From 0536b42693d041d007662105cc223bd929dc054a Mon Sep 17 00:00:00 2001 From: Charlie Gordon Date: Wed, 20 Mar 2024 20:07:39 +0100 Subject: [PATCH] Add custom printf version - add `js_snprintf`, `js_printf`... to handle extra conversions: - support for wxx length modifier - support for `%b` and `%B` - `%oa` and `%#oa` to convert `JSAtom` values - `%ps` to convert `JSString` values - add `dbuf_vprintf_fun` replaceable `dbuf_printf` handler - change `JS_DumpString` to `JS_FormatString` to convert `JSSAtom` to quoted strings - change `JS_AtomGetStrRT` to `JS_FormatAtom` to convert `JSAtom` to strings - change `JS_AtomGetStr` to return direct string pointer for builtin atoms - remove `print_atom` - use custom conversions for trace messages and error messages - add support for `%b`, `%B` and `w` length modifier in `std.printf` - remove error handlers: `JS_ThrowTypeErrorAtom` and `JS_ThrowSyntaxErrorAtom` - add `is_lower_ascii()` and `to_lower_ascii()` - add floating point conversions and wide string conversions - unbreak compilation: prevent name collision on pow10 - minimize `vsnprintf` calls in `dbuf_vprintf_default` --- cutils.c | 172 ++++--- cutils.h | 63 ++- quickjs-libc.c | 68 ++- quickjs-printf.c | 1092 +++++++++++++++++++++++++++++++++++++++++++++ quickjs-printf.h | 63 +++ quickjs.c | 578 ++++++++++-------------- tests/test_conv.c | 70 +-- 7 files changed, 1621 insertions(+), 485 deletions(-) create mode 100644 quickjs-printf.c create mode 100644 quickjs-printf.h diff --git a/cutils.c b/cutils.c index 56982df..e04b1fe 100644 --- a/cutils.c +++ b/cutils.c @@ -35,20 +35,19 @@ #include "cutils.h" #undef NANOSEC -#define NANOSEC ((uint64_t) 1e9) +#define NANOSEC 1000000000 #pragma GCC visibility push(default) -void pstrcpy(char *buf, int buf_size, const char *str) +void pstrcpy(char *buf, size_t buf_size, const char *str) { - int c; char *q = buf; if (buf_size <= 0) return; for(;;) { - c = *str++; + char c = *str++; if (c == 0 || q >= buf + buf_size - 1) break; *q++ = c; @@ -57,10 +56,9 @@ void pstrcpy(char *buf, int buf_size, const char *str) } /* strcat and truncate. */ -char *pstrcat(char *buf, int buf_size, const char *s) +char *pstrcat(char *buf, size_t buf_size, const char *s) { - int len; - len = strlen(buf); + size_t len = strlen(buf); if (len < buf_size) pstrcpy(buf + len, buf_size - len, s); return buf; @@ -105,11 +103,6 @@ void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func) s->realloc_func = realloc_func; } -void dbuf_init(DynBuf *s) -{ - dbuf_init2(s, NULL, NULL); -} - /* return < 0 if error */ int dbuf_realloc(DynBuf *s, size_t new_size) { @@ -178,29 +171,59 @@ int dbuf_putstr(DynBuf *s, const char *str) return dbuf_put(s, (const uint8_t *)str, strlen(str)); } -int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s, - const char *fmt, ...) +static int dbuf_vprintf_default(DynBuf *s, const char *fmt, va_list ap) +{ + char buf[128]; + va_list arg; + size_t size, avail, ulen; + int len; + char *dest; + + dest = buf; + size = sizeof buf; + avail = s->allocated_size - s->size; + if (avail > size) { + dest = (char *)(s->buf + s->size); + size = avail; + } + va_copy(arg, ap); + len = vsnprintf(dest, size, fmt, arg); + va_end(arg); + + if (len < 0) + return len; + + ulen = (size_t)len; + if (ulen >= avail) { + if (dbuf_realloc(s, s->size + ulen + 1)) + return -1; + } + if (dest == buf && ulen < sizeof buf) { + memcpy(s->buf + s->size, buf, ulen + 1); + } else + if (ulen >= avail) { + avail = s->allocated_size - s->size; + va_copy(arg, ap); + vsnprintf((char *)(s->buf + s->size), avail, fmt, arg); + va_end(arg); + } + s->size += ulen; + return len; +} + +/* replaceable formatter */ +int (*dbuf_vprintf_fun)(DynBuf *s, const char *fmt, va_list ap) = dbuf_vprintf_default; + +__attribute__((format(printf, 2, 3))) +int dbuf_printf(DynBuf *s, const char *fmt, ...) { va_list ap; - char buf[128]; int len; va_start(ap, fmt); - len = vsnprintf(buf, sizeof(buf), fmt, ap); + len = (*dbuf_vprintf_fun)(s, fmt, ap); va_end(ap); - if (len < sizeof(buf)) { - /* fast case */ - return dbuf_put(s, (uint8_t *)buf, len); - } else { - if (dbuf_realloc(s, s->size + len + 1)) - return -1; - va_start(ap, fmt); - vsnprintf((char *)(s->buf + s->size), s->allocated_size - s->size, - fmt, ap); - va_end(ap); - s->size += len; - } - return 0; + return len; } void dbuf_free(DynBuf *s) @@ -589,6 +612,7 @@ overflow: /* 2 <= base <= 36 */ char const digits36[36] = "0123456789abcdefghijklmnopqrstuvwxyz"; +char const digits36_upper[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; #define USE_SPECIAL_RADIX_10 1 // special case base 10 radix conversions #define USE_SINGLE_CASE_FAST 1 // special case single digit numbers @@ -600,7 +624,7 @@ char const digits36[36] = "0123456789abcdefghijklmnopqrstuvwxyz"; else \ buf = (buf << 8) | (c) -size_t u7toa_shift(char dest[minimum_length(8)], uint32_t n) +static size_t u7toa_shift(char dest[minimum_length(8)], uint32_t n) { size_t len = 1; uint64_t buf = 0; @@ -615,7 +639,7 @@ size_t u7toa_shift(char dest[minimum_length(8)], uint32_t n) return len; } -size_t u07toa_shift(char dest[minimum_length(8)], uint32_t n, size_t len) +static size_t u07toa_shift(char dest[minimum_length(8)], uint32_t n, size_t len) { size_t i; dest += len; @@ -641,8 +665,9 @@ size_t u32toa(char buf[minimum_length(11)], uint32_t n) #define TEN_POW_7 10000000 if (n >= TEN_POW_7) { uint32_t quo = n / TEN_POW_7; + size_t len; n %= TEN_POW_7; - size_t len = u7toa_shift(buf, quo); + len = u7toa_shift(buf, quo); return u07toa_shift(buf, n, len); } return u7toa_shift(buf, n); @@ -650,30 +675,31 @@ size_t u32toa(char buf[minimum_length(11)], uint32_t n) size_t u64toa(char buf[minimum_length(21)], uint64_t n) { - if (likely(n < 0x100000000)) - return u32toa(buf, n); - size_t len; + + if (likely(n < 0x100000000)) + return u32toa(buf, (uint32_t)n); + if (n >= TEN_POW_7) { uint64_t n1 = n / TEN_POW_7; n %= TEN_POW_7; if (n1 >= TEN_POW_7) { - uint32_t quo = n1 / TEN_POW_7; + uint32_t quo = (uint32_t)(n1 / TEN_POW_7); n1 %= TEN_POW_7; len = u7toa_shift(buf, quo); - len = u07toa_shift(buf, n1, len); + len = u07toa_shift(buf, (uint32_t)n1, len); } else { - len = u7toa_shift(buf, n1); + len = u7toa_shift(buf, (uint32_t)n1); } - return u07toa_shift(buf, n, len); + return u07toa_shift(buf, (uint32_t)n, len); } - return u7toa_shift(buf, n); + return u7toa_shift(buf, (uint32_t)n); } size_t i32toa(char buf[minimum_length(12)], int32_t n) { if (likely(n >= 0)) - return u32toa(buf, n); + return u32toa(buf, (uint32_t)n); buf[0] = '-'; return 1 + u32toa(buf + 1, -(uint32_t)n); @@ -682,7 +708,7 @@ size_t i32toa(char buf[minimum_length(12)], int32_t n) size_t i64toa(char buf[minimum_length(22)], int64_t n) { if (likely(n >= 0)) - return u64toa(buf, n); + return u64toa(buf, (uint64_t)n); buf[0] = '-'; return 1 + u64toa(buf + 1, -(uint64_t)n); @@ -713,7 +739,7 @@ size_t u32toa_radix(char buf[minimum_length(33)], uint32_t n, unsigned base) shift = radix_shift[base & 63]; if (shift) { uint32_t mask = (1 << shift) - 1; - size_t len = (32 - clz32(n) + shift - 1) / shift; + size_t len = (size_t)((32 - clz32(n) + shift - 1) / shift); size_t last = n & mask; char *end = buf + len; n >>= shift; @@ -729,13 +755,14 @@ size_t u32toa_radix(char buf[minimum_length(33)], uint32_t n, unsigned base) } else { size_t len = 2; size_t last = n % base; - n /= base; uint32_t nbase = base; + char *end; + n /= base; while (n >= nbase) { nbase *= base; len++; } - char *end = buf + len; + end = buf + len; *end-- = '\0'; *end-- = digits36[last]; while (n >= base) { @@ -762,33 +789,36 @@ size_t u64toa_radix(char buf[minimum_length(65)], uint64_t n, unsigned base) buf[0] = digits36[n]; buf[1] = '\0'; return 1; - } - uint64_t mask = (1 << shift) - 1; - size_t len = (64 - clz64(n) + shift - 1) / shift; - size_t last = n & mask; - char *end = buf + len; - n >>= shift; - *end-- = '\0'; - *end-- = digits36[last]; - while (n >= base) { - size_t quo = n & mask; + } else { + uint64_t mask = (1 << shift) - 1; + size_t len = (size_t)((64 - clz64(n) + shift - 1) / shift); + size_t last = n & mask; + char *end = buf + len; n >>= shift; - *end-- = digits36[quo]; + *end-- = '\0'; + *end-- = digits36[last]; + while (n >= base) { + size_t quo = n & mask; + n >>= shift; + *end-- = digits36[quo]; + } + *end = digits36[n]; + return len; } - *end = digits36[n]; - return len; + } else + if (likely(n < 0x100000000)) { + return u32toa_radix(buf, (uint32_t)n, base); } else { - if (likely(n < 0x100000000)) - return u32toa_radix(buf, n, base); size_t last = n % base; - n /= base; uint64_t nbase = base; size_t len = 2; + char *end; + n /= base; while (n >= nbase) { nbase *= base; len++; } - char *end = buf + len; + end = buf + len; *end-- = '\0'; *end-- = digits36[last]; while (n >= base) { @@ -813,7 +843,7 @@ size_t i32toa_radix(char buf[minimum_length(34)], int32_t n, unsigned int base) size_t i64toa_radix(char buf[minimum_length(66)], int64_t n, unsigned int base) { if (likely(n >= 0)) - return u64toa_radix(buf, n, base); + return u64toa_radix(buf, (uint64_t)n, base); buf[0] = '-'; return 1 + u64toa_radix(buf + 1, -(uint64_t)n, base); @@ -1008,7 +1038,14 @@ static inline void *med3(void *a, void *b, void *c, cmp_f cmp, void *opaque) /* pointer based version with local stack and insertion sort threshhold */ void rqsort(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) { - struct { uint8_t *base; size_t count; int depth; } stack[50], *sp = stack; + struct { + uint8_t *base; + size_t count; + int depth; +#if SIZE_MAX > UINT_MAX + int pad; +#endif + } stack[50], *sp = stack; uint8_t *ptr, *pi, *pj, *plt, *pgt, *top, *m; size_t m4, i, lt, gt, span, span2; int c, depth; @@ -1391,12 +1428,9 @@ int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout) { if (r == 0) return 0; - if (r == ETIMEDOUT) - return -1; + if (r != ETIMEDOUT) + abort(); - abort(); - - /* Pacify some compilers. */ return -1; } diff --git a/cutils.h b/cutils.h index a5da60d..a9cb4b8 100644 --- a/cutils.h +++ b/cutils.h @@ -25,6 +25,7 @@ #ifndef CUTILS_H #define CUTILS_H +#include #include #include #include @@ -72,24 +73,27 @@ static void *__builtin_frame_address(unsigned int level) { // https://stackoverflow.com/a/6849629 #undef FORMAT_STRING -#if _MSC_VER >= 1400 -# include -# if _MSC_VER > 1400 -# define FORMAT_STRING(p) _Printf_format_string_ p -# else -# define FORMAT_STRING(p) __format_string p -# endif /* FORMAT_STRING */ -#else -# define FORMAT_STRING(p) p +#ifdef _MSC_VER +# if _MSC_VER >= 1400 +# include +# if _MSC_VER > 1400 +# define FORMAT_STRING(p) _Printf_format_string_ p +# else +# define FORMAT_STRING(p) __format_string p +# endif +# endif #endif /* _MSC_VER */ +#ifndef FORMAT_STRING +# define FORMAT_STRING(p) p +#endif #if defined(_MSC_VER) && !defined(__clang__) #include -#define INF INFINITY -#define NEG_INF -INFINITY +#define INF INFINITY +#define NEG_INF (-INFINITY) #else -#define INF (1.0/0.0) -#define NEG_INF (-1.0/0.0) +#define INF (1.0/0.0) +#define NEG_INF (-1.0/0.0) #endif #define xglue(x, y) x ## y @@ -126,8 +130,8 @@ enum { }; #endif -void pstrcpy(char *buf, int buf_size, const char *str); -char *pstrcat(char *buf, int buf_size, const char *s); +void pstrcpy(char *buf, size_t buf_size, const char *str); +char *pstrcat(char *buf, size_t buf_size, const char *s); int strstart(const char *str, const char *val, const char **ptr); int has_suffix(const char *str, const char *suffix); @@ -191,6 +195,7 @@ static inline int64_t min_int64(int64_t a, int64_t b) static inline int clz32(unsigned int a) { #if defined(_MSC_VER) && !defined(__clang__) + // XXX: unsigned int _lzcnt_u32(unsigned int a) unsigned long index; _BitScanReverse(&index, a); return 31 - index; @@ -203,6 +208,7 @@ static inline int clz32(unsigned int a) static inline int clz64(uint64_t a) { #if defined(_MSC_VER) && !defined(__clang__) + // XXX: unsigned int _lzcnt_u64(unsigned __int64 a) unsigned long index; _BitScanReverse64(&index, a); return 63 - index; @@ -215,6 +221,7 @@ static inline int clz64(uint64_t a) static inline int ctz32(unsigned int a) { #if defined(_MSC_VER) && !defined(__clang__) + // XXX: unsigned int _tzcnt_u32 (unsigned int a) unsigned long index; _BitScanForward(&index, a); return index; @@ -227,6 +234,7 @@ static inline int ctz32(unsigned int a) static inline int ctz64(uint64_t a) { #if defined(_MSC_VER) && !defined(__clang__) + // XXX: unsigned int _tzcnt_u64 (unsigned __int64 a) unsigned long index; _BitScanForward64(&index, a); return index; @@ -310,7 +318,7 @@ static inline void put_u8(uint8_t *tab, uint8_t val) #ifndef bswap16 static inline uint16_t bswap16(uint16_t x) { - return (x >> 8) | (x << 8); + return (uint16_t)((x >> 8) | (x << 8)); } #endif @@ -337,7 +345,7 @@ static inline uint64_t bswap64(uint64_t v) #endif static inline void inplace_bswap16(uint8_t *tab) { - put_u16(tab, bswap16(get_u16(tab))); + put_u16(tab, bswap16((uint16_t)get_u16(tab))); } static inline void inplace_bswap32(uint8_t *tab) { @@ -352,12 +360,15 @@ typedef struct DynBuf { size_t size; size_t allocated_size; BOOL error; /* true if a memory allocation error occurred */ +#if SIZE_MAX > UINT_MAX + int pad; /* prevent alignment warning */ +#endif DynBufReallocFunc *realloc_func; void *opaque; /* for realloc_func */ } DynBuf; -void dbuf_init(DynBuf *s); void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func); +static inline void dbuf_init(DynBuf *s) { dbuf_init2(s, NULL, NULL); } int dbuf_realloc(DynBuf *s, size_t new_size); int dbuf_write(DynBuf *s, size_t offset, const void *data, size_t len); int dbuf_put(DynBuf *s, const void *data, size_t len); @@ -376,8 +387,9 @@ static inline int dbuf_put_u64(DynBuf *s, uint64_t val) { return dbuf_put(s, (uint8_t *)&val, 8); } -int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s, - FORMAT_STRING(const char *fmt), ...); +__attribute__((format(printf, 2, 3))) +int dbuf_printf(DynBuf *s, FORMAT_STRING(const char *fmt), ...); +extern int (*dbuf_vprintf_fun)(DynBuf *s, const char *fmt, va_list ap); void dbuf_free(DynBuf *s); static inline BOOL dbuf_error(DynBuf *s) { return s->error; @@ -450,15 +462,24 @@ static inline int from_hex(int c) return -1; } +static inline uint8_t is_lower_ascii(uint8_t c) { + return c >= 'a' && c <= 'z'; +} + static inline uint8_t is_upper_ascii(uint8_t c) { return c >= 'A' && c <= 'Z'; } +static inline uint8_t to_lower_ascii(uint8_t c) { + return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; +} + static inline uint8_t to_upper_ascii(uint8_t c) { - return c >= 'a' && c <= 'z' ? c - 'a' + 'A' : c; + return c >= 'a' && c <= 'z' ? c - ('a' - 'A') : c; } extern char const digits36[36]; +extern char const digits36_upper[36]; size_t u32toa(char buf[minimum_length(11)], uint32_t n); size_t i32toa(char buf[minimum_length(12)], int32_t n); size_t u64toa(char buf[minimum_length(21)], uint64_t n); diff --git a/quickjs-libc.c b/quickjs-libc.c index f5c496b..20db6f4 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -185,7 +185,7 @@ static JSValue js_printf_internal(JSContext *ctx, double double_arg; const char *string_arg; /* Use indirect call to dbuf_printf to prevent gcc warning */ - int (*dbuf_printf_fun)(DynBuf *s, const char *fmt, ...) = (void*)dbuf_printf; + int (*dbuf_printf_fun)(DynBuf *s, const char *fmt, ...) = dbuf_printf; js_std_dbuf_init(ctx, &dbuf); @@ -225,7 +225,9 @@ static JSValue js_printf_internal(JSContext *ctx, goto missing; if (JS_ToInt32(ctx, &int32_arg, argv[i++])) goto fail; - q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); + if (q > fmtbuf + sizeof(fmtbuf) - 11) + goto invalid; + q += i32toa(q, int32_arg); fmt++; } else { while (my_isdigit(*fmt)) { @@ -243,7 +245,9 @@ static JSValue js_printf_internal(JSContext *ctx, goto missing; if (JS_ToInt32(ctx, &int32_arg, argv[i++])) goto fail; - q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); + if (q > fmtbuf + sizeof(fmtbuf) - 11) + goto invalid; + q += i32toa(q, int32_arg); fmt++; } else { while (my_isdigit(*fmt)) { @@ -254,10 +258,33 @@ static JSValue js_printf_internal(JSContext *ctx, } } - /* we only support the "l" modifier for 64 bit numbers */ - mod = ' '; - if (*fmt == 'l') { - mod = *fmt++; + /* we only support the "l" modifier for 64 bit numbers + and the w# length modifier with a bitlength of 1 to 64 + */ + // XXX: should use value changing conversions + mod = *fmt; + if (mod == 'w') { + int bitwidth; + if (q >= fmtbuf + sizeof(fmtbuf) - 4) + goto invalid; + *q++ = *fmt++; + if (!(*fmt >= '1' && *fmt <= '9')) + goto invalid; + bitwidth = *fmt - '0'; + *q++ = *fmt++; + if (*fmt >= '0' && *fmt <= '9') { + bitwidth = bitwidth * 10 + *fmt - '0'; + *q++ = *fmt++; + } + if (bitwidth > 32) + mod = 'l'; + } else + if (mod == 'l') { + fmt++; + if (q >= fmtbuf + sizeof(fmtbuf) - 3) + goto invalid; + *q++ = 'l'; + *q++ = 'l'; } /* type */ @@ -286,10 +313,14 @@ static JSValue js_printf_internal(JSContext *ctx, if ((unsigned)int32_arg > 0x10FFFF) int32_arg = 0xFFFD; /* ignore conversion flags, width and precision */ + // XXX: Hash modifier could output pretty Unicode character + // XXX: `l` length modifier is implicit len = utf8_encode(cbuf, int32_arg); dbuf_put(&dbuf, cbuf, len); break; + case 'b': + case 'B': case 'd': case 'i': case 'o': @@ -298,27 +329,12 @@ static JSValue js_printf_internal(JSContext *ctx, case 'X': if (i >= argc) goto missing; + // XXX: should handle BigInt values if (JS_ToInt64Ext(ctx, &int64_arg, argv[i++])) goto fail; if (mod == 'l') { /* 64 bit number */ -#if defined(_WIN32) - if (q >= fmtbuf + sizeof(fmtbuf) - 3) - goto invalid; - q[2] = q[-1]; - q[-1] = 'I'; - q[0] = '6'; - q[1] = '4'; - q[3] = '\0'; dbuf_printf_fun(&dbuf, fmtbuf, (int64_t)int64_arg); -#else - if (q >= fmtbuf + sizeof(fmtbuf) - 2) - goto invalid; - q[1] = q[-1]; - q[-1] = q[0] = 'l'; - q[2] = '\0'; - dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg); -#endif } else { dbuf_printf_fun(&dbuf, fmtbuf, (int)int64_arg); } @@ -328,6 +344,10 @@ static JSValue js_printf_internal(JSContext *ctx, if (i >= argc) goto missing; /* XXX: handle strings containing null characters */ + // XXX: # could output encoded string + // XXX: null values should output as `` + // XXX: undefined values should output as `` + // XXX: `l` length modifier is implicit string_arg = JS_ToCString(ctx, argv[i++]); if (!string_arg) goto fail; @@ -351,6 +371,8 @@ static JSValue js_printf_internal(JSContext *ctx, break; case '%': + if (q != fmtbuf + 2) // accept only %% + goto invalid; dbuf_putc(&dbuf, '%'); break; diff --git a/quickjs-printf.c b/quickjs-printf.c new file mode 100644 index 0000000..6544e70 --- /dev/null +++ b/quickjs-printf.c @@ -0,0 +1,1092 @@ +/* + * QuickJS Javascript printf functions + * + * Copyright (c) 2024 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#ifdef TEST_QUICKJS +#include +#include +#include +#include +#include "cutils.h" +//#include "cutils.c" +#include "quickjs-printf.h" +typedef union JSFloat64Union { + double d; + uint64_t u64; + uint32_t u32[2]; +} JSFloat64Union; +#define JS_GET_CTX_RT(ctx) 0 +#define JS_GET_RT_RT(rt) 0 +#define JS_GET_DBUF_RT(s) 0 +//#define js_malloc_rt(rt, size) malloc(size) +//#define js_free_rt(rt, ptr) free(ptr) +#else +#define JS_GET_RT_RT(rt) (rt) +#define JS_GET_CTX_RT(ctx) ((ctx)->rt) +#define JS_GET_DBUF_RT(s) ((s)->opaque) +#endif + +/* Rounding modes: There are six possible rounding modes for values + exactly half way between 2 numbers: 1.5 2.5 -1.5 -2.5 + ROUND_HALF_UP Round away from zero: 2 3 -2 -3 + ROUND_HALF_DOWN Round towards zero: 1 2 -1 -2 + ROUND_HALF_EVEN Round to nearest even value: 2 2 -2 -2 + ROUND_HALF_ODD Round to nearest odd value: 1 3 -1 -3 + ROUND_HALF_NEXT Round toward +Infinity: 2 3 -1 -2 + ROUND_HALF_PREV Round toward -Infinity: 1 2 -2 -3 + ECMA specifies rounding as ROUND_HALF_UP. + Standard C printf specifies rounding should be performed according + to fegetround(), which defaults to FE_TONEAREST, which should + correspond to ROUND_HALF_EVEN. + This implementation only supports the first 4 modes. + */ +enum { + FLAG_ROUND_HALF_ODD = 0, + FLAG_ROUND_HALF_EVEN = 1, + FLAG_ROUND_HALF_UP = 2, + FLAG_ROUND_HALF_NEXT = 3, + FLAG_ROUND_HALF_DOWN = 4, + FLAG_ROUND_HALF_PREV = 5, + FLAG_STRIP_ZEROES = 0x10, + FLAG_FORCE_DOT = 0x20, +}; + +typedef struct JSFormatContext { + JSRuntime *rt; + void *ptr; + size_t size, pos; + int (*write)(struct JSFormatContext *fcp, const char *str, size_t len); +} JSFormatContext; + +enum { + FLAG_LEFT = 1, FLAG_HASH = 2, FLAG_ZERO = 4, + FLAG_WIDTH = 8, FLAG_PREC = 16, +}; + +/*---- floating point conversions ----*/ + +// handle 9 decimal digits at a time */ +#define COMP10 1000000000 +#define COMP10_LEN 9 +#define COMP10_MAX_SHIFT 34 // 64 - ceil(log2(1e9)) + +/* Initialize a bignum from a 64-bit unsigned int */ +static size_t comp10_init(uint32_t *num, uint64_t mant) { + size_t i = 0; + while (mant >= COMP10) { + num[i++] = mant % COMP10; + mant /= COMP10; + } + num[i++] = (uint32_t)mant; + return i; +} + +/* Shift a bignum by a bit count in 0..COMP10_MAX_SHIFT */ +static size_t comp10_shift(uint32_t *num, size_t plen, int shift) { + uint64_t carry = 0; + size_t i; + for (i = 0; i < plen; i++) { + carry += (uint64_t)num[i] << shift; + num[i] = carry % COMP10; + carry /= COMP10; + } + if (carry) { + if (carry >= COMP10) { + num[plen++] = carry % COMP10; + carry /= COMP10; + } + num[plen++] = (uint32_t)carry; + } + return plen; +} + +/* Multiply a bignum by a constant <= UINT32_MAX */ +static size_t comp10_multiply(uint32_t *num, size_t plen, uint32_t mul) { + uint64_t carry = 0; + size_t i; + for (i = 0; i < plen; i++) { + carry += num[i] * (uint64_t)mul; + num[i] = carry % COMP10; + carry /= COMP10; + } + if (carry) { + if (carry >= COMP10) { + num[plen++] = carry % COMP10; + carry /= COMP10; + } + num[plen++] = (uint32_t)carry; + } + return plen; +} + +/* Compute the number of decimal digits in a normalized non-zero COMP10 unit */ +static int digits_count(uint32_t val) { + /* this code hopefully branchless */ + return (1 + (val > 9) + + (val > 99) + + (val > 999) + + (val > 9999) + + (val > 99999) + + (val > 999999) + + (val > 9999999) +#if COMP10_LEN > 8 + + (val > 99999999) +#endif + ); +} + +/* Powers of 5 less than UINT32_MAX */ +static uint32_t const pow5_table[14] = { + 1UL, + 5UL, + 5UL*5UL, + 5UL*5UL*5UL, + 5UL*5UL*5UL*5UL, + 5UL*5UL*5UL*5UL*5UL, + 5UL*5UL*5UL*5UL*5UL*5UL, + 5UL*5UL*5UL*5UL*5UL*5UL*5UL, + 5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL, + 5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL, + 5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL, + 5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL, + 5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL, + 5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL*5UL, +}; + +/* Powers of 10 less than UINT32_MAX */ +static uint32_t const pow10_table[10] = { + 1UL, + 10UL, + 100UL, + 1000UL, + 10000UL, + 100000UL, + 1000000UL, + 10000000UL, + 100000000UL, + 1000000000UL, +}; + +/* Increment a bignum by a value `inc` times COMP10 power `from`, + assuming from <= plen */ +static size_t comp10_inc(uint32_t *num, size_t plen, uint32_t inc, size_t from) { + uint32_t carry = inc; + size_t i; + for (i = from; i < plen; i++) { + if ((num[i] += carry) < COMP10) + return plen; + num[i] -= COMP10; + carry = 1; + } + if (carry) { + num[plen++] = carry; + } + return plen; +} + +// minimum buffer length is 1077 bytes for printf("%.1074f", 5e-324) +// including the null terminator +static int js_format_f(double value, char dest[minimum_length(2+1074+1)], + int prec, int use_exp, + int fflags, size_t *trailing_zeroes, int *pexp) +{ + uint32_t digits[(1022 + 52 + COMP10_LEN - 1 - 297) / COMP10_LEN]; + JSFloat64Union u; + uint64_t mant; + char *p1, *p = dest; + size_t plen; + int exp2, i, j, numd, maxd, spt; + unsigned int val; + + /* assuming IEEE 64-bit format */ + u.d = value; + exp2 = (int)((u.u64 >> 52U) & 0x07FFU) - 1023; + mant = u.u64 & ((1ULL << 52U) - 1); + // uncomment to support FLAG_ROUND_HALF_NEXT and FLAG_ROUND_HALF_PREV + // fflags += ((fflags & 2) - (fflags & 4)) & -((int)(u.u64 >> 63) & fflags & 1); + if (exp2 == -1023) { + exp2++; + if (mant == 0) + goto has_zero; + } else { + mant |= (1ULL << 52U); + } + j = ctz64(mant); + mant >>= j; + exp2 -= 52 - j; + if (exp2 >= 0) { + /* integer value: compute mant * 2**exp2 */ +#if 1 + j += 59 - 52; + if (j >= exp2) + j = exp2; + mant <<= j; + exp2 -= j; +#else + if (exp2 + 52 - j < 64) { + /* if value fits in a uint64_t, shift it */ + mant <<= exp2; + exp2 = 0; + } +#endif + plen = comp10_init(digits, mant); + while (exp2 > 0) { + int k = exp2 >= COMP10_MAX_SHIFT ? COMP10_MAX_SHIFT : exp2; + plen = comp10_shift(digits, plen, k); + exp2 -= k; + } + } else { + /* fractional part: compute power of 2 and multiply by mant */ + /* this is faster than using 32-bit digits and multiply by 10**9 */ + /* -exp2 is the number of bits after the decimal point. */ + /* multiply mant by 5 to the power of -exp2 */ + int exp; + plen = comp10_init(digits, mant); + for (exp = -exp2; exp > 0;) { + int k = exp >= 13 ? 13 : exp; + plen = comp10_multiply(digits, plen, pow5_table[k]); + exp -= k; + } + } + /* round converted number according to prec: + if !use_exp, the number is an integer, no rounding necessary + if use_exp, add 5*pow(10, numd-1-prec) unless numd==1+prec and + digit 1 has even parity. + */ + val = digits[--plen]; // leading digits: 1..COMP10_LEN + j = digits_count(val); + numd = j + plen * COMP10_LEN; // number of significant digits + i = numd + exp2; // number of digits before the . + + if (use_exp) { + maxd = prec + 1; + } else { + maxd = prec + i; + /* if maxd < 0, the result is 0 */ + if (maxd < 0) { + has_zero: + *dest = '0'; + *pexp = 0; + i = 1; + if (fflags & FLAG_STRIP_ZEROES) + prec = 0; + *trailing_zeroes = prec; + if (prec || (fflags & FLAG_FORCE_DOT)) + dest[i++] = '.'; + return i; + } + } + if (maxd < numd) { + /* round converted number to maxd digits */ + unsigned int trunc = numd - maxd; + /* add 0.5 * 10**trunc unless rounding to even */ + // initialize trail to non zero if FLAG_ROUND_HALF_UP + uint32_t inc, half, low, trail = fflags & FLAG_ROUND_HALF_UP; + size_t start = 0; + while (trunc > COMP10_LEN) { + trail |= digits[start++]; + trunc -= COMP10_LEN; + } + inc = pow10_table[trunc]; // trunc is in range 1..COMP10_LEN + half = inc / 2; + low = digits[start] % inc; + // round to nearest, tie to even + if (low > half || + (low == half && !(fflags & FLAG_ROUND_HALF_DOWN) && + (trail || + (trunc == COMP10_LEN ? + digits[start + 1] % 2 == (fflags & FLAG_ROUND_HALF_EVEN) : + digits[start] / inc % 2 == (fflags & FLAG_ROUND_HALF_EVEN))))) + { + /* add inc to the number */ + plen = comp10_inc(digits, plen + 1, inc, start) - 1; + if (val != digits[plen]) { + val = digits[plen]; + j = digits_count(val); + numd = j + plen * COMP10_LEN; + i = numd + exp2; + if (use_exp) { + maxd = prec + 1; + } else { + maxd = prec + i; + } + } + } + } else { + maxd = numd; + } + if (use_exp) { + spt = 1; + *pexp = i - 1; + } else { + while (i <= 0) { + *p++ = '0'; + i++; + } + spt = i; + *pexp = 0; + } + /* only output maxd digits? */ + p1 = p += j; + while (val > 9) { + *--p1 = '0' + val % 10; + val /= 10; + } + *--p1 = (char)('0' + val); + maxd -= j; + for (i = plen; maxd > 0 && i --> 0; maxd -= COMP10_LEN) { + val = digits[i]; + p1 = p += COMP10_LEN; + for (j = 0; j < COMP10_LEN - 1; j++) { + *--p1 = '0' + val % 10; + val /= 10; + } + *--p1 = (char)('0' + val); + } + p += maxd; + i = p - dest; + + /* strip trailing zeroes after the decimal point */ + while (i > spt && dest[i - 1] == '0') + i--; + if (fflags & FLAG_STRIP_ZEROES) + prec = i - spt; + *trailing_zeroes = spt + prec - i; + /* insert decimal point */ + if (prec || (fflags & FLAG_FORCE_DOT)) { + for (j = ++i; j --> spt;) + dest[j] = dest[j - 1]; + dest[spt] = '.'; + } + return i; +} + +static int js_format_a(double d, char dest[minimum_length(2+13+6+1)], + int prec, const char *digits, + int fflags, size_t *trailing_zeroes, int *pexp) +{ + JSFloat64Union u; + int shift, exp2, ndig, zeroes, tzcount; + uint64_t mant, half; + char *p = dest; + + u.d = d; + + /* extract mantissa and binary exponent */ + mant = u.u64 & ((1ULL << 52) - 1); + exp2 = (u.u64 >> 52) & 0x07FF; + // uncomment to support FLAG_ROUND_HALF_NEXT and FLAG_ROUND_HALF_PREV + // fflags += ((fflags & 2) - (fflags & 4)) & -((int)(u.u64 >> 63) & fflags & 1); + if (exp2 == 0) { + /* subnormal */ + ndig = 0; + tzcount = 0; + if (mant == 0) + goto next; + shift = clz64(mant) - 11; + mant <<= shift; + exp2 = 1 - shift; + } + mant |= 1ULL << 52; + exp2 -= 1023; + tzcount = ctz64(mant); + ndig = 13 - (tzcount >> 2); + next: + *pexp = exp2; + zeroes = 0; + if (prec >= 0) { + if (prec >= ndig) { + zeroes = prec - ndig; + } else { + // round to nearest according to flags + shift = 52 - prec * 4 - 1; + ndig = prec; + half = (1ULL << shift) - + !(tzcount == shift && !(fflags & FLAG_ROUND_HALF_DOWN) && + ((fflags & FLAG_ROUND_HALF_UP) || + (mant >> (shift + 1)) % 2 == (fflags & FLAG_ROUND_HALF_EVEN))); + mant += half; + } + } + *trailing_zeroes = zeroes; + *p++ = (char)('0' + (int)(mant >> 52)); + if ((fflags & FLAG_FORCE_DOT) | zeroes | ndig) { + *p++ = '.'; + for (shift = 52 - 4; ndig --> 0; shift -= 4) + *p++ = digits[(size_t)(mant >> shift) & 15]; + } + return p - dest; +} + +// construct the exponent. dest minimum length is 7 bytes +// (including space for the null terminator, which is not set here) +static size_t js_format_exp(char *dest, int exp, char pref, int min_digits) { + size_t i, len; + dest[0] = pref; + dest[1] = '+'; + if (exp < 0) { + dest[1] = '-'; + exp = -exp; + } + len = 3 + (exp >= 1000) + (exp >= 100) + (exp >= 10 || min_digits > 1); + for (i = len; i --> 3;) { + dest[i] = (char)('0' + exp % 10); + exp /= 10; + } + dest[i] = (char)('0' + exp); + return len; +} + +#if 1 +static size_t js_format_spaces(JSFormatContext *fcp, size_t count) +{ + static char const buf[16] = " "; + size_t len = count; + + while (count > 0) { + size_t chunk = count < sizeof(buf) ? count : sizeof(buf); + fcp->write(fcp, buf, chunk); + count -= chunk; + } + return len; +} + +static size_t js_format_zeroes(JSFormatContext *fcp, size_t count) +{ + static char const buf[16] = "0000000000000000"; + size_t len = count; + + while (count > 0) { + size_t chunk = count < sizeof(buf) ? count : sizeof(buf); + fcp->write(fcp, buf, chunk); + count -= chunk; + } + return len; +} +#else +static size_t js_format_run(JSFormatContext *fcp, char c, size_t count) +{ + char buf[128]; + size_t len = 0; + + while (count > 0) { + size_t chunk = count < sizeof(buf) ? count : sizeof(buf); + memset(buf, c, chunk); + fcp->write(fcp, buf, chunk); + len += chunk; + count -= chunk; + } + return len; +} +#define js_format_spaces(fcp, count) js_format_run(fcp, ' ', count) +#define js_format_zeroes(fcp, count) js_format_run(fcp, '0', count) +#endif + +static int js_format_str(JSFormatContext *fcp, int flags, int width, int prec, const char *str) +{ + size_t slen, pad, pos; + + if (flags & FLAG_PREC) { + // emulate slen = strnlen(str, prec); + for (slen = 0; slen < (size_t)prec && str[slen]; slen++) + continue; + } else { + slen = strlen(str); + } + + pos = pad = 0; + if (slen < (size_t)width) { + pad = width - slen; + if (!(flags & FLAG_LEFT)) { + /* left pad with spaces */ + pos += js_format_spaces(fcp, pad); + pad = 0; + } + } + pos += fcp->write(fcp, str, slen); + if (pad) + pos += js_format_spaces(fcp, pad); + return pos; +} + +static int js_format_wstr(JSFormatContext *fcp, int flags, int width, int prec, + char *dest, size_t size, const wchar_t *wstr) +{ + size_t i, j, k, pos = 0; + uint32_t c; + + if (!(flags & FLAG_PREC)) + prec = INT_MAX; + pos = 0; + if (width > 0) { + // compute the converted string length + for (i = j = 0; (c = wstr[i++]) != 0;) { + if (sizeof(wchar_t) == 2 && is_hi_surrogate(c) && is_lo_surrogate(wstr[i])) + c = from_surrogate(c, wstr[i++]); + j += utf8_encode((uint8_t *)dest, c); + } + if (j < (size_t)width && !(flags & FLAG_LEFT)) { + /* left pad with spaces */ + pos += js_format_spaces(fcp, width - j); + } + } + + for (i = j = 0; (c = wstr[i++]) != 0;) { + if (sizeof(wchar_t) == 2 && is_hi_surrogate(c) && is_lo_surrogate(wstr[i])) + c = from_surrogate(c, wstr[i++]); + if (j + UTF8_CHAR_LEN_MAX > size) { + pos += fcp->write(fcp, dest, j); + j = 0; + } + k = utf8_encode((uint8_t *)dest + j, c); + if ((size_t)prec < k) + break; + prec -= k; + j += k; + } + pos += fcp->write(fcp, dest, j); + if (pos < (size_t)width) + pos += js_format_spaces(fcp, width - pos); + return pos; +} + +static int js_format(JSFormatContext *fcp, const char *fmt, va_list ap) +{ + char buf[1080]; // used for integer and floating point conversions + char prefix[4]; // sign and/or 0x, 0X, 0b or 0B prefixes + char suffix[8]; // floating point exponent, range 'p-1074 to p+1023' + size_t pos = 0, i, slen, prefix_len, suffix_len, leading_zeroes, trailing_zeroes, prec, pad; + const char *str; + const char *digits; + char cc, lc; + int width, flags, length, wc, shift; + unsigned ww; + uint64_t uval, signbit, mask; + double val; + int exp, fprec, fflags; + + str = fmt; + for (;;) { + cc = *fmt++; + if (cc != '%' && cc != '\0') + continue; + slen = fmt - str - 1; + if (slen) + pos += fcp->write(fcp, str, slen); + if (cc == '\0') + break; + /* quick dispatch for special and common formats */ + switch (*fmt) { + case '%': + str = fmt++; + continue; + case 'd': + str = buf; + slen = i32toa(buf, va_arg(ap, int)); + goto hasstr; + case 's': + str = va_arg(ap, const char *); + if (!str) + str = "(null)"; + slen = strlen(str); + hasstr: + pos += fcp->write(fcp, str, slen); + str = ++fmt; + continue; + } + prefix[0] = '\0'; + flags = 0; + length = sizeof(unsigned int) * CHAR_BIT; + str = fmt - 1; + prefix_len = leading_zeroes = slen = trailing_zeroes = suffix_len = prec = width = 0; + moreflags: + switch (cc = *fmt) { + case ' ': + case '+': fmt++; prefix[0] |= cc; goto moreflags; /* assuming ASCII */ + case '-': fmt++; flags |= FLAG_LEFT; goto moreflags; + case '#': fmt++; flags |= FLAG_HASH; goto moreflags; + case '0': fmt++; flags |= FLAG_ZERO; goto moreflags; + case '*': + //flags |= FLAG_WIDTH; + fmt++; + wc = va_arg(ap, int); + if (wc < 0) { + flags |= FLAG_LEFT; + if (wc != INT_MIN) + width = -wc; + } else { + width = wc; + } + if (*fmt == '.') + goto hasprec; + break; + case '1': case '2': case '3': + case '4': case '5': case '6': + case '7': case '8': case '9': + ww = *fmt++ - '0'; + //flags |= FLAG_WIDTH; + while (*fmt >= '0' && *fmt <= '9') { + unsigned digit = *fmt++ - '0'; + if (ww >= UINT_MAX / 10 && + (ww > UINT_MAX / 10 || digit > UINT_MAX % 10)) + ww = UINT_MAX; + else + ww = ww * 10 + digit; + } + if (ww <= INT_MAX) + width = ww; + if (*fmt == '.') + goto hasprec; + break; + case '.': + hasprec: + fmt++; + if (*fmt == '*') { + fmt++; + wc = va_arg(ap, int); + if (wc >= 0) { + flags |= FLAG_PREC; + prec = wc; + } + } else { + ww = 0; + while (*fmt >= '0' && *fmt <= '9') { + unsigned digit = *fmt++ - '0'; + if (ww >= UINT_MAX / 10 && + (ww > UINT_MAX / 10 || digit > UINT_MAX % 10)) + ww = UINT_MAX; + else + ww = ww * 10 + digit; + } + if (ww <= INT_MAX) { + flags |= FLAG_PREC; + prec = ww; + } + } + break; + } + switch (lc = *fmt++) { + case 'j': + length = sizeof(intmax_t) * CHAR_BIT; + break; + case 'z': + length = sizeof(size_t) * CHAR_BIT; + break; + case 't': + length = sizeof(ptrdiff_t) * CHAR_BIT; + break; + case 'l': + length = sizeof(unsigned long) * CHAR_BIT; + if (*fmt == 'l') { + fmt++; + length = sizeof(unsigned long long) * CHAR_BIT; + } + break; + case 'h': + length = sizeof(unsigned short) * CHAR_BIT; + if (*fmt == 'h') { + fmt++; + length = sizeof(unsigned char) * CHAR_BIT; + } + break; + case 'w': + if (!(*fmt >= '1' && *fmt <= '9')) + goto error; + length = *fmt++ - '0'; + if (*fmt >= '0' && *fmt <= '9') + length = length * 10 + *fmt++ - '0'; + if (length > 64) + goto error; + break; + default: + fmt--; + break; + } + digits = digits36; + switch (cc = *fmt++) { + case 's': + if (lc == 'l') { + const wchar_t *wstr = va_arg(ap, const wchar_t *); + if (!wstr) + wstr = L"(null)"; + pos += js_format_wstr(fcp, flags, width, prec, + buf, sizeof buf, wstr); + } else { + str = va_arg(ap, const char *); + if (!str) + str = "(null)"; + pos += js_format_str(fcp, flags, width, prec, str); + } + str = fmt; + continue; + case 'c': + flags &= ~FLAG_ZERO; + wc = va_arg(ap, int); + if (lc == 'l') { + slen = 0; + if (wc) + slen = utf8_encode((uint8_t *)buf, wc); + } else { + *buf = (char)wc; + slen = 1; + } + break; + case 'p': +#ifndef TEST_QUICKJS + if (*fmt == 's' && fcp->rt) { + // %ps -> JSString converted to quoted string + // TODO(chqrlie) allocate buffer if conversion does not fit + JSString *pstr = va_arg(ap, void *); + JS_FormatString(fcp->rt, buf, sizeof(buf), pstr, '"'); + pos += js_format_str(fcp, flags, width, prec, buf); + str = ++fmt; + continue; + } +#endif + shift = 4; + prefix[0] = '0'; + prefix[1] = cc = 'x'; + prefix_len = 2; + length = sizeof(void *) * CHAR_BIT; + goto radix_number; + case 'B': + digits = digits36_upper; + /* fall thru */ + case 'b': + shift = 1; + goto radix_number; + case 'X': + digits = digits36_upper; + /* fall thru */ + case 'x': + shift = 4; + goto radix_number; + case 'o': +#ifndef TEST_QUICKJS + if (*fmt == 'a' && length == sizeof(JSAtom) * CHAR_BIT && fcp->rt) { + // %oa -> JSAtom converted to utf-8 string + // %#oa -> JSAtom converted to identifier, number or quoted string + // TODO(chqrlie) allocate buffer if conversion does not fit + JSAtom atom = va_arg(ap, unsigned); + JS_FormatAtom(fcp->rt, buf, sizeof(buf), atom, flags & FLAG_HASH); + pos += js_format_str(fcp, flags, width, prec, buf); + str = ++fmt; + continue; + } +#endif + shift = 3; + radix_number: + uval = (length <= 32) ? va_arg(ap, uint32_t) : va_arg(ap, uint64_t); + signbit = (uint64_t)1 << (length - 1); + /* mask off extra bits, keep value bits */ + uval &= (signbit << 1) - 1; + slen = uval ? (64 - clz64(uval) + shift - 1) / shift : 1; + mask = (1ULL << shift) - 1; + for (wc = slen * shift, i = 0; wc > 0; i++) { + wc -= shift; + buf[i] = digits[(uval >> wc) & mask]; + } + if (flags & FLAG_PREC) { + if (prec == 0 && uval == 0) + slen = 0; + if (slen < prec) + leading_zeroes = prec - slen; + flags &= ~FLAG_ZERO; + } + if (flags & FLAG_HASH) { + if (shift == 3) { + /* at least one leading `0` */ + if (leading_zeroes == 0 && (uval != 0 || slen == 0)) + leading_zeroes = 1; + } else { + if (uval) { + /* output a 0x/0X or 0b/0B prefix */ + prefix[0] = '0'; + prefix[1] = cc; + prefix_len = 2; + } + } + } + break; + case 'u': + case 'd': + case 'i': + uval = (length <= 32) ? va_arg(ap, uint32_t) : va_arg(ap, uint64_t); + signbit = 1ULL << (length - 1); + if (cc != 'u') { + if (uval & signbit) { + prefix[0] = '-'; + uval = -uval; + } + prefix_len = (prefix[0] != '\0'); + } + /* mask off extra bits, keep value bits */ + uval &= (signbit << 1) - 1; + slen = u64toa(buf, uval); + if (flags & FLAG_PREC) { + if (prec == 0 && uval == 0) + slen = 0; + if (slen < prec) + leading_zeroes = prec - slen; + flags &= ~FLAG_ZERO; + } + break; + case 'A': + case 'E': + case 'F': + case 'G': + cc += 'a' - 'A'; + digits = digits36_upper; + /* fall through */ + case 'a': + case 'e': + case 'f': + case 'g': + val = va_arg(ap, double); + fflags = FLAG_ROUND_HALF_EVEN; + fprec = -1; + if (flags & FLAG_PREC) + fprec = prec; +#define LETTER(c) digits[(c) - 'a' + 10] + + if (signbit(val)) { + prefix[0] = '-'; + //val = -val; + } + prefix_len = (prefix[0] != '\0'); + if (!isfinite(val)) { + flags &= ~FLAG_ZERO; + slen = 3; + buf[0] = LETTER('i'); + buf[1] = LETTER('n'); + buf[2] = LETTER('f'); + if (isnan(val)) { + prefix_len = 0; + buf[0] = LETTER('n'); + buf[1] = LETTER('a'); + buf[2] = LETTER('n'); + } + break; + } + if (flags & FLAG_HASH) + fflags |= FLAG_FORCE_DOT; + if (cc == 'a') { + prefix[prefix_len++] = '0'; + prefix[prefix_len++] = LETTER('x'); + slen = js_format_a(val, buf, fprec, digits, fflags, &trailing_zeroes, &exp); + suffix_len = js_format_exp(suffix, exp, LETTER('p'), 1); + break; + } + if (fprec < 0) + fprec = 6; + if (cc == 'g') { + fprec -= (fprec != 0); + if (!(flags & FLAG_HASH)) + fflags |= FLAG_STRIP_ZEROES; + if (val != 0) { + exp = (int)+floor(log10(fabs(val))); + if (fprec <= exp || exp < -4) { + /* convert with exponent, then re-test border cases */ + // TODO(chqrlie): avoid computing digits twice + slen = js_format_f(val, buf, fprec, TRUE, fflags, + &trailing_zeroes, &exp); + if (fprec < exp || exp < -4) { + suffix_len = js_format_exp(suffix, exp, LETTER('e'), 2); + break; + } + } + fprec -= exp; + } + } + slen = js_format_f(val, buf, fprec, cc == 'e', fflags, + &trailing_zeroes, &exp); + if (cc == 'e') { + suffix_len = js_format_exp(suffix, exp, LETTER('e'), 2); + } + break; + case '\0': + fmt--; + continue; + error: + default: + /* invalid format: stop conversions and print format string */ + fmt += strlen(fmt); + continue; + } + pad = 0; + // XXX: potential overflow + wc = prefix_len + leading_zeroes + slen + trailing_zeroes + suffix_len; + if (width > wc) { + pad = width - wc; + if (!(flags & FLAG_LEFT)) { + if (flags & FLAG_ZERO) { + /* left pad with zeroes between prefix and string */ + leading_zeroes += pad; + pad = 0; + } else { + /* left pad with spaces */ + pos += js_format_spaces(fcp, pad); + pad = 0; + } + } + } + /* output prefix: sign and/or 0x/0b */ + if (prefix_len) + pos += fcp->write(fcp, prefix, prefix_len); + if (leading_zeroes) + pos += js_format_zeroes(fcp, leading_zeroes); + /* output string fragment */ + pos += fcp->write(fcp, buf, slen); + if (trailing_zeroes) + pos += js_format_zeroes(fcp, trailing_zeroes); + /* output suffix: exponent */ + if (suffix_len) + pos += fcp->write(fcp, suffix, suffix_len); + /* right pad with spaces */ + if (pad) + pos += js_format_spaces(fcp, pad); + str = fmt; + } + return (int)pos; +} + +static int js_snprintf_write(JSFormatContext *fcp, const char *str, size_t len) +{ + size_t i, pos = fcp->pos; + char *dest = fcp->ptr; + for (i = 0; i < len; i++) { + if (pos < fcp->size) + dest[pos] = str[i]; + pos++; + } + fcp->pos = pos; + return len; +} + +int js_snprintf(JSContext *ctx, char *dest, size_t size, const char *fmt, ...) +{ + JSFormatContext fc = { JS_GET_CTX_RT(ctx), dest, size, 0, js_snprintf_write }; + va_list ap; + int len; + + va_start(ap, fmt); + len = js_format(&fc, fmt, ap); + va_end(ap); + if (fc.pos < fc.size) + dest[fc.pos] = '\0'; + else if (fc.size > 0) + dest[fc.size - 1] = '\0'; + return len; +} + +int js_vsnprintf(JSContext *ctx, char *dest, size_t size, const char *fmt, va_list ap) +{ + JSFormatContext fc = { JS_GET_CTX_RT(ctx), dest, size, 0, js_snprintf_write }; + int len; + + len = js_format(&fc, fmt, ap); + if (fc.pos < fc.size) + dest[fc.pos] = '\0'; + else if (fc.size > 0) + dest[fc.size - 1] = '\0'; + return len; +} + +static int dbuf_printf_write(JSFormatContext *fcp, const char *str, size_t len) +{ + return dbuf_put(fcp->ptr, str, len); +} + +int dbuf_vprintf_ext(DynBuf *s, const char *fmt, va_list ap) +{ + JSFormatContext fc = { JS_GET_DBUF_RT(s), s, 0, 0, dbuf_printf_write }; + return js_format(&fc, fmt, ap); +} + +static int js_fprintf_write(JSFormatContext *fcp, const char *str, size_t len) +{ + return fwrite(str, 1, len, fcp->ptr); +} + +int js_printf(JSContext *ctx, const char *fmt, ...) +{ + JSFormatContext fc = { JS_GET_CTX_RT(ctx), stdout, 0, 0, js_fprintf_write }; + va_list ap; + int len; + + va_start(ap, fmt); + len = js_format(&fc, fmt, ap); + va_end(ap); + return len; +} + +int js_vprintf(JSContext *ctx, const char *fmt, va_list ap) +{ + JSFormatContext fc = { JS_GET_CTX_RT(ctx), stdout, 0, 0, js_fprintf_write }; + return js_format(&fc, fmt, ap); +} + +int js_printf_RT(JSRuntime *rt, const char *fmt, ...) +{ + JSFormatContext fc = { JS_GET_RT_RT(rt), stdout, 0, 0, js_fprintf_write }; + va_list ap; + int len; + + va_start(ap, fmt); + len = js_format(&fc, fmt, ap); + va_end(ap); + return len; +} + +int js_vprintf_RT(JSRuntime *rt, const char *fmt, va_list ap) +{ + JSFormatContext fc = { JS_GET_RT_RT(rt), stdout, 0, 0, js_fprintf_write }; + return js_format(&fc, fmt, ap); +} + +int js_fprintf_RT(JSRuntime *rt, FILE *fp, const char *fmt, ...) +{ + JSFormatContext fc = { JS_GET_RT_RT(rt), fp, 0, 0, js_fprintf_write }; + va_list ap; + int len; + + va_start(ap, fmt); + len = js_format(&fc, fmt, ap); + va_end(ap); + return len; +} + +int js_vfprintf_RT(JSRuntime *rt, FILE *fp, const char *fmt, va_list ap) +{ + JSFormatContext fc = { JS_GET_RT_RT(rt), fp, 0, 0, js_fprintf_write }; + return js_format(&fc, fmt, ap); +} + +#undef JS_GET_CTX_RT +#undef JS_GET_RT_RT +#undef JS_GET_DBUF_RT +#undef COMP10 +#undef COMP10_LEN +#undef COMP10_MAX_SHIFT diff --git a/quickjs-printf.h b/quickjs-printf.h new file mode 100644 index 0000000..3b0eda9 --- /dev/null +++ b/quickjs-printf.h @@ -0,0 +1,63 @@ +/* + * QuickJS Javascript printf functions + * + * Copyright (c) 2024 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef QUICKJS_PRINTF +#define QUICKJS_PRINTF + +#include +#include + +#ifdef TEST_QUICKJS +#define JS_EXTERN +#define __js_printf_like(f, a) __attribute__((format(printf, f-1, a-1))) +#define DynBuf void +#define JSContext void +#define JSRuntime void +#define js_snprintf(ctx, ...) qjs_snprintf(__VA_ARGS__) +#define js_printf(ctx, ...) qjs_printf(__VA_ARGS__) +#define js_printf_RT(rt, ...) qjs_printf_RT(__VA_ARGS__) +#define js_fprintf(ctx, ...) qjs_fprintf(__VA_ARGS__) +#define js_fprintf_RT(rt, ...) qjs_fprintf_RT(__VA_ARGS__) +#else +#define __js_printf_like(f, a) __attribute__((format(printf, f, a))) +#endif + +__js_printf_like(4, 5) +int js_snprintf(JSContext *ctx, char *dest, size_t size, const char *fmt, ...); +int js_vsnprintf(JSContext *ctx, char *dest, size_t size, const char *fmt, va_list ap); +int dbuf_vprintf_ext(DynBuf *s, const char *fmt, va_list ap); +__js_printf_like(2, 3) +int js_printf(JSContext *ctx, const char *fmt, ...); +int js_vprintf(JSContext *ctx, const char *fmt, va_list ap); +__js_printf_like(2, 3) +int js_printf_RT(JSRuntime *rt, const char *fmt, ...); +int js_vprintf_RT(JSRuntime *rt, const char *fmt, va_list ap); +__js_printf_like(3, 4) +int js_fprintf(JSContext *ctx, FILE *fp, const char *fmt, ...); +int js_vfprintf(JSContext *ctx, FILE *fp, const char *fmt, va_list ap); +__js_printf_like(3, 4) +int js_fprintf_RT(JSRuntime *rt, FILE *fp, const char *fmt, ...); +int js_vfprintf_RT(JSRuntime *rt, FILE *fp, const char *fmt, va_list ap); + +#endif // QUICKJS_PRINTF diff --git a/quickjs.c b/quickjs.c index 085037e..b361722 100644 --- a/quickjs.c +++ b/quickjs.c @@ -45,6 +45,7 @@ #include "quickjs.h" #include "libregexp.h" #include "libbf.h" +#include "quickjs-printf.h" #if defined(EMSCRIPTEN) || defined(_MSC_VER) #define DIRECT_DISPATCH 0 @@ -1026,7 +1027,6 @@ static JSValue JS_EvalObject(JSContext *ctx, JSValue this_obj, JSValue val, int flags, int scope_idx); JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...); -static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p); static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt); static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p); static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p); @@ -1603,6 +1603,9 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) JSRuntime *rt; JSMallocState ms; + // add support for extended conversions in dbuf_printf + dbuf_vprintf_fun = dbuf_vprintf_ext; + memset(&ms, 0, sizeof(ms)); ms.opaque = opaque; ms.malloc_limit = 0; @@ -2000,22 +2003,16 @@ void JS_FreeRuntime(JSRuntime *rt) } switch (p->atom_type) { case JS_ATOM_TYPE_STRING: - JS_DumpString(rt, p); + js_printf_RT(rt, "%ps", p); break; case JS_ATOM_TYPE_GLOBAL_SYMBOL: - printf("Symbol.for("); - JS_DumpString(rt, p); - printf(")"); + js_printf_RT(rt, "Symbol.for(%ps)", p); break; case JS_ATOM_TYPE_SYMBOL: if (p->hash == JS_ATOM_HASH_SYMBOL) { - printf("Symbol("); - JS_DumpString(rt, p); - printf(")"); + js_printf_RT(rt, "Symbol(%ps)", p); } else { - printf("Private("); - JS_DumpString(rt, p); - printf(")"); + js_printf_RT(rt, "Private(%ps)", p); } break; } @@ -2061,7 +2058,7 @@ void JS_FreeRuntime(JSRuntime *rt) } else { printf(" %6u ", str->header.ref_count); } - JS_DumpString(rt, str); + js_printf_RT(rt, "%ps", str); if (rt->rt_info) { printf(":%u", str->header.ref_count); } else { @@ -2461,36 +2458,69 @@ static uint32_t hash_string(const JSString *str, uint32_t h) return h; } -static __maybe_unused void JS_DumpString(JSRuntime *rt, - const JSString *p) +// TODO(chqrlie) take precision argument to limit number of code points? +static size_t JS_FormatString(JSRuntime *rt, char dest[minimum_length(64)], + size_t size, const JSString *p, uint32_t sep) { - int i, c, sep; + char buf[16]; + uint32_t c; + size_t i, j, k, pos = 0; if (p == NULL) { - printf(""); - return; + memcpy(dest, "", sizeof("")); + return sizeof("") - 1; } - if (p->header.ref_count != 1) - printf("%d", p->header.ref_count); + k = 0; if (p->is_wide_char) - putchar('L'); + buf[k++] = 'L'; sep = '\"'; - putchar(sep); - for(i = 0; i < p->len; i++) { + buf[k++] = sep; + for(i = 0;; i++) { + for(j = 0; j < k; j++) { + if (pos < size) + dest[pos] = buf[j]; + pos++; + } + if (i >= p->len) + break; + k = 0; c = string_get(p, i); if (c == sep || c == '\\') { - putchar('\\'); - putchar(c); + buf[k++] = '\\'; + buf[k++] = c; } else if (c >= ' ' && c <= 126) { - putchar(c); + buf[k++] = c; } else if (c == '\n') { - putchar('\\'); - putchar('n'); + buf[k++] = '\\'; + buf[k++] = 'n'; + } else if (c < 256) { + buf[k++] = '\\'; + buf[k++] = 'x'; + buf[k++] = digits36[c >> 4]; + buf[k++] = digits36[c & 15]; } else { - printf("\\u%04x", c); + buf[k++] = '\\'; + buf[k++] = 'u'; + buf[k++] = digits36[(c >> 12) & 15]; + buf[k++] = digits36[(c >> 8) & 15]; + buf[k++] = digits36[(c >> 4) & 15]; + buf[k++] = digits36[c & 15]; } } - putchar(sep); + if (pos < size) + dest[pos] = sep; + pos++; + + if (pos < size) { + dest[pos] = '\0'; + } else { + dest[size - 5] = '.'; + dest[size - 4] = '.'; + dest[size - 3] = '.'; + dest[size - 2] = sep; + dest[size - 1] = '\0'; + } + return (int)pos; } static __maybe_unused void JS_DumpAtoms(JSRuntime *rt) @@ -2508,7 +2538,9 @@ static __maybe_unused void JS_DumpAtoms(JSRuntime *rt) while (h) { p = rt->atom_array[h]; printf(" "); - JS_DumpString(rt, p); + if (p->header.ref_count != 1) + printf("%d", p->header.ref_count); + js_printf_RT(rt, "%ps", p); h = p->hash_next; } printf("\n"); @@ -2520,8 +2552,10 @@ static __maybe_unused void JS_DumpAtoms(JSRuntime *rt) p = rt->atom_array[i]; if (!atom_is_free(p)) { printf(" %d: { %d %08x ", i, p->atom_type, p->hash); + if (p->header.ref_count != 1) + printf("%d", p->header.ref_count); if (!(p->len == 0 && p->is_wide_char != 0)) - JS_DumpString(rt, p); + js_printf_RT(rt, "%ps", p); printf(" %d }\n", p->hash_next); } } @@ -3011,55 +3045,76 @@ JSValue JS_NewSymbol(JSContext *ctx, const char *description, JS_BOOL is_global) return JS_NewSymbolFromAtom(ctx, atom, is_global ? JS_ATOM_TYPE_GLOBAL_SYMBOL : JS_ATOM_TYPE_SYMBOL); } -#define ATOM_GET_STR_BUF_SIZE 64 - -/* Should only be used for debug. */ -static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size, - JSAtom atom) +/* Used for %#oa and %#oa js_printf conversions */ +static size_t JS_FormatAtom(JSRuntime *rt, char dest[minimum_length(64)], + int size, JSAtom atom, int hash) { - if (__JS_AtomIsTaggedInt(atom)) { - snprintf(buf, buf_size, "%u", __JS_AtomToUInt32(atom)); - } else if (atom == JS_ATOM_NULL) { - snprintf(buf, buf_size, ""); - } else if (atom >= rt->atom_size) { + JSAtomStruct *p; + uint32_t i, len; + JSString *str; + + /* Generate a UTF-8 encoded string for the atom. + Tagged int atoms are converted to the corresponding number. + `hash` argument generates a quoted string if the atom + is not an integer or an identifier + */ + if (__JS_AtomIsTaggedInt(atom)) + return u32toa(dest, __JS_AtomToUInt32(atom)); + if (atom == JS_ATOM_NULL) { + memcpy(dest, "", sizeof("")); + return sizeof("") - 1; + } + if (atom >= rt->atom_size) { assert(atom < rt->atom_size); - snprintf(buf, buf_size, "", atom); - } else { - JSAtomStruct *p = rt->atom_array[atom]; - *buf = '\0'; - if (atom_is_free(p)) { - assert(!atom_is_free(p)); - snprintf(buf, buf_size, "", atom); - } else if (p != NULL) { - JSString *str = p; - if (str->is_wide_char) { - /* encode surrogates correctly */ - utf8_encode_buf16(buf, buf_size, str->u.str16, str->len); - } else { - /* special case ASCII strings */ - int i, c = 0; - for(i = 0; i < str->len; i++) { - c |= str->u.str8[i]; - } - if (c < 0x80) - return (const char *)str->u.str8; - utf8_encode_buf8(buf, buf_size, str->u.str8, str->len); + // XXX: should avoid recursive snprintf call + return snprintf(dest, size, "", atom); + } + p = rt->atom_array[atom]; + if (p == NULL || atom_is_free(p)) { + assert(!atom_is_free(p)); + return snprintf(dest, size, "", atom); + } + str = p; + len = str->len; + if (hash) { + if (!str->is_wide_char && len > 0) { + /* check of identifier strings */ + for(i = 0; i < len; i++) { + uint8_t c = str->u.str8[i]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) + break; + } + if (i == len) { + pstrcpy(dest, size, (const char *)str->u.str8); + return len; } } + return JS_FormatString(rt, dest, size, str, '\''); + } + if (str->is_wide_char) { + /* encode surrogates correctly */ + return utf8_encode_buf16(dest, size, str->u.str16, len); + } else { + return utf8_encode_buf8(dest, size, str->u.str8, len); } - return buf; } -static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom) +/* Return a pointer to the string for a builtin atom */ +static const char *JS_AtomGetStr(JSContext *ctx, JSAtom atom) { - return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom); + if (atom > JS_ATOM_NULL && atom < JS_ATOM_END) { + JSAtomStruct *p = ctx->rt->atom_array[atom]; + if (!p->is_wide_char) + return (const char *)p->u.str8; + } + return NULL; } static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, BOOL force_string) { - char buf[ATOM_GET_STR_BUF_SIZE]; - if (__JS_AtomIsTaggedInt(atom)) { + char buf[12]; size_t len = u32toa(buf, __JS_AtomToUInt32(atom)); return js_new_string8_len(ctx, buf, len); } else { @@ -3240,44 +3295,6 @@ static BOOL JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v) !(p->len == 0 && p->is_wide_char != 0)); } -static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - const char *p; - int i; - - /* XXX: should handle embedded null characters */ - /* XXX: should move encoding code to JS_AtomGetStr */ - p = JS_AtomGetStr(ctx, buf, sizeof(buf), atom); - for (i = 0; p[i]; i++) { - int c = (unsigned char)p[i]; - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || - (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) - break; - } - if (i > 0 && p[i] == '\0') { - printf("%s", p); - } else { - putchar('"'); - printf("%.*s", i, p); - for (; p[i]; i++) { - int c = (unsigned char)p[i]; - if (c == '\"' || c == '\\') { - putchar('\\'); - putchar(c); - } else if (c >= ' ' && c <= 126) { - putchar(c); - } else if (c == '\n') { - putchar('\\'); - putchar('n'); - } else { - printf("\\u%04x", c); - } - } - putchar('\"'); - } -} - /* free with JS_FreeCString() */ const char *JS_AtomToCString(JSContext *ctx, JSAtom atom) { @@ -4651,7 +4668,6 @@ static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh, static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh) { - char atom_buf[ATOM_GET_STR_BUF_SIZE]; int j; /* XXX: should output readable class prototype */ @@ -4659,8 +4675,7 @@ static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh) sh->header.ref_count, " *"[sh->is_hashed], (void *)sh->proto, sh->prop_size, sh->prop_count); for(j = 0; j < sh->prop_count; j++) { - printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), - sh->prop[j].atom)); + js_printf_RT(rt, " %#oa", sh->prop[j].atom); } printf("\n"); } @@ -6189,9 +6204,8 @@ void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) fprintf(fp, " %5d %2.0d %s\n", obj_classes[0], 0, "none"); for (class_id = 1; class_id < JS_CLASS_INIT_COUNT; class_id++) { if (obj_classes[class_id]) { - char buf[ATOM_GET_STR_BUF_SIZE]; - fprintf(fp, " %5d %2.0d %s\n", obj_classes[class_id], class_id, - JS_AtomGetStrRT(rt, buf, sizeof(buf), js_std_class_def[class_id - 1].class_name)); + js_fprintf_RT(rt, fp, " %5d %2.0d %#oa\n", obj_classes[class_id], class_id, + js_std_class_def[class_id - 1].class_name); } } if (obj_classes[JS_CLASS_INIT_COUNT]) @@ -6601,7 +6615,7 @@ static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num, char buf[256]; JSValue obj, ret, msg; - vsnprintf(buf, sizeof(buf), fmt, ap); + js_vsnprintf(ctx, buf, sizeof(buf), fmt, ap); if (error_num == JS_PLAIN_ERROR) obj = JS_NewError(ctx); else @@ -6688,32 +6702,11 @@ static int __attribute__((format(printf, 3, 4))) JS_ThrowTypeErrorOrFalse(JSCont } } -/* never use it directly */ -static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowTypeErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowTypeError(ctx, fmt, - JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); -} - -/* never use it directly */ -static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowSyntaxErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...) -{ - char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowSyntaxError(ctx, fmt, - JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); -} - -/* %s is replaced by 'atom'. The macro is used so that gcc can check - the format string. */ -#define JS_ThrowTypeErrorAtom(ctx, fmt, atom) __JS_ThrowTypeErrorAtom(ctx, atom, fmt, "") -#define JS_ThrowSyntaxErrorAtom(ctx, fmt, atom) __JS_ThrowSyntaxErrorAtom(ctx, atom, fmt, "") - static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom) { if ((flags & JS_PROP_THROW) || ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { - JS_ThrowTypeErrorAtom(ctx, "'%s' is read-only", atom); + JS_ThrowTypeError(ctx, "'%#oa' is read-only", atom); return -1; } else { return FALSE; @@ -6781,17 +6774,16 @@ static JSValue JS_ThrowTypeErrorNotASymbol(JSContext *ctx) static JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name) { - char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowReferenceError(ctx, "%s is not defined", - JS_AtomGetStr(ctx, buf, sizeof(buf), name)); + return JS_ThrowReferenceError(ctx, "%#oa is not defined", name); } static JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name) { - char buf[ATOM_GET_STR_BUF_SIZE]; - return JS_ThrowReferenceError(ctx, "%s is not initialized", - name == JS_ATOM_NULL ? "lexical variable" : - JS_AtomGetStr(ctx, buf, sizeof(buf), name)); + if (name == JS_ATOM_NULL) { + return JS_ThrowReferenceError(ctx, "lexical variable is not initialized"); + } else { + return JS_ThrowReferenceError(ctx, "%#oa is not initialized", name); + } } static JSValue JS_ThrowReferenceErrorUninitialized2(JSContext *ctx, @@ -6814,7 +6806,7 @@ static JSValue JS_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id) JSRuntime *rt = ctx->rt; JSAtom name; name = rt->class_array[class_id].class_name; - return JS_ThrowTypeErrorAtom(ctx, "%s object expected", name); + return JS_ThrowTypeError(ctx, "%#oa object expected", name); } static no_inline __exception int __js_poll_interrupts(JSContext *ctx) @@ -7135,9 +7127,9 @@ static JSValue JS_GetPropertyInternal2(JSContext *ctx, JSValue obj, if (unlikely(tag != JS_TAG_OBJECT)) { switch(tag) { case JS_TAG_NULL: - return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", prop); + return JS_ThrowTypeError(ctx, "cannot read property '%#oa' of null", prop); case JS_TAG_UNDEFINED: - return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", prop); + return JS_ThrowTypeError(ctx, "cannot read property '%#oa' of undefined", prop); case JS_TAG_EXCEPTION: return JS_EXCEPTION; case JS_TAG_STRING: @@ -7302,8 +7294,7 @@ slow_path: static JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom) { - return JS_ThrowTypeErrorAtom(ctx, "private class field '%s' does not exist", - atom); + return JS_ThrowTypeError(ctx, "private class field '%#oa' does not exist", atom); } /* Private fields can be added even on non extensible objects or @@ -7329,8 +7320,7 @@ static int JS_DefinePrivateField(JSContext *ctx, JSValue obj, p = JS_VALUE_GET_OBJ(obj); prs = find_own_property(&pr, p, prop); if (prs) { - JS_ThrowTypeErrorAtom(ctx, "private class field '%s' already exists", - prop); + JS_ThrowTypeError(ctx, "private class field '%#oa' already exists", prop); goto fail; } pr = add_property(ctx, p, prop, JS_PROP_C_W_E); @@ -8448,10 +8438,10 @@ static int JS_SetPropertyInternal2(JSContext *ctx, JSValue obj, switch(JS_VALUE_GET_TAG(this_obj)) { case JS_TAG_NULL: - JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop); + JS_ThrowTypeError(ctx, "cannot set property '%#oa' of null", prop); goto fail; case JS_TAG_UNDEFINED: - JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined", prop); + JS_ThrowTypeError(ctx, "cannot set property '%#oa' of undefined", prop); goto fail; case JS_TAG_OBJECT: p = JS_VALUE_GET_OBJ(this_obj); @@ -9533,7 +9523,7 @@ static int JS_DefineObjectNameComputed(JSContext *ctx, JSValue obj, static JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop) { - return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop); + return JS_ThrowSyntaxError(ctx, "redeclaration of '%#oa'", prop); } /* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */ @@ -9559,8 +9549,7 @@ static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags) ((prs->flags & (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)) != (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)))) { define_error: - JS_ThrowTypeErrorAtom(ctx, "cannot define variable '%s'", - prop); + JS_ThrowTypeError(ctx, "cannot define variable '%#oa'", prop); return -1; } } @@ -11572,7 +11561,6 @@ static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) { uint32_t i; - char atom_buf[ATOM_GET_STR_BUF_SIZE]; JSShape *sh; JSShapeProperty *prs; JSProperty *pr; @@ -11580,9 +11568,7 @@ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) /* XXX: should encode atoms with special characters */ sh = p->shape; /* the shape can be NULL while freeing an object */ - printf("%14p %4d ", - (void *)p, - p->header.ref_count); + printf("%14p %4d ", (void *)p, p->header.ref_count); if (sh) { printf("%3d%c %14p ", sh->header.ref_count, @@ -11591,8 +11577,7 @@ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) } else { printf("%3s %14s ", "-", "-"); } - printf("%10s ", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), rt->class_array[p->class_id].class_name)); + js_printf_RT(rt, "%#oa ", rt->class_array[p->class_id].class_name); if (p->is_exotic && p->fast_array) { printf("[ "); for(i = 0; i < p->u.array.count; i++) { @@ -11633,8 +11618,7 @@ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) pr = &p->prop[i]; if (!is_first) printf(", "); - printf("%s: ", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), prs->atom)); + js_printf_RT(rt, "%#oa: ", prs->atom); if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { printf("[getset %p %p]", (void *)pr->u.getset.getter, (void *)pr->u.getset.setter); @@ -11659,7 +11643,7 @@ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) JSVarRef **var_refs; if (b->closure_var_count) { var_refs = p->u.func.var_refs; - printf(" Closure:"); + printf(" Closure: {"); for(i = 0; i < b->closure_var_count; i++) { printf(" "); JS_DumpValue(rt, var_refs[i]->value); @@ -11668,6 +11652,7 @@ static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) printf(" HomeObject: "); JS_DumpValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); } + printf(" }"); } } printf("\n"); @@ -11751,15 +11736,14 @@ static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValue val) { JSString *p; p = JS_VALUE_GET_STRING(val); - JS_DumpString(rt, p); + js_printf_RT(rt, "%ps", p); } break; case JS_TAG_FUNCTION_BYTECODE: { JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); - char buf[ATOM_GET_STR_BUF_SIZE]; if (b->func_name) { - printf("[bytecode %s]", JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name)); + js_printf_RT(rt, "[bytecode %#oa]", b->func_name); } else { printf("[bytecode (anonymous)]"); } @@ -11769,17 +11753,13 @@ static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValue val) { JSObject *p = JS_VALUE_GET_OBJ(val); JSAtom atom = rt->class_array[p->class_id].class_name; - char atom_buf[ATOM_GET_STR_BUF_SIZE]; - printf("[%s %p]", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), atom), (void *)p); + js_printf_RT(rt, "[%#oa %p]", atom, (void *)p); } break; case JS_TAG_SYMBOL: { JSAtomStruct *p = JS_VALUE_GET_PTR(val); - char atom_buf[ATOM_GET_STR_BUF_SIZE]; - printf("Symbol(%s)", - JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), js_get_atom_index(rt, p))); + js_printf_RT(rt, "Symbol(%ps)", p); } break; case JS_TAG_MODULE: @@ -18595,9 +18575,7 @@ static void __attribute((unused)) dump_token(JSParseState *s, case TOK_IDENT: dump_atom: { - char buf[ATOM_GET_STR_BUF_SIZE]; - printf("ident: '%s'\n", - JS_AtomGetStr(s->ctx, buf, sizeof(buf), token->u.ident.atom)); + js_printf(s->ctx, "ident: '%#oa'\n", token->u.ident.atom); } break; case TOK_STRING: @@ -18648,6 +18626,10 @@ int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const va_list ap; int backtrace_flags; + // XXX: poor man fix for stack overflow check failure in V8 tests + if (!JS_IsNull(ctx->rt->current_exception)) + return -1; + va_start(ap, fmt); JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); va_end(ap); @@ -18661,8 +18643,6 @@ int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const static int js_parse_expect(JSParseState *s, int tok) { - char buf[ATOM_GET_STR_BUF_SIZE]; - if (s->token.val == tok) return next_token(s); @@ -18678,9 +18658,8 @@ static int js_parse_expect(JSParseState *s, int tok) case TOK_REGEXP: return js_parse_error(s, "Unexpected regexp"); case TOK_IDENT: - return js_parse_error(s, "Unexpected identifier '%s'", - JS_AtomGetStr(s->ctx, buf, sizeof(buf), - s->token.u.ident.atom)); + return js_parse_error(s, "Unexpected identifier '%oa'", + s->token.u.ident.atom); case TOK_ERROR: return js_parse_error(s, "Invalid or unexpected token"); default: @@ -18704,10 +18683,7 @@ static int js_parse_expect_semi(JSParseState *s) static int js_parse_error_reserved_identifier(JSParseState *s) { - char buf1[ATOM_GET_STR_BUF_SIZE]; - return js_parse_error(s, "'%s' is a reserved identifier", - JS_AtomGetStr(s->ctx, buf1, sizeof(buf1), - s->token.u.ident.atom)); + return js_parse_error(s, "'%#oa' is a reserved identifier", s->token.u.ident.atom); } static __exception int js_parse_template_part(JSParseState *s, @@ -22427,9 +22403,7 @@ static __exception int js_parse_expr_paren(JSParseState *s) static int js_unsupported_keyword(JSParseState *s, JSAtom atom) { - char buf[ATOM_GET_STR_BUF_SIZE]; - return js_parse_error(s, "unsupported keyword: %s", - JS_AtomGetStr(s->ctx, buf, sizeof(buf), atom)); + return js_parse_error(s, "unsupported keyword: %#oa", atom); } static __exception int js_define_var(JSParseState *s, JSAtom name, int tok) @@ -25773,12 +25747,10 @@ static JSExportEntry *add_export_entry2(JSContext *ctx, JSExportEntry *me; if (find_export_entry(ctx, m, export_name)) { - char buf1[ATOM_GET_STR_BUF_SIZE]; if (s) { - js_parse_error(s, "duplicate exported name '%s'", - JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name)); + js_parse_error(s, "duplicate exported name '%#oa'", export_name); } else { - JS_ThrowSyntaxErrorAtom(ctx, "duplicate exported name '%s'", export_name); + JS_ThrowSyntaxError(ctx, "duplicate exported name '%#oa'", export_name); } return NULL; } @@ -25887,8 +25859,8 @@ static char *js_default_module_normalize_name(JSContext *ctx, { char *filename, *p; const char *r; - int cap; - int len; + size_t cap; + size_t len; if (name[0] != '.') { /* if no initial dot, the module name is not modified */ @@ -26175,26 +26147,21 @@ static void js_resolve_export_throw_error(JSContext *ctx, JSResolveResultEnum res, JSModuleDef *m, JSAtom export_name) { - char buf1[ATOM_GET_STR_BUF_SIZE]; - char buf2[ATOM_GET_STR_BUF_SIZE]; switch(res) { case JS_RESOLVE_RES_EXCEPTION: break; default: case JS_RESOLVE_RES_NOT_FOUND: - JS_ThrowSyntaxError(ctx, "Could not find export '%s' in module '%s'", - JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name), - JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name)); + JS_ThrowSyntaxError(ctx, "Could not find export '%#oa' in module '%#oa'", + export_name, m->module_name); break; case JS_RESOLVE_RES_CIRCULAR: - JS_ThrowSyntaxError(ctx, "circular reference when looking for export '%s' in module '%s'", - JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name), - JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name)); + JS_ThrowSyntaxError(ctx, "circular reference when looking for export '%#oa' in module '%#oa'", + export_name, m->module_name); break; case JS_RESOLVE_RES_AMBIGUOUS: - JS_ThrowSyntaxError(ctx, "export '%s' in module '%s' is ambiguous", - JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name), - JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name)); + JS_ThrowSyntaxError(ctx, "export '%#oa' in module '%#oa' is ambiguous", + export_name, m->module_name); break; } } @@ -26443,7 +26410,7 @@ JSValue JS_GetModuleNamespace(JSContext *ctx, JSModuleDef *m) #ifdef DUMP_MODULE_RESOLVE #define module_trace(ctx, ...) \ do if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) \ - printf(__VA_ARGS__); while (0) + js_printf(ctx, __VA_ARGS__); while (0) #else #define module_trace(...) #endif @@ -26456,12 +26423,9 @@ static int js_resolve_module(JSContext *ctx, JSModuleDef *m) if (m->resolved) return 0; -#ifdef DUMP_MODULE_RESOLVE - if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) { - char buf1[ATOM_GET_STR_BUF_SIZE]; - printf("resolving module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name)); - } -#endif + + module_trace(ctx, "resolving module '%#oa':\n", m->module_name); + m->resolved = TRUE; /* resolve each requested module */ for(i = 0; i < m->req_module_entries_count; i++) { @@ -26605,12 +26569,7 @@ static int js_link_module(JSContext *ctx, JSModuleDef *m) return 0; m->instantiated = TRUE; -#ifdef DUMP_MODULE_RESOLVE - if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) { - char buf1[ATOM_GET_STR_BUF_SIZE]; - printf("start instantiating module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name)); - } -#endif + module_trace(ctx, "start instantiating module '%#oa':\n", m->module_name); for(i = 0; i < m->req_module_entries_count; i++) { JSReqModuleEntry *rme = &m->req_module_entries[i]; @@ -26618,12 +26577,8 @@ static int js_link_module(JSContext *ctx, JSModuleDef *m) goto fail; } -#ifdef DUMP_MODULE_RESOLVE - if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) { - char buf1[ATOM_GET_STR_BUF_SIZE]; - printf("instantiating module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name)); - } -#endif + module_trace(ctx, "instantiating module '%#oa':\n", m->module_name); + /* check the indirect exports */ for(i = 0; i < m->export_entries_count; i++) { JSExportEntry *me = &m->export_entries[i]; @@ -26646,9 +26601,9 @@ static int js_link_module(JSContext *ctx, JSModuleDef *m) printf("exported bindings:\n"); for(i = 0; i < m->export_entries_count; i++) { JSExportEntry *me = &m->export_entries[i]; - printf(" name="); print_atom(ctx, me->export_name); - printf(" local="); print_atom(ctx, me->local_name); - printf(" type=%d idx=%d\n", me->export_type, me->u.local.var_idx); + js_printf(ctx, " name=%#oa local=%#oa type=%d idx=%d\n", + me->export_name, me->local_name, + me->export_type, me->u.local.var_idx); } } #endif @@ -26661,13 +26616,10 @@ static int js_link_module(JSContext *ctx, JSModuleDef *m) for(i = 0; i < m->import_entries_count; i++) { mi = &m->import_entries[i]; -#ifdef DUMP_MODULE_RESOLVE - if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) { - printf("import var_idx=%d name=", mi->var_idx); - print_atom(ctx, mi->import_name); - printf(": "); - } -#endif + + module_trace(ctx, "import var_idx=%d name=%#oa: ", + mi->var_idx, mi->import_name); + m1 = m->req_module_entries[mi->req_module_idx].module; if (mi->import_name == JS_ATOM__star_) { JSValue val; @@ -27807,23 +27759,17 @@ static void dump_byte_code(JSContext *ctx, int pass, } break; case OP_FMT_atom: - printf(" "); - print_atom(ctx, get_u32(tab + pos)); + js_printf(ctx, " %#oa", get_u32(tab + pos)); break; case OP_FMT_atom_u8: - printf(" "); - print_atom(ctx, get_u32(tab + pos)); - printf(",%d", get_u8(tab + pos + 4)); + js_printf(ctx, " %#oa,%d", get_u32(tab + pos), get_u8(tab + pos + 4)); break; case OP_FMT_atom_u16: - printf(" "); - print_atom(ctx, get_u32(tab + pos)); - printf(",%d", get_u16(tab + pos + 4)); + js_printf(ctx, " %#oa,%d", get_u32(tab + pos), get_u16(tab + pos + 4)); break; case OP_FMT_atom_label_u8: case OP_FMT_atom_label_u16: - printf(" "); - print_atom(ctx, get_u32(tab + pos)); + js_printf(ctx, " %#oa", get_u32(tab + pos)); addr = get_u32(tab + pos + 4); if (pass == 1) printf(",%u:%u", addr, label_slots[addr].pos); @@ -27847,7 +27793,7 @@ static void dump_byte_code(JSContext *ctx, int pass, has_loc: printf(" %-4d ; ", idx); if (idx < var_count) { - print_atom(ctx, vars[idx].var_name); + js_printf(ctx, "%#oa", vars[idx].var_name); } break; case OP_FMT_none_arg: @@ -27858,7 +27804,7 @@ static void dump_byte_code(JSContext *ctx, int pass, has_arg: printf(" %-4d ; ", idx); if (idx < arg_count) { - print_atom(ctx, args[idx].var_name); + js_printf(ctx, "%#oa", args[idx].var_name); } break; case OP_FMT_none_var_ref: @@ -27869,7 +27815,7 @@ static void dump_byte_code(JSContext *ctx, int pass, has_var_ref: printf(" %-4d ; ", idx); if (idx < closure_var_count) { - print_atom(ctx, closure_var[idx].var_name); + js_printf(ctx, "%#oa", closure_var[idx].var_name); } break; default: @@ -27971,16 +27917,16 @@ fail: static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionBytecode *b) { int i; - char atom_buf[ATOM_GET_STR_BUF_SIZE]; const char *str; + const char *kind = ""; if (b->filename != JS_ATOM_NULL) { - str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->filename); - printf("%s:%d:%d: ", str, b->line_num, b->col_num); + js_printf(ctx, "%#oa:%d:%d: ", b->filename, b->line_num, b->col_num); } - str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->func_name); - printf("function: %s%s\n", &"*"[b->func_kind != JS_FUNC_GENERATOR], str); + if (b->func_kind == JS_FUNC_GENERATOR) + kind = "*"; + js_printf(ctx, "function: %s%#oa\n", kind, b->func_name); if (b->js_mode) { printf(" mode:"); if (b->js_mode & JS_MODE_STRICT) @@ -27991,8 +27937,7 @@ static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionB if (b->arg_count && b->vardefs) { printf(" args:"); for(i = 0; i < b->arg_count; i++) { - printf(" %s", JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), - b->vardefs[i].var_name)); + js_printf(ctx, " %#oa", b->vardefs[i].var_name); } printf("\n"); } @@ -28000,13 +27945,13 @@ static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionB printf(" locals:\n"); for(i = 0; i < b->var_count; i++) { JSVarDef *vd = &b->vardefs[b->arg_count + i]; - printf("%5d: %s %s", i, - vd->var_kind == JS_VAR_CATCH ? "catch" : - (vd->var_kind == JS_VAR_FUNCTION_DECL || - vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) ? "function" : - vd->is_const ? "const" : - vd->is_lexical ? "let" : "var", - JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), vd->var_name)); + js_printf(ctx, "%5d: %s %#oa", i, + vd->var_kind == JS_VAR_CATCH ? "catch" : + (vd->var_kind == JS_VAR_FUNCTION_DECL || + vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) ? "function" : + vd->is_const ? "const" : + vd->is_lexical ? "let" : "var", + vd->var_name); if (vd->scope_level) printf(" [level:%d next:%d]", vd->scope_level, vd->scope_next); printf("\n"); @@ -28016,12 +27961,11 @@ static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionB printf(" closure vars:\n"); for(i = 0; i < b->closure_var_count; i++) { JSClosureVar *cv = &b->closure_var[i]; - printf("%5d: %s %s:%s%d %s\n", i, - JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), cv->var_name), - cv->is_local ? "local" : "parent", - cv->is_arg ? "arg" : "loc", cv->var_idx, - cv->is_const ? "const" : - cv->is_lexical ? "let" : "var"); + js_printf(ctx, "%5d: %#oa %s:%s%d %s\n", i, cv->var_name, + cv->is_local ? "local" : "parent", + cv->is_arg ? "arg" : "loc", cv->var_idx, + cv->is_const ? "const" : + cv->is_lexical ? "let" : "var"); } } printf(" stack_size: %d\n", b->stack_size); @@ -28838,8 +28782,7 @@ static int resolve_scope_private_field1(JSContext *ctx, } /* XXX: no line number info */ // XXX: resolve_scope_private_field1() should take JSParseState *s and use js_parse_error_atom - JS_ThrowSyntaxErrorAtom(ctx, "undefined private field '%s'", - var_name); + JS_ThrowSyntaxError(ctx, "undefined private field '%#oa'", var_name); return -1; } else { fd = fd->parent; @@ -31258,8 +31201,7 @@ static int add_module_variables(JSContext *ctx, JSFunctionDef *fd) idx = find_closure_var(ctx, fd, me->local_name); if (idx < 0) { // XXX: add_module_variables() should take JSParseState *s and use js_parse_error_atom - JS_ThrowSyntaxErrorAtom(ctx, "exported variable '%s' does not exist", - me->local_name); + JS_ThrowSyntaxError(ctx, "exported variable '%#oa' does not exist", me->local_name); return -1; } me->u.local.var_idx = idx; @@ -33578,7 +33520,7 @@ static void __attribute__((format(printf, 2, 3))) bc_read_trace(BCReaderState *s printf("%*s", 32 + s->level * 2 - n, ""); } va_start(ap, fmt); - vfprintf(stdout, fmt, ap); + js_vprintf_RT(s->ctx->rt, fmt, ap); va_end(ap); if (strchr(fmt, '{')) s->level++; @@ -33766,13 +33708,7 @@ static JSString *JS_ReadString(BCReaderState *s) } else { p->u.str8[size] = '\0'; /* add the trailing zero for 8 bit strings */ } -#ifdef DUMP_READ_OBJECT - if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { - bc_read_trace(s, ""); // hex dump and indentation - JS_DumpString(s->ctx->rt, p); - printf("\n"); - } -#endif + bc_read_trace(s, "%ps\n", p); return p; } @@ -34021,15 +33957,9 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) obj = JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b); -#ifdef DUMP_READ_OBJECT - if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { - if (b->func_name) { - bc_read_trace(s, "name: "); - print_atom(s->ctx, b->func_name); - printf("\n"); - } + if (b->func_name) { + bc_read_trace(s, "name: %#oa\n", b->func_name); } -#endif bc_read_trace(s, "args=%d vars=%d defargs=%d closures=%d cpool=%d\n", b->arg_count, b->var_count, b->defined_arg_count, b->closure_var_count, b->cpool_count); @@ -34055,18 +33985,12 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) vd->is_const = bc_get_flags(v8, &idx, 1); vd->is_lexical = bc_get_flags(v8, &idx, 1); vd->is_captured = bc_get_flags(v8, &idx, 1); -#ifdef DUMP_READ_OBJECT - if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { - bc_read_trace(s, "%3d %d%c%c%c %4d ", - i, vd->var_kind, - vd->is_const ? 'C' : '.', - vd->is_lexical ? 'L' : '.', - vd->is_captured ? 'X' : '.', - vd->scope_level); - print_atom(s->ctx, vd->var_name); - printf("\n"); - } -#endif + bc_read_trace(s, "%3d %d%c%c%c %4d %#oa\n", + i, vd->var_kind, + vd->is_const ? 'C' : '.', + vd->is_lexical ? 'L' : '.', + vd->is_captured ? 'X' : '.', + vd->scope_level, vd->var_name); } bc_read_trace(s, "}\n"); } @@ -34089,19 +34013,13 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) cv->is_const = bc_get_flags(v8, &idx, 1); cv->is_lexical = bc_get_flags(v8, &idx, 1); cv->var_kind = bc_get_flags(v8, &idx, 4); -#ifdef DUMP_READ_OBJECT - if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { - bc_read_trace(s, "%3d %d%c%c%c%c %3d ", - i, cv->var_kind, - cv->is_local ? 'L' : '.', - cv->is_arg ? 'A' : '.', - cv->is_const ? 'C' : '.', - cv->is_lexical ? 'X' : '.', - cv->var_idx); - print_atom(s->ctx, cv->var_name); - printf("\n"); - } -#endif + bc_read_trace(s, "%3d %d%c%c%c%c %3d %#oa\n", + i, cv->var_kind, + cv->is_local ? 'L' : '.', + cv->is_arg ? 'A' : '.', + cv->is_const ? 'C' : '.', + cv->is_lexical ? 'X' : '.', + cv->var_idx, cv->var_name); } bc_read_trace(s, "}\n"); } @@ -34133,13 +34051,8 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s) goto fail; if (bc_get_leb128_int(s, &b->col_num)) goto fail; -#ifdef DUMP_READ_OBJECT - if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { - bc_read_trace(s, "filename: "); - print_atom(s->ctx, b->filename); - printf(", line: %d, column: %d\n", b->line_num, b->col_num); - } -#endif + bc_read_trace(s, "filename: %#oa, line: %d, column: %d\n", + b->filename, b->line_num, b->col_num); if (bc_get_leb128_int(s, &b->pc2line_len)) goto fail; if (b->pc2line_len) { @@ -34185,13 +34098,7 @@ static JSValue JS_ReadModule(BCReaderState *s) if (bc_get_atom(s, &module_name)) goto fail; -#ifdef DUMP_READ_OBJECT - if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { - bc_read_trace(s, "name: "); - print_atom(s->ctx, module_name); - printf("\n"); - } -#endif + bc_read_trace(s, "name: %#oa\n", module_name); m = js_new_module_def(ctx, module_name); if (!m) goto fail; @@ -34296,13 +34203,7 @@ static JSValue JS_ReadObjectTag(BCReaderState *s) for(i = 0; i < prop_count; i++) { if (bc_get_atom(s, &atom)) goto fail; -#ifdef DUMP_READ_OBJECT - if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { - bc_read_trace(s, "propname: "); - print_atom(s->ctx, atom); - printf("\n"); - } -#endif + bc_read_trace(s, "propname: %#oa\n", atom); val = JS_ReadObjectRec(s); if (JS_IsException(val)) { JS_FreeAtom(ctx, atom); @@ -45634,11 +45535,9 @@ void JS_AddIntrinsicMapSet(JSContext *ctx) { int i; JSValue obj1; - char buf[ATOM_GET_STR_BUF_SIZE]; for(i = 0; i < 4; i++) { - const char *name = JS_AtomGetStr(ctx, buf, sizeof(buf), - JS_ATOM_Map + i); + const char *name = JS_AtomGetStr(ctx, JS_ATOM_Map + i); ctx->class_proto[JS_CLASS_MAP + i] = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_MAP + i], js_map_proto_funcs_ptr[i], @@ -45722,7 +45621,7 @@ static void promise_reaction_data_free(JSRuntime *rt, #ifdef DUMP_PROMISE #define promise_trace(ctx, ...) \ do if (check_dump_flag(ctx->rt, DUMP_PROMISE)) \ - printf(__VA_ARGS__); while (0) + js_printf(ctx, __VA_ARGS__); while (0) #else #define promise_trace(...) #endif @@ -48843,9 +48742,8 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) JS_SetPropertyFunctionList(ctx, obj, js_symbol_funcs, countof(js_symbol_funcs)); for(i = JS_ATOM_Symbol_toPrimitive; i < JS_ATOM_END; i++) { - char buf[ATOM_GET_STR_BUF_SIZE]; const char *str, *p; - str = JS_AtomGetStr(ctx, buf, sizeof(buf), i); + str = JS_AtomGetStr(ctx, i); /* skip "Symbol." */ p = strchr(str, '.'); if (p) @@ -51982,7 +51880,6 @@ void JS_AddIntrinsicTypedArrays(JSContext *ctx) JSCFunctionType ft = { .generic_magic = js_typed_array_constructor }; for(i = JS_CLASS_UINT8C_ARRAY; i < JS_CLASS_UINT8C_ARRAY + JS_TYPED_ARRAY_COUNT; i++) { JSValue func_obj; - char buf[ATOM_GET_STR_BUF_SIZE]; const char *name; ctx->class_proto[i] = JS_NewObjectProto(ctx, typed_array_base_proto); @@ -51990,8 +51887,7 @@ void JS_AddIntrinsicTypedArrays(JSContext *ctx) "BYTES_PER_ELEMENT", js_int32(1 << typed_array_size_log2(i)), 0); - name = JS_AtomGetStr(ctx, buf, sizeof(buf), - JS_ATOM_Uint8ClampedArray + i - JS_CLASS_UINT8C_ARRAY); + name = JS_AtomGetStr(ctx, JS_ATOM_Uint8ClampedArray + i - JS_CLASS_UINT8C_ARRAY); func_obj = JS_NewCFunction3(ctx, ft.generic, name, 3, JS_CFUNC_constructor_magic, i, typed_array_base_func); @@ -52731,3 +52627,5 @@ static void _JS_AddIntrinsicCallSite(JSContext *ctx) js_callsite_proto_funcs, countof(js_callsite_proto_funcs)); } + +#include "quickjs-printf.c" diff --git a/tests/test_conv.c b/tests/test_conv.c index 9761b30..ddc099a 100644 --- a/tests/test_conv.c +++ b/tests/test_conv.c @@ -152,7 +152,7 @@ size_t i64toa_##v(char buf[minimum_length(22)], int64_t n) \ else \ buf = (buf << 8) | (c) -size_t u7toa_shift(char dest[minimum_length(8)], uint32_t n) +static size_t u7toa_shift(char dest[minimum_length(8)], uint32_t n) { size_t len = 1; uint64_t buf = 0; @@ -167,7 +167,7 @@ size_t u7toa_shift(char dest[minimum_length(8)], uint32_t n) return len; } -size_t u07toa_shift(char dest[minimum_length(8)], uint32_t n, size_t len) +static size_t u07toa_shift(char dest[minimum_length(8)], uint32_t n, size_t len) { size_t i; dest += len; @@ -193,8 +193,9 @@ size_t u32toa_shift(char buf[minimum_length(11)], uint32_t n) #define TEN_POW_7 10000000 if (n >= TEN_POW_7) { uint32_t quo = n / TEN_POW_7; + size_t len; n %= TEN_POW_7; - size_t len = u7toa_shift(buf, quo); + len = u7toa_shift(buf, quo); return u07toa_shift(buf, n, len); } return u7toa_shift(buf, n); @@ -202,24 +203,25 @@ size_t u32toa_shift(char buf[minimum_length(11)], uint32_t n) size_t u64toa_shift(char buf[minimum_length(21)], uint64_t n) { - if (likely(n < 0x100000000)) - return u32toa_shift(buf, n); - size_t len; + + if (likely(n < 0x100000000)) + return u32toa_shift(buf, (uint32_t)n); + if (n >= TEN_POW_7) { uint64_t n1 = n / TEN_POW_7; n %= TEN_POW_7; if (n1 >= TEN_POW_7) { - uint32_t quo = n1 / TEN_POW_7; + uint32_t quo = (uint32_t)(n1 / TEN_POW_7); n1 %= TEN_POW_7; len = u7toa_shift(buf, quo); - len = u07toa_shift(buf, n1, len); + len = u07toa_shift(buf, (uint32_t)n1, len); } else { - len = u7toa_shift(buf, n1); + len = u7toa_shift(buf, (uint32_t)n1); } - return u07toa_shift(buf, n, len); + return u07toa_shift(buf, (uint32_t)n, len); } - return u7toa_shift(buf, n); + return u7toa_shift(buf, (uint32_t)n); } define_i32toa(shift) @@ -878,7 +880,7 @@ size_t u32toa_radix_length(char buf[minimum_length(33)], uint32_t n, unsigned ba shift = radix_shift[base & 63]; if (shift) { uint32_t mask = (1 << shift) - 1; - size_t len = (32 - clz32(n) + shift - 1) / shift; + size_t len = (size_t)((32 - clz32(n) + shift - 1) / shift); size_t last = n & mask; char *end = buf + len; n >>= shift; @@ -893,14 +895,15 @@ size_t u32toa_radix_length(char buf[minimum_length(33)], uint32_t n, unsigned ba return len; } else { size_t len = 2; - size_t last = n % base; - n /= base; uint32_t nbase = base; + size_t last = n % base; + char *end; + n /= base; while (n >= nbase) { nbase *= base; len++; } - char *end = buf + len; + end = buf + len; *end-- = '\0'; *end-- = digits36[last]; while (n >= base) { @@ -927,33 +930,36 @@ size_t u64toa_radix_length(char buf[minimum_length(65)], uint64_t n, unsigned ba buf[0] = digits36[n]; buf[1] = '\0'; return 1; - } - uint64_t mask = (1 << shift) - 1; - size_t len = (64 - clz64(n) + shift - 1) / shift; - size_t last = n & mask; - char *end = buf + len; - n >>= shift; - *end-- = '\0'; - *end-- = digits36[last]; - while (n >= base) { - size_t quo = n & mask; + } else { + uint64_t mask = (1 << shift) - 1; + size_t len = (size_t)((64 - clz64(n) + shift - 1) / shift); + size_t last = n & mask; + char *end = buf + len; n >>= shift; - *end-- = digits36[quo]; + *end-- = '\0'; + *end-- = digits36[last]; + while (n >= base) { + size_t quo = n & mask; + n >>= shift; + *end-- = digits36[quo]; + } + *end = digits36[n]; + return len; } - *end = digits36[n]; - return len; + } else + if (likely(n < 0x100000000)) { + return u32toa_radix_length(buf, (uint32_t)n, base); } else { - if (likely(n < 0x100000000)) - return u32toa_radix_length(buf, n, base); size_t last = n % base; - n /= base; uint64_t nbase = base; size_t len = 2; + char *end; + n /= base; while (n >= nbase) { nbase *= base; len++; } - char *end = buf + len; + end = buf + len; *end-- = '\0'; *end-- = digits36[last]; while (n >= base) {