mirror of https://github.com/svaarala/duktape.git
Sami Vaarala
9 years ago
8 changed files with 604 additions and 1 deletions
@ -0,0 +1,5 @@ |
|||
# Just for manual testing
|
|||
.PHONY: test |
|||
test: duk_minimal_printf.c |
|||
gcc -fno-stack-protector -m32 -otest -Wall -Wextra -Os -fomit-frame-pointer duk_minimal_printf.c test.c |
|||
./test |
@ -0,0 +1,110 @@ |
|||
============================================== |
|||
Minimal sprintf/sscanf replacement for Duktape |
|||
============================================== |
|||
|
|||
The ``duk_minimal_printf.c`` provides a portable provider for sprintf()/scanf() |
|||
with a feature set matching minimally what Duktape needs. The provider |
|||
compiles to less than 1kB. The functions provided are:: |
|||
|
|||
sprintf() |
|||
snprintf() |
|||
vsnprintf() |
|||
sscanf() |
|||
|
|||
Assumptions: |
|||
|
|||
* ``sizeof(void *) <= sizeof(long)`` |
|||
|
|||
* ``sizeof(long) <= 8`` |
|||
|
|||
Note that these assumptions don't hold e.g. on 64-bit Windows. This printf |
|||
provider is mostly useful for low memory targets where these assumptions are |
|||
typically not an issue. The limitations are easy to fix if one relies more |
|||
on platform typing. |
|||
|
|||
Supported formatting |
|||
==================== |
|||
|
|||
sprintf() |
|||
--------- |
|||
|
|||
Duktape relies on a ``sprintf()`` provider which supports at least the |
|||
following (this list is from Duktape 1.5.0):: |
|||
|
|||
%c |
|||
%s |
|||
%p |
|||
|
|||
%02d |
|||
%03d |
|||
%ld |
|||
%lld (JSON fast path only) |
|||
|
|||
%lu |
|||
|
|||
%lx |
|||
%02lx |
|||
%08lx |
|||
|
|||
This minimal provider supports the following slightly different set: |
|||
|
|||
* Character format ``%c``. |
|||
|
|||
* String format ``%s``. |
|||
|
|||
* Pointer format ``%p``. |
|||
|
|||
* Integer formats ``%d``, ``%ld``, ``%lu`` with optional padding and |
|||
length modifiers. |
|||
|
|||
* Hex formats ``%x``, ``%lx`` with optional padding and length modifiers. |
|||
|
|||
The ``%lld`` format is not supported to avoid depending on the ``long long`` |
|||
type; this makes the replacement incompatible with the JSON fast path which |
|||
must thus be disabled. |
|||
|
|||
sscanf() |
|||
-------- |
|||
|
|||
There's only one call site for ``sscanf()``, for JX parsing of pointers:: |
|||
|
|||
duk_bi_json.c: (void) DUK_SSCANF((const char *) js_ctx->p, DUK_STR_FMT_PTR, &voidptr); |
|||
|
|||
The exact format string here is ``%p`` and nothing else needs to be supported. |
|||
Further, when the minimal printf/scanf providers are used together we only |
|||
need to parse what we produce. In particular: |
|||
|
|||
* Pointer prefix is ``0x``, no need to match ``0X`` for example. |
|||
|
|||
* All digits are ``[0-9a-f]`` with no need to match uppercase. |
|||
|
|||
Building "duk" with minimal printf/scanf |
|||
======================================== |
|||
|
|||
The necessary defines in ``duk_config.h`` can be given to genconfig, but you |
|||
can also just make the following manual additions to the bottom of the config |
|||
file:: |
|||
|
|||
#include "duk_minimal_printf.h" |
|||
|
|||
#undef DUK_SPRINTF |
|||
#define DUK_SPRINTF duk_minimal_sprintf |
|||
#undef DUK_SNPRINTF |
|||
#define DUK_SNPRINTF duk_minimal_snprintf |
|||
#undef DUK_VSNPRINTF |
|||
#define DUK_VSNPRINTF duk_minimal_vsnprintf |
|||
#undef DUK_SSCANF |
|||
#define DUK_SSCANF duk_minimal_sscanf |
|||
|
|||
Then just add ``duk_minimal_printf.c`` to build and compile the application. |
|||
|
|||
Future work |
|||
=========== |
|||
|
|||
* Add support for ``%lld`` (maybe conditional) to allow JSON fast path to |
|||
be supported. |
|||
|
|||
* Add support for platforms such as 64-bit Windows where |
|||
``sizeof(long) < sizeof(void *)``. This can be achieved by using a few |
|||
typedefs internally; typedef an integer type large enough to hold all |
|||
formatted types. |
@ -0,0 +1,271 @@ |
|||
/*
|
|||
* Minimal sprintf() for Duktape. |
|||
*/ |
|||
|
|||
#include <stdarg.h> /* va_list etc */ |
|||
#include <stddef.h> /* size_t */ |
|||
#include <stdint.h> /* SIZE_MAX */ |
|||
|
|||
#define DUK__WRITE_CHAR(c) do { \ |
|||
if (off < size) { \ |
|||
str[off] = (char) c; \ |
|||
} \ |
|||
off++; \ |
|||
} while (0) |
|||
|
|||
static const char duk__fmt_nybbles[16] = { |
|||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' |
|||
}; |
|||
|
|||
static size_t duk__format_long(char *str, size_t size, size_t off, int fixed_length, char pad, int radix, int neg_sign, unsigned long v) { |
|||
char buf[24]; /* 2^64 = 18446744073709552000, length 20 */ |
|||
char *required; |
|||
char *p; |
|||
int i; |
|||
|
|||
/* Format in reverse order first. Ensure at least one digit is output
|
|||
* to handle '0' correctly. Note that space padding and zero padding |
|||
* handle negative sign differently: |
|||
* |
|||
* %9d and -321 => ' -321' |
|||
* %09d and -321 => '-00000321' |
|||
*/ |
|||
|
|||
for (i = 0; i < (int) sizeof(buf); i++) { |
|||
buf[i] = pad; /* compiles into memset() equivalent, avoid memset() dependency */ |
|||
} |
|||
|
|||
p = buf; |
|||
do { |
|||
*p++ = duk__fmt_nybbles[v % radix]; |
|||
v /= radix; |
|||
} while (v != 0); |
|||
|
|||
required = buf + fixed_length; |
|||
if (pad == (char) '0' && fixed_length > 0 /* handle "%0d" correctly, though insane */) { |
|||
/* Leave space for negative sign. */ |
|||
if (p < required - neg_sign) { |
|||
p = required - neg_sign; |
|||
} |
|||
} |
|||
if (neg_sign) { |
|||
*p++ = '-'; |
|||
} |
|||
if (p < required) { |
|||
p = required; |
|||
} |
|||
|
|||
/* Now [buf,p[ contains the result in reverse; copy into place. */ |
|||
|
|||
while (p > buf) { |
|||
p--; |
|||
DUK__WRITE_CHAR(*p); |
|||
} |
|||
|
|||
return off; |
|||
} |
|||
|
|||
static int duk__parse_pointer(const char *str, void **out) { |
|||
const unsigned char *p; |
|||
long val; /* assume void * fits into long */ |
|||
int count; |
|||
unsigned char ch; |
|||
|
|||
/* We only need to parse what our minimal printf() produces, so that
|
|||
* we can check for a '0x' prefix, and assume all hex digits are |
|||
* lowercase. |
|||
*/ |
|||
|
|||
p = (const unsigned char *) str; |
|||
if (*p++ != (unsigned char) '0') { |
|||
return 0; |
|||
} |
|||
if (*p++ != (unsigned char) 'x') { |
|||
return 0; |
|||
} |
|||
|
|||
for (val = 0, count = 0; count < (int) (sizeof(void *) * 2); count++) { |
|||
ch = *p++; |
|||
|
|||
val <<= 4; |
|||
if (ch >= (unsigned char) '0' && ch <= (unsigned char) '9') { |
|||
val += ch - (unsigned char) '0'; |
|||
} else if (ch >= (unsigned char) 'a' && ch <= (unsigned char) 'f') { |
|||
val += ch - (unsigned char) 'a' + 0x0a; |
|||
} else { |
|||
return 0; |
|||
} |
|||
} |
|||
|
|||
/* The input may end at a NUL or garbage may follow. As long as we
|
|||
* parse the '%p' correctly, garbage is allowed to follow, and the |
|||
* JX pointer parsing also relies on that. |
|||
*/ |
|||
|
|||
*out = (void *) val; |
|||
return 1; |
|||
} |
|||
|
|||
int duk_minimal_vsnprintf(char *str, size_t size, const char *format, va_list ap) { |
|||
size_t off = 0; |
|||
const char *p; |
|||
const char *p_tmp; |
|||
const char *p_fmt_start; |
|||
char c; |
|||
char pad; |
|||
int fixed_length; |
|||
int is_long; |
|||
|
|||
/* Assume str != NULL unless size == 0.
|
|||
* Assume format != NULL. |
|||
*/ |
|||
|
|||
p = format; |
|||
for (;;) { |
|||
c = *p++; |
|||
if (c == (char) 0) { |
|||
break; |
|||
} |
|||
if (c != (char) '%') { |
|||
DUK__WRITE_CHAR(c); |
|||
continue; |
|||
} |
|||
|
|||
/* Start format sequence. Scan flags and format specifier. */ |
|||
|
|||
p_fmt_start = p - 1; |
|||
is_long = 0; |
|||
pad = ' '; |
|||
fixed_length = 0; |
|||
for (;;) { |
|||
c = *p++; |
|||
if (c == (char) 'l') { |
|||
is_long = 1; |
|||
} else if (c == (char) '0') { |
|||
/* Only support pad character '0'. */ |
|||
pad = '0'; |
|||
} else if (c >= (char) '1' && c <= (char) '9') { |
|||
/* Only support fixed lengths 1-9. */ |
|||
fixed_length = (int) (c - (char) '0'); |
|||
} else if (c == (char) 'd') { |
|||
long v; |
|||
int neg_sign = 0; |
|||
if (is_long) { |
|||
v = va_arg(ap, long); |
|||
} else { |
|||
v = (long) va_arg(ap, int); |
|||
} |
|||
if (v < 0) { |
|||
neg_sign = 1; |
|||
v = -v; |
|||
} |
|||
off = duk__format_long(str, size, off, fixed_length, pad, 10, neg_sign, (unsigned long) v); |
|||
break; |
|||
} else if (c == (char) 'u') { |
|||
unsigned long v; |
|||
if (is_long) { |
|||
v = va_arg(ap, unsigned long); |
|||
} else { |
|||
v = (unsigned long) va_arg(ap, unsigned int); |
|||
} |
|||
off = duk__format_long(str, size, off, fixed_length, pad, 10, 0, v); |
|||
break; |
|||
} else if (c == (char) 'x') { |
|||
unsigned long v; |
|||
if (is_long) { |
|||
v = va_arg(ap, unsigned long); |
|||
} else { |
|||
v = (unsigned long) va_arg(ap, unsigned int); |
|||
} |
|||
off = duk__format_long(str, size, off, fixed_length, pad, 16, 0, v); |
|||
break; |
|||
} else if (c == (char) 'c') { |
|||
char v; |
|||
v = va_arg(ap, int); /* intentionally not 'char' */ |
|||
DUK__WRITE_CHAR(v); |
|||
break; |
|||
} else if (c == (char) 's') { |
|||
const char *v; |
|||
char c_tmp; |
|||
v = va_arg(ap, const char *); |
|||
if (v) { |
|||
for (;;) { |
|||
c_tmp = *v++; |
|||
if (c_tmp) { |
|||
DUK__WRITE_CHAR(c_tmp); |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
break; |
|||
} else if (c == (char) 'p') { |
|||
/* Assume a void * can be represented by 'long'. This is not
|
|||
* always the case. NULL pointer is printed out as 0x0000... |
|||
*/ |
|||
void *v; |
|||
v = va_arg(ap, void *); |
|||
DUK__WRITE_CHAR('0'); |
|||
DUK__WRITE_CHAR('x'); |
|||
off = duk__format_long(str, size, off, sizeof(void *) * 2, '0', 16, 0, (unsigned long) v); |
|||
break; |
|||
} else { |
|||
/* Unrecognized, just copy verbatim. */ |
|||
#if 0 |
|||
DUK__WRITE_CHAR('!'); |
|||
#endif |
|||
for (p_tmp = p_fmt_start; p_tmp != p; p_tmp++) { |
|||
DUK__WRITE_CHAR(*p_tmp); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (off < size) { |
|||
str[off] = (char) 0; /* No increment for 'off', not counted in return value. */ |
|||
} else if (size > 0) { |
|||
/* Forced termination. */ |
|||
str[size - 1] = 0; |
|||
} |
|||
|
|||
return (int) off; |
|||
} |
|||
|
|||
int duk_minimal_snprintf(char *str, size_t size, const char *format, ...) { |
|||
va_list ap; |
|||
int ret; |
|||
va_start(ap, format); |
|||
ret = duk_minimal_vsnprintf(str, size, format, ap); |
|||
va_end(ap); |
|||
return ret; |
|||
} |
|||
|
|||
int duk_minimal_sprintf(char *str, const char *format, ...) { |
|||
va_list ap; |
|||
int ret; |
|||
va_start(ap, format); |
|||
ret = duk_minimal_vsnprintf(str, SIZE_MAX, format, ap); |
|||
va_end(ap); |
|||
return ret; |
|||
} |
|||
|
|||
int duk_minimal_sscanf(const char *str, const char *format, ...) { |
|||
va_list ap; |
|||
int ret; |
|||
void **out; |
|||
|
|||
/* Only the exact "%p" format is supported. */ |
|||
if (format[0] != (char) '%' || format[1] != (char) 'p' || |
|||
format[2] != (char) 0) { |
|||
} |
|||
|
|||
va_start(ap, format); |
|||
out = va_arg(ap, void **); |
|||
ret = duk__parse_pointer(str, out); |
|||
va_end(ap); |
|||
|
|||
return ret; |
|||
} |
|||
|
|||
#undef DUK__WRITE_CHAR |
@ -0,0 +1,12 @@ |
|||
#if !defined(DUK_MINIMAL_PRINTF_H_INCLUDED) |
|||
#define DUK_MINIMAL_PRINTF_H_INCLUDED |
|||
|
|||
#include <stdarg.h> /* va_list etc */ |
|||
#include <stddef.h> /* size_t */ |
|||
|
|||
extern int duk_minimal_sprintf(char *str, const char *format, ...); |
|||
extern int duk_minimal_snprintf(char *str, size_t size, const char *format, ...); |
|||
extern int duk_minimal_vsnprintf(char *str, size_t size, const char *format, va_list ap); |
|||
extern int duk_minimal_sscanf(const char *str, const char *format, ...); |
|||
|
|||
#endif /* DUK_MINIMAL_PRINTF_H_INCLUDED */ |
@ -0,0 +1,190 @@ |
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
#include "duk_minimal_printf.h" |
|||
|
|||
char buffer[32]; |
|||
|
|||
static void init_buffer(void) { |
|||
int i; |
|||
|
|||
for (i = 0; i < (int) sizeof(buffer); i++) { |
|||
buffer[i] = 0xff; |
|||
} |
|||
} |
|||
|
|||
static void dump_buffer(void) { |
|||
int i; |
|||
unsigned char c; |
|||
|
|||
printf("Buffer: '"); |
|||
for (i = 0; i < (int) sizeof(buffer); i++) { |
|||
c = (unsigned char) buffer[i]; |
|||
if (c < 0x20 || c >= 0x7e) { |
|||
printf("<%02x>", (unsigned int) c); |
|||
} else { |
|||
printf("%c", (int) c); |
|||
} |
|||
} |
|||
printf("'"); |
|||
#if 0 |
|||
printf(" -> "); |
|||
printf("Buffer:"); |
|||
for (i = 0; i < sizeof(buffer); i++) { |
|||
c = (unsigned char) buffer[i]; |
|||
if (c <= 0x20 || c >= 0x7e) { |
|||
printf(" <%02x>", (unsigned int) c); |
|||
} else { |
|||
printf(" %c", (char) c); |
|||
} |
|||
} |
|||
#endif |
|||
printf("\n"); |
|||
} |
|||
|
|||
int main(int argc, char *argv[]) { |
|||
int ret; |
|||
void *voidptr; |
|||
int i; |
|||
|
|||
(void) argc; (void) argv; |
|||
|
|||
/* Char format. */ |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, sizeof(buffer), "foo %c bar", 'Z'); |
|||
dump_buffer(); |
|||
|
|||
/* Signed long format. */ |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, sizeof(buffer), "%ld %9ld", (long) 123, (long) 4321); |
|||
dump_buffer(); |
|||
|
|||
/* Signed long with zero padding. */ |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, sizeof(buffer), "%09ld", (long) 4321); |
|||
dump_buffer(); |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, sizeof(buffer), "%03ld %03ld %03ld", (long) -4321, (long) -432, (long) -43); |
|||
dump_buffer(); |
|||
|
|||
/* Unsigned long with zero padding. */ |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, sizeof(buffer), "%03lu %03lu %03lu", (long) -4321, (long) -432, (long) -43); |
|||
dump_buffer(); |
|||
|
|||
/* Signed integer. */ |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, sizeof(buffer), "%d %9d", (int) 0, (int) 4321); |
|||
dump_buffer(); |
|||
|
|||
/* Signed negative integer, fixed field width. */ |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, sizeof(buffer), "%9d", (int) -321); |
|||
dump_buffer(); |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, sizeof(buffer), "%09d", (int) -321); |
|||
dump_buffer(); |
|||
printf(" -- printf comparison: %9d %09d\n", -321, -321); |
|||
|
|||
/* Hex formatting. */ |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, sizeof(buffer), "%03x %03lx 0x%08lx", (int) 510, (long) 5105, (long) 0xdeadbeef); |
|||
dump_buffer(); |
|||
|
|||
/* Pointer formatting, NULL and non-NULL. */ |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, sizeof(buffer), "%p %p", (void *) NULL, (void *) buffer); |
|||
dump_buffer(); |
|||
|
|||
/* File/line like format test. */ |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, sizeof(buffer), "%s:%d", "foo bar quux", 123); |
|||
dump_buffer(); |
|||
|
|||
/* Zero size output buffer. */ |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, 0, "%s:%d", "foo bar quux", 123); |
|||
dump_buffer(); |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, 0, ""); |
|||
dump_buffer(); |
|||
|
|||
/* NUL terminator boundary test. */ |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, 7, "foo: %s", "bar"); |
|||
dump_buffer(); |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, 8, "foo: %s", "bar"); |
|||
dump_buffer(); |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, 9, "foo: %s", "bar"); |
|||
dump_buffer(); |
|||
|
|||
/* sprintf() binding, uses SIZE_MAX internally. */ |
|||
init_buffer(); |
|||
duk_minimal_sprintf(buffer, "unbounded print %s", "foo"); |
|||
dump_buffer(); |
|||
|
|||
/* Pointer formatting; non-NULL and NULL. */ |
|||
init_buffer(); |
|||
duk_minimal_snprintf(buffer, sizeof(buffer), "%p %p", (void *) NULL, (void *) 0xdeadbeef); |
|||
dump_buffer(); |
|||
|
|||
/* Pointer parsing, non-NULL (32-bit) pointer. */ |
|||
voidptr = (void *) 123; |
|||
ret = duk_minimal_sscanf("0xdeadbeef", "%p", &voidptr); |
|||
printf("ret=%d, void pointer: %p\n", ret, voidptr); |
|||
|
|||
/* Pointer parsing, NULL (32-bit) pointer. */ |
|||
voidptr = (void *) 123; |
|||
ret = duk_minimal_sscanf("0x00000000", "%p", &voidptr); |
|||
printf("ret=%d, void pointer: %p\n", ret, voidptr); |
|||
|
|||
/* Pointer parsing, non-NULL (32-bit) pointer but garbage follows. */ |
|||
voidptr = (void *) 123; |
|||
ret = duk_minimal_sscanf("0xdeadbeefx", "%p", &voidptr); |
|||
printf("ret=%d, void pointer: %p\n", ret, voidptr); |
|||
|
|||
/* Fixed width test over a range of widths. */ |
|||
for (i = 0; i <= 9; i++) { |
|||
char fmtbuf[16]; |
|||
|
|||
printf("--- pos/neg fixed width test, i=%d\n", i); |
|||
|
|||
/* %0<i>d. %00d makes no sense, but tested anyway. */ |
|||
memset((void *) fmtbuf, 0, sizeof(fmtbuf)); |
|||
fmtbuf[0] = (char) '%'; |
|||
fmtbuf[1] = (char) '0'; |
|||
fmtbuf[2] = (char) ('0' + i); |
|||
fmtbuf[3] = 'd'; |
|||
init_buffer(); |
|||
duk_minimal_sprintf(buffer, (const char *) fmtbuf, 321); |
|||
dump_buffer(); |
|||
init_buffer(); |
|||
duk_minimal_sprintf(buffer, (const char *) fmtbuf, -321); |
|||
dump_buffer(); |
|||
printf(" ==> printf: |"); |
|||
printf((const char *) fmtbuf, 321); |
|||
printf("| |"); |
|||
printf((const char *) fmtbuf, -321); |
|||
printf("|\n"); |
|||
|
|||
/* %<i>d. */ |
|||
memset((void *) fmtbuf, 0, sizeof(fmtbuf)); |
|||
fmtbuf[0] = (char) '%'; |
|||
fmtbuf[1] = (char) ('0' + i); |
|||
fmtbuf[2] = 'd'; |
|||
init_buffer(); |
|||
duk_minimal_sprintf(buffer, (const char *) fmtbuf, 321); |
|||
dump_buffer(); |
|||
init_buffer(); |
|||
duk_minimal_sprintf(buffer, (const char *) fmtbuf, -321); |
|||
dump_buffer(); |
|||
printf(" ==> printf: |"); |
|||
printf((const char *) fmtbuf, 321); |
|||
printf("| |"); |
|||
printf((const char *) fmtbuf, -321); |
|||
printf("|\n"); |
|||
} |
|||
|
|||
return 0; |
|||
} |
Loading…
Reference in new issue