/* * JSON built-ins. * * See doc/json.rst. * * Codepoints are handled as duk_uint_fast32_t to ensure that the full * unsigned 32-bit range is supported. This matters to e.g. JX. * * Input parsing doesn't do an explicit end-of-input check at all. This is * safe: input string data is always NUL-terminated (0x00) and valid JSON * inputs never contain plain NUL characters, so that as long as syntax checks * are correct, we'll never read past the NUL. This approach reduces code size * and improves parsing performance, but it's critical that syntax checks are * indeed correct! */ #include "duk_internal.h" #if defined(DUK_USE_JSON_SUPPORT) /* * Local defines and forward declarations. */ #define DUK__JSON_DECSTR_BUFSIZE 128 #define DUK__JSON_DECSTR_CHUNKSIZE 64 #define DUK__JSON_ENCSTR_CHUNKSIZE 64 #define DUK__JSON_STRINGIFY_BUFSIZE 128 #define DUK__JSON_MAX_ESC_LEN 10 /* '\Udeadbeef' */ DUK_LOCAL_DECL void duk__dec_syntax_error(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__dec_eat_white(duk_json_dec_ctx *js_ctx); #if defined(DUK_USE_JX) DUK_LOCAL_DECL duk_uint8_t duk__dec_peek(duk_json_dec_ctx *js_ctx); #endif DUK_LOCAL_DECL duk_uint8_t duk__dec_get(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL duk_uint8_t duk__dec_get_nonwhite(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL duk_uint_fast32_t duk__dec_decode_hex_escape(duk_json_dec_ctx *js_ctx, duk_small_uint_t n); DUK_LOCAL_DECL void duk__dec_req_stridx(duk_json_dec_ctx *js_ctx, duk_small_uint_t stridx); DUK_LOCAL_DECL void duk__dec_string(duk_json_dec_ctx *js_ctx); #if defined(DUK_USE_JX) DUK_LOCAL_DECL void duk__dec_plain_string(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__dec_pointer(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__dec_buffer(duk_json_dec_ctx *js_ctx); #endif DUK_LOCAL_DECL void duk__dec_number(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__dec_objarr_entry(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__dec_objarr_exit(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__dec_object(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__dec_array(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__dec_value(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__dec_reviver_walk(duk_json_dec_ctx *js_ctx); DUK_LOCAL_DECL void duk__emit_1(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch); DUK_LOCAL_DECL void duk__emit_2(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch1, duk_uint_fast8_t ch2); DUK_LOCAL_DECL void duk__unemit_1(duk_json_enc_ctx *js_ctx); DUK_LOCAL_DECL void duk__emit_hstring(duk_json_enc_ctx *js_ctx, duk_hstring *h); #if defined(DUK_USE_FASTINT) DUK_LOCAL_DECL void duk__emit_cstring(duk_json_enc_ctx *js_ctx, const char *p); #endif DUK_LOCAL_DECL void duk__emit_stridx(duk_json_enc_ctx *js_ctx, duk_small_uint_t stridx); DUK_LOCAL_DECL duk_uint8_t *duk__emit_esc_auto_fast(duk_json_enc_ctx *js_ctx, duk_uint_fast32_t cp, duk_uint8_t *q); DUK_LOCAL_DECL void duk__enc_key_autoquote(duk_json_enc_ctx *js_ctx, duk_hstring *k); DUK_LOCAL_DECL void duk__enc_quote_string(duk_json_enc_ctx *js_ctx, duk_hstring *h_str); DUK_LOCAL_DECL void duk__enc_objarr_entry(duk_json_enc_ctx *js_ctx, duk_idx_t *entry_top); DUK_LOCAL_DECL void duk__enc_objarr_exit(duk_json_enc_ctx *js_ctx, duk_idx_t *entry_top); DUK_LOCAL_DECL void duk__enc_object(duk_json_enc_ctx *js_ctx); DUK_LOCAL_DECL void duk__enc_array(duk_json_enc_ctx *js_ctx); DUK_LOCAL_DECL duk_bool_t duk__enc_value(duk_json_enc_ctx *js_ctx, duk_idx_t idx_holder); DUK_LOCAL_DECL duk_bool_t duk__enc_allow_into_proplist(duk_tval *tv); DUK_LOCAL_DECL void duk__enc_double(duk_json_enc_ctx *js_ctx); #if defined(DUK_USE_FASTINT) DUK_LOCAL_DECL void duk__enc_fastint_tval(duk_json_enc_ctx *js_ctx, duk_tval *tv); #endif #if defined(DUK_USE_JX) || defined(DUK_USE_JC) DUK_LOCAL_DECL void duk__enc_buffer_jx_jc(duk_json_enc_ctx *js_ctx, duk_hbuffer *h); DUK_LOCAL_DECL void duk__enc_pointer(duk_json_enc_ctx *js_ctx, void *ptr); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) DUK_LOCAL_DECL void duk__enc_bufobj(duk_json_enc_ctx *js_ctx, duk_hbufobj *h_bufobj); #endif #endif #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH) DUK_LOCAL_DECL void duk__enc_buffer_json_fastpath(duk_json_enc_ctx *js_ctx, duk_hbuffer *h); #endif DUK_LOCAL_DECL void duk__enc_newline_indent(duk_json_enc_ctx *js_ctx, duk_uint_t depth); /* * Helper tables */ #if defined(DUK_USE_JSON_QUOTESTRING_FASTPATH) DUK_LOCAL const duk_uint8_t duk__json_quotestr_lookup[256] = { /* 0x00 ... 0x7f: as is * 0x80: escape generically * 0x81: slow path * 0xa0 ... 0xff: backslash + one char */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xe2, 0xf4, 0xee, 0x80, 0xe6, 0xf2, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20, 0x21, 0xa2, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0xdc, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81 }; #else /* DUK_USE_JSON_QUOTESTRING_FASTPATH */ DUK_LOCAL const duk_uint8_t duk__json_quotestr_esc[14] = { DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_LC_B, DUK_ASC_LC_T, DUK_ASC_LC_N, DUK_ASC_NUL, DUK_ASC_LC_F, DUK_ASC_LC_R }; #endif /* DUK_USE_JSON_QUOTESTRING_FASTPATH */ #if defined(DUK_USE_JSON_DECSTRING_FASTPATH) DUK_LOCAL const duk_uint8_t duk__json_decstr_lookup[256] = { /* 0x00: slow path * other: as is */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x21, 0x00, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x00, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; #endif /* DUK_USE_JSON_DECSTRING_FASTPATH */ #if defined(DUK_USE_JSON_EATWHITE_FASTPATH) DUK_LOCAL const duk_uint8_t duk__json_eatwhite_lookup[256] = { /* 0x00: finish (non-white) * 0x01: continue */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #endif /* DUK_USE_JSON_EATWHITE_FASTPATH */ #if defined(DUK_USE_JSON_DECNUMBER_FASTPATH) DUK_LOCAL const duk_uint8_t duk__json_decnumber_lookup[256] = { /* 0x00: finish (not part of number) * 0x01: continue */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #endif /* DUK_USE_JSON_DECNUMBER_FASTPATH */ /* * Parsing implementation. * * JSON lexer is now separate from duk_lexer.c because there are numerous * small differences making it difficult to share the lexer. * * The parser here works with raw bytes directly; this works because all * JSON delimiters are ASCII characters. Invalid xUTF-8 encoded values * inside strings will be passed on without normalization; this is not a * compliance concern because compliant inputs will always be valid * CESU-8 encodings. */ DUK_LOCAL void duk__dec_syntax_error(duk_json_dec_ctx *js_ctx) { /* Shared handler to minimize parser size. Cause will be * hidden, unfortunately, but we'll have an offset which * is often quite enough. */ DUK_ERROR_FMT1(js_ctx->thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_FMT_INVALID_JSON, (long) (js_ctx->p - js_ctx->p_start)); DUK_WO_NORETURN(return;); } DUK_LOCAL void duk__dec_eat_white(duk_json_dec_ctx *js_ctx) { const duk_uint8_t *p; duk_uint8_t t; p = js_ctx->p; for (;;) { DUK_ASSERT(p <= js_ctx->p_end); t = *p; #if defined(DUK_USE_JSON_EATWHITE_FASTPATH) /* This fast path is pretty marginal in practice. * XXX: candidate for removal. */ DUK_ASSERT(duk__json_eatwhite_lookup[0x00] == 0x00); /* end-of-input breaks */ if (duk__json_eatwhite_lookup[t] == 0) { break; } #else /* DUK_USE_JSON_EATWHITE_FASTPATH */ if (!(t == 0x20 || t == 0x0a || t == 0x0d || t == 0x09)) { /* NUL also comes here. Comparison order matters, 0x20 * is most common whitespace. */ break; } #endif /* DUK_USE_JSON_EATWHITE_FASTPATH */ p++; } js_ctx->p = p; } #if defined(DUK_USE_JX) DUK_LOCAL duk_uint8_t duk__dec_peek(duk_json_dec_ctx *js_ctx) { DUK_ASSERT(js_ctx->p <= js_ctx->p_end); return *js_ctx->p; } #endif DUK_LOCAL duk_uint8_t duk__dec_get(duk_json_dec_ctx *js_ctx) { DUK_ASSERT(js_ctx->p <= js_ctx->p_end); return *js_ctx->p++; } DUK_LOCAL duk_uint8_t duk__dec_get_nonwhite(duk_json_dec_ctx *js_ctx) { duk__dec_eat_white(js_ctx); return duk__dec_get(js_ctx); } /* For JX, expressing the whole unsigned 32-bit range matters. */ DUK_LOCAL duk_uint_fast32_t duk__dec_decode_hex_escape(duk_json_dec_ctx *js_ctx, duk_small_uint_t n) { duk_small_uint_t i; duk_uint_fast32_t res = 0; duk_uint8_t x; duk_small_int_t t; for (i = 0; i < n; i++) { /* XXX: share helper from lexer; duk_lexer.c / hexval(). */ x = duk__dec_get(js_ctx); DUK_DDD(DUK_DDDPRINT("decode_hex_escape: i=%ld, n=%ld, res=%ld, x=%ld", (long) i, (long) n, (long) res, (long) x)); /* x == 0x00 (EOF) causes syntax_error */ DUK_ASSERT(duk_hex_dectab[0] == -1); t = duk_hex_dectab[x & 0xff]; if (DUK_LIKELY(t >= 0)) { res = (res * 16) + (duk_uint_fast32_t) t; } else { /* catches EOF and invalid digits */ goto syntax_error; } } DUK_DDD(DUK_DDDPRINT("final hex decoded value: %ld", (long) res)); return res; syntax_error: duk__dec_syntax_error(js_ctx); DUK_UNREACHABLE(); return 0; } DUK_LOCAL void duk__dec_req_stridx(duk_json_dec_ctx *js_ctx, duk_small_uint_t stridx) { duk_hstring *h; const duk_uint8_t *p; duk_uint8_t x, y; /* First character has already been eaten and checked by the caller. * We can scan until a NUL in stridx string because no built-in strings * have internal NULs. */ DUK_ASSERT_STRIDX_VALID(stridx); h = DUK_HTHREAD_GET_STRING(js_ctx->thr, stridx); DUK_ASSERT(h != NULL); p = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h) + 1; DUK_ASSERT(*(js_ctx->p - 1) == *(p - 1)); /* first character has been matched */ for (;;) { x = *p; if (x == 0) { break; } y = duk__dec_get(js_ctx); if (x != y) { /* Catches EOF of JSON input. */ goto syntax_error; } p++; } return; syntax_error: duk__dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } DUK_LOCAL duk_small_int_t duk__dec_string_escape(duk_json_dec_ctx *js_ctx, duk_uint8_t **ext_p) { duk_uint_fast32_t cp; /* EOF (-1) will be cast to an unsigned value first * and then re-cast for the switch. In any case, it * will match the default case (syntax error). */ cp = (duk_uint_fast32_t) duk__dec_get(js_ctx); switch (cp) { case DUK_ASC_BACKSLASH: break; case DUK_ASC_DOUBLEQUOTE: break; case DUK_ASC_SLASH: break; case DUK_ASC_LC_T: cp = 0x09; break; case DUK_ASC_LC_N: cp = 0x0a; break; case DUK_ASC_LC_R: cp = 0x0d; break; case DUK_ASC_LC_F: cp = 0x0c; break; case DUK_ASC_LC_B: cp = 0x08; break; case DUK_ASC_LC_U: { cp = duk__dec_decode_hex_escape(js_ctx, 4); break; } #if defined(DUK_USE_JX) case DUK_ASC_UC_U: { if (js_ctx->flag_ext_custom) { cp = duk__dec_decode_hex_escape(js_ctx, 8); } else { return 1; /* syntax error */ } break; } case DUK_ASC_LC_X: { if (js_ctx->flag_ext_custom) { cp = duk__dec_decode_hex_escape(js_ctx, 2); } else { return 1; /* syntax error */ } break; } #endif /* DUK_USE_JX */ default: /* catches EOF (0x00) */ return 1; /* syntax error */ } DUK_RAW_WRITE_XUTF8(*ext_p, cp); return 0; } DUK_LOCAL void duk__dec_string(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_bufwriter_ctx bw_alloc; duk_bufwriter_ctx *bw; duk_uint8_t *q; /* '"' was eaten by caller */ /* Note that we currently parse -bytes-, not codepoints. * All non-ASCII extended UTF-8 will encode to bytes >= 0x80, * so they'll simply pass through (valid UTF-8 or not). */ bw = &bw_alloc; DUK_BW_INIT_PUSHBUF(js_ctx->thr, bw, DUK__JSON_DECSTR_BUFSIZE); q = DUK_BW_GET_PTR(js_ctx->thr, bw); #if defined(DUK_USE_JSON_DECSTRING_FASTPATH) for (;;) { duk_small_uint_t safe; duk_uint8_t b, x; const duk_uint8_t *p; /* Select a safe loop count where no output checks are * needed assuming we won't encounter escapes. Input * bound checks are not necessary as a NUL (guaranteed) * will cause a SyntaxError before we read out of bounds. */ safe = DUK__JSON_DECSTR_CHUNKSIZE; /* Ensure space for 1:1 output plus one escape. */ q = DUK_BW_ENSURE_RAW(js_ctx->thr, bw, safe + DUK_UNICODE_MAX_XUTF8_LENGTH, q); p = js_ctx->p; /* temp copy, write back for next loop */ for (;;) { if (safe == 0) { js_ctx->p = p; break; } safe--; /* End of input (NUL) goes through slow path and causes SyntaxError. */ DUK_ASSERT(duk__json_decstr_lookup[0] == 0x00); b = *p++; x = (duk_small_int_t) duk__json_decstr_lookup[b]; if (DUK_LIKELY(x != 0)) { /* Fast path, decode as is. */ *q++ = b; } else if (b == DUK_ASC_DOUBLEQUOTE) { js_ctx->p = p; goto found_quote; } else if (b == DUK_ASC_BACKSLASH) { /* We've ensured space for one escaped input; then * bail out and recheck (this makes escape handling * quite slow but it's uncommon). */ js_ctx->p = p; if (duk__dec_string_escape(js_ctx, &q) != 0) { goto syntax_error; } break; } else { js_ctx->p = p; goto syntax_error; } } } found_quote: #else /* DUK_USE_JSON_DECSTRING_FASTPATH */ for (;;) { duk_uint8_t x; q = DUK_BW_ENSURE_RAW(js_ctx->thr, bw, DUK_UNICODE_MAX_XUTF8_LENGTH, q); x = duk__dec_get(js_ctx); if (x == DUK_ASC_DOUBLEQUOTE) { break; } else if (x == DUK_ASC_BACKSLASH) { if (duk__dec_string_escape(js_ctx, &q) != 0) { goto syntax_error; } } else if (x < 0x20) { /* catches EOF (NUL) */ goto syntax_error; } else { *q++ = (duk_uint8_t) x; } } #endif /* DUK_USE_JSON_DECSTRING_FASTPATH */ DUK_BW_SETPTR_AND_COMPACT(js_ctx->thr, bw, q); (void) duk_buffer_to_string(thr, -1); /* Safe if input string is safe. */ /* [ ... str ] */ return; syntax_error: duk__dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } #if defined(DUK_USE_JX) /* Decode a plain string consisting entirely of identifier characters. * Used to parse plain keys (e.g. "foo: 123"). */ DUK_LOCAL void duk__dec_plain_string(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; const duk_uint8_t *p; duk_small_int_t x; /* Caller has already eaten the first char so backtrack one byte. */ js_ctx->p--; /* safe */ p = js_ctx->p; /* Here again we parse bytes, and non-ASCII UTF-8 will cause end of * parsing (which is correct except if there are non-shortest encodings). * There is also no need to check explicitly for end of input buffer as * the input is NUL padded and NUL will exit the parsing loop. * * Because no unescaping takes place, we can just scan to the end of the * plain string and intern from the input buffer. */ for (;;) { x = *p; /* There is no need to check the first character specially here * (i.e. reject digits): the caller only accepts valid initial * characters and won't call us if the first character is a digit. * This also ensures that the plain string won't be empty. */ if (!duk_unicode_is_identifier_part((duk_codepoint_t) x)) { break; } p++; } duk_push_lstring(thr, (const char *) js_ctx->p, (duk_size_t) (p - js_ctx->p)); js_ctx->p = p; /* [ ... str ] */ } #endif /* DUK_USE_JX */ #if defined(DUK_USE_JX) DUK_LOCAL void duk__dec_pointer(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; const duk_uint8_t *p; duk_small_int_t x; void *voidptr; /* Caller has already eaten the first character ('(') which we don't need. */ p = js_ctx->p; for (;;) { x = *p; /* Assume that the native representation never contains a closing * parenthesis. */ if (x == DUK_ASC_RPAREN) { break; } else if (x <= 0) { /* NUL term or -1 (EOF), NUL check would suffice */ goto syntax_error; } p++; } /* There is no need to NUL delimit the sscanf() call: trailing garbage is * ignored and there is always a NUL terminator which will force an error * if no error is encountered before it. It's possible that the scan * would scan further than between [js_ctx->p,p[ though and we'd advance * by less than the scanned value. * * Because pointers are platform specific, a failure to scan a pointer * results in a null pointer which is a better placeholder than a missing * value or an error. */ voidptr = NULL; (void) DUK_SSCANF((const char *) js_ctx->p, DUK_STR_FMT_PTR, &voidptr); duk_push_pointer(thr, voidptr); js_ctx->p = p + 1; /* skip ')' */ /* [ ... ptr ] */ return; syntax_error: duk__dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } #endif /* DUK_USE_JX */ #if defined(DUK_USE_JX) DUK_LOCAL void duk__dec_buffer(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; const duk_uint8_t *p; duk_uint8_t *buf; duk_size_t src_len; duk_small_int_t x; /* Caller has already eaten the first character ('|') which we don't need. */ p = js_ctx->p; /* XXX: Would be nice to share the fast path loop from duk_hex_decode() * and avoid creating a temporary buffer. However, there are some * differences which prevent trivial sharing: * * - Pipe char detection * - EOF detection * - Unknown length of input and output * * The best approach here would be a bufwriter and a reasonaly sized * safe inner loop (e.g. 64 output bytes at a time). */ for (;;) { x = *p; /* This loop intentionally does not ensure characters are valid * ([0-9a-fA-F]) because the hex decode call below will do that. */ if (x == DUK_ASC_PIPE) { break; } else if (x <= 0) { /* NUL term or -1 (EOF), NUL check would suffice */ goto syntax_error; } p++; } /* XXX: this is not very nice; unnecessary copy is made. */ src_len = (duk_size_t) (p - js_ctx->p); buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, src_len); DUK_ASSERT(buf != NULL); duk_memcpy((void *) buf, (const void *) js_ctx->p, src_len); duk_hex_decode(thr, -1); js_ctx->p = p + 1; /* skip '|' */ /* [ ... buf ] */ return; syntax_error: duk__dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } #endif /* DUK_USE_JX */ /* Parse a number, other than NaN or +/- Infinity */ DUK_LOCAL void duk__dec_number(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; const duk_uint8_t *p_start; const duk_uint8_t *p; duk_uint8_t x; duk_small_uint_t s2n_flags; DUK_DDD(DUK_DDDPRINT("parse_number")); p_start = js_ctx->p; /* First pass parse is very lenient (e.g. allows '1.2.3') and extracts a * string for strict number parsing. */ p = js_ctx->p; for (;;) { x = *p; DUK_DDD(DUK_DDDPRINT("parse_number: p_start=%p, p=%p, p_end=%p, x=%ld", (const void *) p_start, (const void *) p, (const void *) js_ctx->p_end, (long) x)); #if defined(DUK_USE_JSON_DECNUMBER_FASTPATH) /* This fast path is pretty marginal in practice. * XXX: candidate for removal. */ DUK_ASSERT(duk__json_decnumber_lookup[0x00] == 0x00); /* end-of-input breaks */ if (duk__json_decnumber_lookup[x] == 0) { break; } #else /* DUK_USE_JSON_DECNUMBER_FASTPATH */ if (!((x >= DUK_ASC_0 && x <= DUK_ASC_9) || (x == DUK_ASC_PERIOD || x == DUK_ASC_LC_E || x == DUK_ASC_UC_E || x == DUK_ASC_MINUS || x == DUK_ASC_PLUS))) { /* Plus sign must be accepted for positive exponents * (e.g. '1.5e+2'). This clause catches NULs. */ break; } #endif /* DUK_USE_JSON_DECNUMBER_FASTPATH */ p++; /* safe, because matched (NUL causes a break) */ } js_ctx->p = p; DUK_ASSERT(js_ctx->p > p_start); duk_push_lstring(thr, (const char *) p_start, (duk_size_t) (p - p_start)); s2n_flags = DUK_S2N_FLAG_ALLOW_EXP | DUK_S2N_FLAG_ALLOW_MINUS | /* but don't allow leading plus */ DUK_S2N_FLAG_ALLOW_FRAC; DUK_DDD(DUK_DDDPRINT("parse_number: string before parsing: %!T", (duk_tval *) duk_get_tval(thr, -1))); duk_numconv_parse(thr, 10 /*radix*/, s2n_flags); if (duk_is_nan(thr, -1)) { duk__dec_syntax_error(js_ctx); } DUK_ASSERT(duk_is_number(thr, -1)); DUK_DDD(DUK_DDDPRINT("parse_number: final number: %!T", (duk_tval *) duk_get_tval(thr, -1))); /* [ ... num ] */ } DUK_LOCAL void duk__dec_objarr_entry(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_require_stack(thr, DUK_JSON_DEC_REQSTACK); /* c recursion check */ DUK_ASSERT_DISABLE(js_ctx->recursion_depth >= 0); /* unsigned */ DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); if (js_ctx->recursion_depth >= js_ctx->recursion_limit) { DUK_ERROR_RANGE(thr, DUK_STR_JSONDEC_RECLIMIT); DUK_WO_NORETURN(return;); } js_ctx->recursion_depth++; } DUK_LOCAL void duk__dec_objarr_exit(duk_json_dec_ctx *js_ctx) { /* c recursion check */ DUK_ASSERT(js_ctx->recursion_depth > 0); DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); js_ctx->recursion_depth--; } DUK_LOCAL void duk__dec_object(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_int_t key_count; /* XXX: a "first" flag would suffice */ duk_uint8_t x; DUK_DDD(DUK_DDDPRINT("parse_object")); duk__dec_objarr_entry(js_ctx); duk_push_object(thr); /* Initial '{' has been checked and eaten by caller. */ key_count = 0; for (;;) { x = duk__dec_get_nonwhite(js_ctx); DUK_DDD(DUK_DDDPRINT("parse_object: obj=%!T, x=%ld, key_count=%ld", (duk_tval *) duk_get_tval(thr, -1), (long) x, (long) key_count)); /* handle comma and closing brace */ if (x == DUK_ASC_COMMA && key_count > 0) { /* accept comma, expect new value */ x = duk__dec_get_nonwhite(js_ctx); } else if (x == DUK_ASC_RCURLY) { /* eat closing brace */ break; } else if (key_count == 0) { /* accept anything, expect first value (EOF will be * caught by key parsing below. */ ; } else { /* catches EOF (NUL) and initial comma */ goto syntax_error; } /* parse key and value */ if (x == DUK_ASC_DOUBLEQUOTE) { duk__dec_string(js_ctx); #if defined(DUK_USE_JX) } else if (js_ctx->flag_ext_custom && duk_unicode_is_identifier_start((duk_codepoint_t) x)) { duk__dec_plain_string(js_ctx); #endif } else { goto syntax_error; } /* [ ... obj key ] */ x = duk__dec_get_nonwhite(js_ctx); if (x != DUK_ASC_COLON) { goto syntax_error; } duk__dec_value(js_ctx); /* [ ... obj key val ] */ duk_xdef_prop_wec(thr, -3); /* [ ... obj ] */ key_count++; } /* [ ... obj ] */ DUK_DDD(DUK_DDDPRINT("parse_object: final object is %!T", (duk_tval *) duk_get_tval(thr, -1))); duk__dec_objarr_exit(js_ctx); return; syntax_error: duk__dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } DUK_LOCAL void duk__dec_array(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_uarridx_t arr_idx; duk_uint8_t x; DUK_DDD(DUK_DDDPRINT("parse_array")); duk__dec_objarr_entry(js_ctx); duk_push_array(thr); /* Initial '[' has been checked and eaten by caller. */ arr_idx = 0; for (;;) { x = duk__dec_get_nonwhite(js_ctx); DUK_DDD(DUK_DDDPRINT("parse_array: arr=%!T, x=%ld, arr_idx=%ld", (duk_tval *) duk_get_tval(thr, -1), (long) x, (long) arr_idx)); /* handle comma and closing bracket */ if ((x == DUK_ASC_COMMA) && (arr_idx != 0)) { /* accept comma, expect new value */ ; } else if (x == DUK_ASC_RBRACKET) { /* eat closing bracket */ break; } else if (arr_idx == 0) { /* accept anything, expect first value (EOF will be * caught by duk__dec_value() below. */ js_ctx->p--; /* backtrack (safe) */ } else { /* catches EOF (NUL) and initial comma */ goto syntax_error; } /* parse value */ duk__dec_value(js_ctx); /* [ ... arr val ] */ duk_xdef_prop_index_wec(thr, -2, arr_idx); arr_idx++; } /* Must set 'length' explicitly when using duk_xdef_prop_xxx() to * set the values. */ duk_set_length(thr, -1, arr_idx); /* [ ... arr ] */ DUK_DDD(DUK_DDDPRINT("parse_array: final array is %!T", (duk_tval *) duk_get_tval(thr, -1))); duk__dec_objarr_exit(js_ctx); return; syntax_error: duk__dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } DUK_LOCAL void duk__dec_value(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_uint8_t x; x = duk__dec_get_nonwhite(js_ctx); DUK_DDD(DUK_DDDPRINT("parse_value: initial x=%ld", (long) x)); /* Note: duk__dec_req_stridx() backtracks one char */ if (x == DUK_ASC_DOUBLEQUOTE) { duk__dec_string(js_ctx); } else if ((x >= DUK_ASC_0 && x <= DUK_ASC_9) || (x == DUK_ASC_MINUS)) { #if defined(DUK_USE_JX) if (js_ctx->flag_ext_custom && x == DUK_ASC_MINUS && duk__dec_peek(js_ctx) == DUK_ASC_UC_I) { duk__dec_req_stridx(js_ctx, DUK_STRIDX_MINUS_INFINITY); /* "-Infinity", '-' has been eaten */ duk_push_number(thr, -DUK_DOUBLE_INFINITY); } else { #else { /* unconditional block */ #endif /* We already ate 'x', so backup one byte. */ js_ctx->p--; /* safe */ duk__dec_number(js_ctx); } } else if (x == DUK_ASC_LC_T) { duk__dec_req_stridx(js_ctx, DUK_STRIDX_TRUE); duk_push_true(thr); } else if (x == DUK_ASC_LC_F) { duk__dec_req_stridx(js_ctx, DUK_STRIDX_FALSE); duk_push_false(thr); } else if (x == DUK_ASC_LC_N) { duk__dec_req_stridx(js_ctx, DUK_STRIDX_LC_NULL); duk_push_null(thr); #if defined(DUK_USE_JX) } else if (js_ctx->flag_ext_custom && x == DUK_ASC_LC_U) { duk__dec_req_stridx(js_ctx, DUK_STRIDX_LC_UNDEFINED); duk_push_undefined(thr); } else if (js_ctx->flag_ext_custom && x == DUK_ASC_UC_N) { duk__dec_req_stridx(js_ctx, DUK_STRIDX_NAN); duk_push_nan(thr); } else if (js_ctx->flag_ext_custom && x == DUK_ASC_UC_I) { duk__dec_req_stridx(js_ctx, DUK_STRIDX_INFINITY); duk_push_number(thr, DUK_DOUBLE_INFINITY); } else if (js_ctx->flag_ext_custom && x == DUK_ASC_LPAREN) { duk__dec_pointer(js_ctx); } else if (js_ctx->flag_ext_custom && x == DUK_ASC_PIPE) { duk__dec_buffer(js_ctx); #endif } else if (x == DUK_ASC_LCURLY) { duk__dec_object(js_ctx); } else if (x == DUK_ASC_LBRACKET) { duk__dec_array(js_ctx); } else { /* catches EOF (NUL) */ goto syntax_error; } duk__dec_eat_white(js_ctx); /* [ ... val ] */ return; syntax_error: duk__dec_syntax_error(js_ctx); DUK_UNREACHABLE(); } /* Recursive value reviver, implements the Walk() algorithm. No C recursion * check is done here because the initial parsing step will already ensure * there is a reasonable limit on C recursion depth and hence object depth. */ DUK_LOCAL void duk__dec_reviver_walk(duk_json_dec_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_hobject *h; duk_uarridx_t i, arr_len; DUK_DDD(DUK_DDDPRINT("walk: top=%ld, holder=%!T, name=%!T", (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); duk_dup_top(thr); duk_get_prop(thr, -3); /* -> [ ... holder name val ] */ h = duk_get_hobject(thr, -1); if (h != NULL) { if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAY) { arr_len = (duk_uarridx_t) duk_get_length(thr, -1); for (i = 0; i < arr_len; i++) { /* [ ... holder name val ] */ DUK_DDD(DUK_DDDPRINT("walk: array, top=%ld, i=%ld, arr_len=%ld, holder=%!T, name=%!T, val=%!T", (long) duk_get_top(thr), (long) i, (long) arr_len, (duk_tval *) duk_get_tval(thr, -3), (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); duk_dup_top(thr); (void) duk_push_uint_to_hstring(thr, (duk_uint_t) i); /* -> [ ... holder name val val ToString(i) ] */ duk__dec_reviver_walk(js_ctx); /* -> [ ... holder name val new_elem ] */ if (duk_is_undefined(thr, -1)) { duk_pop(thr); duk_del_prop_index(thr, -1, i); } else { /* XXX: duk_xdef_prop_index_wec() would be more appropriate * here but it currently makes some assumptions that might * not hold (e.g. that previous property is not an accessor). */ duk_put_prop_index(thr, -2, i); } } } else { /* [ ... holder name val ] */ duk_enum(thr, -1, DUK_ENUM_OWN_PROPERTIES_ONLY /*flags*/); while (duk_next(thr, -1 /*enum_index*/, 0 /*get_value*/)) { DUK_DDD(DUK_DDDPRINT("walk: object, top=%ld, holder=%!T, name=%!T, val=%!T, enum=%!iT, obj_key=%!T", (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, -5), (duk_tval *) duk_get_tval(thr, -4), (duk_tval *) duk_get_tval(thr, -3), (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); /* [ ... holder name val enum obj_key ] */ duk_dup_m3(thr); duk_dup_m2(thr); /* [ ... holder name val enum obj_key val obj_key ] */ duk__dec_reviver_walk(js_ctx); /* [ ... holder name val enum obj_key new_elem ] */ if (duk_is_undefined(thr, -1)) { duk_pop(thr); duk_del_prop(thr, -3); } else { /* XXX: duk_xdef_prop_index_wec() would be more appropriate * here but it currently makes some assumptions that might * not hold (e.g. that previous property is not an accessor). * * Using duk_put_prop() works incorrectly with '__proto__' * if the own property with that name has been deleted. This * does not happen normally, but a clever reviver can trigger * that, see complex reviver case in: test-bug-json-parse-__proto__.js. */ duk_put_prop(thr, -4); } } duk_pop(thr); /* pop enum */ } } /* [ ... holder name val ] */ duk_dup(thr, js_ctx->idx_reviver); duk_insert(thr, -4); /* -> [ ... reviver holder name val ] */ duk_call_method(thr, 2); /* -> [ ... res ] */ DUK_DDD(DUK_DDDPRINT("walk: top=%ld, result=%!T", (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, -1))); } /* * Stringify implementation. */ #define DUK__EMIT_1(js_ctx,ch) duk__emit_1((js_ctx), (duk_uint_fast8_t) (ch)) #define DUK__EMIT_2(js_ctx,ch1,ch2) duk__emit_2((js_ctx), (duk_uint_fast8_t) (ch1), (duk_uint_fast8_t) (ch2)) #define DUK__EMIT_HSTR(js_ctx,h) duk__emit_hstring((js_ctx), (h)) #if defined(DUK_USE_FASTINT) || defined(DUK_USE_JX) || defined(DUK_USE_JC) #define DUK__EMIT_CSTR(js_ctx,p) duk__emit_cstring((js_ctx), (p)) #endif #define DUK__EMIT_STRIDX(js_ctx,i) duk__emit_stridx((js_ctx), (i)) #define DUK__UNEMIT_1(js_ctx) duk__unemit_1((js_ctx)) DUK_LOCAL void duk__emit_1(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch) { DUK_BW_WRITE_ENSURE_U8(js_ctx->thr, &js_ctx->bw, ch); } DUK_LOCAL void duk__emit_2(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch1, duk_uint_fast8_t ch2) { DUK_BW_WRITE_ENSURE_U8_2(js_ctx->thr, &js_ctx->bw, ch1, ch2); } DUK_LOCAL void duk__emit_hstring(duk_json_enc_ctx *js_ctx, duk_hstring *h) { DUK_BW_WRITE_ENSURE_HSTRING(js_ctx->thr, &js_ctx->bw, h); } #if defined(DUK_USE_FASTINT) || defined(DUK_USE_JX) || defined(DUK_USE_JC) DUK_LOCAL void duk__emit_cstring(duk_json_enc_ctx *js_ctx, const char *str) { DUK_BW_WRITE_ENSURE_CSTRING(js_ctx->thr, &js_ctx->bw, str); } #endif DUK_LOCAL void duk__emit_stridx(duk_json_enc_ctx *js_ctx, duk_small_uint_t stridx) { duk_hstring *h; DUK_ASSERT_STRIDX_VALID(stridx); h = DUK_HTHREAD_GET_STRING(js_ctx->thr, stridx); DUK_ASSERT(h != NULL); DUK_BW_WRITE_ENSURE_HSTRING(js_ctx->thr, &js_ctx->bw, h); } DUK_LOCAL void duk__unemit_1(duk_json_enc_ctx *js_ctx) { DUK_ASSERT(DUK_BW_GET_SIZE(js_ctx->thr, &js_ctx->bw) >= 1); DUK_BW_ADD_PTR(js_ctx->thr, &js_ctx->bw, -1); } #define DUK__MKESC(nybbles,esc1,esc2) \ (((duk_uint_fast32_t) (nybbles)) << 16) | \ (((duk_uint_fast32_t) (esc1)) << 8) | \ ((duk_uint_fast32_t) (esc2)) DUK_LOCAL duk_uint8_t *duk__emit_esc_auto_fast(duk_json_enc_ctx *js_ctx, duk_uint_fast32_t cp, duk_uint8_t *q) { duk_uint_fast32_t tmp; duk_small_uint_t dig; DUK_UNREF(js_ctx); /* Caller ensures space for at least DUK__JSON_MAX_ESC_LEN. */ /* Select appropriate escape format automatically, and set 'tmp' to a * value encoding both the escape format character and the nybble count: * * (nybble_count << 16) | (escape_char1) | (escape_char2) */ #if defined(DUK_USE_JX) if (DUK_LIKELY(cp < 0x100UL)) { if (DUK_UNLIKELY(js_ctx->flag_ext_custom != 0U)) { tmp = DUK__MKESC(2, DUK_ASC_BACKSLASH, DUK_ASC_LC_X); } else { tmp = DUK__MKESC(4, DUK_ASC_BACKSLASH, DUK_ASC_LC_U); } } else #endif if (DUK_LIKELY(cp < 0x10000UL)) { tmp = DUK__MKESC(4, DUK_ASC_BACKSLASH, DUK_ASC_LC_U); } else { #if defined(DUK_USE_JX) if (DUK_LIKELY(js_ctx->flag_ext_custom != 0U)) { tmp = DUK__MKESC(8, DUK_ASC_BACKSLASH, DUK_ASC_UC_U); } else #endif { /* In compatible mode and standard JSON mode, output * something useful for non-BMP characters. This won't * roundtrip but will still be more or less readable and * more useful than an error. */ tmp = DUK__MKESC(8, DUK_ASC_UC_U, DUK_ASC_PLUS); } } *q++ = (duk_uint8_t) ((tmp >> 8) & 0xff); *q++ = (duk_uint8_t) (tmp & 0xff); tmp = tmp >> 16; while (tmp > 0) { tmp--; dig = (duk_small_uint_t) ((cp >> (4 * tmp)) & 0x0f); *q++ = duk_lc_digits[dig]; } return q; } DUK_LOCAL void duk__enc_key_autoquote(duk_json_enc_ctx *js_ctx, duk_hstring *k) { const duk_int8_t *p, *p_start, *p_end; /* Note: intentionally signed. */ duk_size_t k_len; duk_codepoint_t cp; DUK_ASSERT(k != NULL); /* Accept ASCII strings which conform to identifier requirements * as being emitted without key quotes. Since we only accept ASCII * there's no need for actual decoding: 'p' is intentionally signed * so that bytes >= 0x80 extend to negative values and are rejected * as invalid identifier codepoints. */ if (js_ctx->flag_avoid_key_quotes) { k_len = DUK_HSTRING_GET_BYTELEN(k); p_start = (const duk_int8_t *) DUK_HSTRING_GET_DATA(k); p_end = p_start + k_len; p = p_start; if (p == p_end) { /* Zero length string is not accepted without quotes */ goto quote_normally; } cp = (duk_codepoint_t) (*p++); if (DUK_UNLIKELY(!duk_unicode_is_identifier_start(cp))) { goto quote_normally; } while (p < p_end) { cp = (duk_codepoint_t) (*p++); if (DUK_UNLIKELY(!duk_unicode_is_identifier_part(cp))) { goto quote_normally; } } /* This seems faster than emitting bytes one at a time and * then potentially rewinding. */ DUK__EMIT_HSTR(js_ctx, k); return; } quote_normally: duk__enc_quote_string(js_ctx, k); } /* The Quote(value) operation: quote a string. * * Stack policy: [ ] -> [ ]. */ DUK_LOCAL void duk__enc_quote_string(duk_json_enc_ctx *js_ctx, duk_hstring *h_str) { duk_hthread *thr = js_ctx->thr; const duk_uint8_t *p, *p_start, *p_end, *p_now, *p_tmp; duk_uint8_t *q; duk_ucodepoint_t cp; /* typed for duk_unicode_decode_xutf8() */ DUK_DDD(DUK_DDDPRINT("duk__enc_quote_string: h_str=%!O", (duk_heaphdr *) h_str)); DUK_ASSERT(h_str != NULL); p_start = DUK_HSTRING_GET_DATA(h_str); p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_str); p = p_start; DUK__EMIT_1(js_ctx, DUK_ASC_DOUBLEQUOTE); /* Encode string in small chunks, estimating the maximum expansion so that * there's no need to ensure space while processing the chunk. */ while (p < p_end) { duk_size_t left, now, space; left = (duk_size_t) (p_end - p); now = (left > DUK__JSON_ENCSTR_CHUNKSIZE ? DUK__JSON_ENCSTR_CHUNKSIZE : left); /* Maximum expansion per input byte is 6: * - invalid UTF-8 byte causes "\uXXXX" to be emitted (6/1 = 6). * - 2-byte UTF-8 encodes as "\uXXXX" (6/2 = 3). * - 4-byte UTF-8 encodes as "\Uxxxxxxxx" (10/4 = 2.5). */ space = now * 6; q = DUK_BW_ENSURE_GETPTR(thr, &js_ctx->bw, space); p_now = p + now; while (p < p_now) { #if defined(DUK_USE_JSON_QUOTESTRING_FASTPATH) duk_uint8_t b; b = duk__json_quotestr_lookup[*p++]; if (DUK_LIKELY(b < 0x80)) { /* Most input bytes go through here. */ *q++ = b; } else if (b >= 0xa0) { *q++ = DUK_ASC_BACKSLASH; *q++ = (duk_uint8_t) (b - 0x80); } else if (b == 0x80) { cp = (duk_ucodepoint_t) (*(p - 1)); q = duk__emit_esc_auto_fast(js_ctx, cp, q); } else if (b == 0x7f && js_ctx->flag_ascii_only) { /* 0x7F is special */ DUK_ASSERT(b == 0x81); cp = (duk_ucodepoint_t) 0x7f; q = duk__emit_esc_auto_fast(js_ctx, cp, q); } else { DUK_ASSERT(b == 0x81); p--; /* slow path is shared */ #else /* DUK_USE_JSON_QUOTESTRING_FASTPATH */ cp = *p; if (DUK_LIKELY(cp <= 0x7f)) { /* ascii fast path: avoid decoding utf-8 */ p++; if (cp == 0x22 || cp == 0x5c) { /* double quote or backslash */ *q++ = DUK_ASC_BACKSLASH; *q++ = (duk_uint8_t) cp; } else if (cp < 0x20) { duk_uint_fast8_t esc_char; /* This approach is a bit shorter than a straight * if-else-ladder and also a bit faster. */ if (cp < (sizeof(duk__json_quotestr_esc) / sizeof(duk_uint8_t)) && (esc_char = duk__json_quotestr_esc[cp]) != 0) { *q++ = DUK_ASC_BACKSLASH; *q++ = (duk_uint8_t) esc_char; } else { q = duk__emit_esc_auto_fast(js_ctx, cp, q); } } else if (cp == 0x7f && js_ctx->flag_ascii_only) { q = duk__emit_esc_auto_fast(js_ctx, cp, q); } else { /* any other printable -> as is */ *q++ = (duk_uint8_t) cp; } } else { /* slow path is shared */ #endif /* DUK_USE_JSON_QUOTESTRING_FASTPATH */ /* slow path decode */ /* If XUTF-8 decoding fails, treat the offending byte as a codepoint directly * and go forward one byte. This is of course very lossy, but allows some kind * of output to be produced even for internal strings which don't conform to * XUTF-8. All standard ECMAScript strings are always CESU-8, so this behavior * does not violate the ECMAScript specification. The behavior is applied to * all modes, including ECMAScript standard JSON. Because the current XUTF-8 * decoding is not very strict, this behavior only really affects initial bytes * and truncated codepoints. * * Another alternative would be to scan forwards to start of next codepoint * (or end of input) and emit just one replacement codepoint. */ p_tmp = p; if (!duk_unicode_decode_xutf8(thr, &p, p_start, p_end, &cp)) { /* Decode failed. */ cp = *p_tmp; p = p_tmp + 1; } #if defined(DUK_USE_NONSTD_JSON_ESC_U2028_U2029) if (js_ctx->flag_ascii_only || cp == 0x2028 || cp == 0x2029) { #else if (js_ctx->flag_ascii_only) { #endif q = duk__emit_esc_auto_fast(js_ctx, cp, q); } else { /* as is */ DUK_RAW_WRITE_XUTF8(q, cp); } } } DUK_BW_SET_PTR(thr, &js_ctx->bw, q); } DUK__EMIT_1(js_ctx, DUK_ASC_DOUBLEQUOTE); } /* Encode a double (checked by caller) from stack top. Stack top may be * replaced by serialized string but is not popped (caller does that). */ DUK_LOCAL void duk__enc_double(duk_json_enc_ctx *js_ctx) { duk_hthread *thr; duk_tval *tv; duk_double_t d; duk_small_int_t c; duk_small_int_t s; duk_small_uint_t stridx; duk_small_uint_t n2s_flags; duk_hstring *h_str; DUK_ASSERT(js_ctx != NULL); thr = js_ctx->thr; DUK_ASSERT(thr != NULL); /* Caller must ensure 'tv' is indeed a double and not a fastint! */ tv = DUK_GET_TVAL_NEGIDX(thr, -1); DUK_ASSERT(DUK_TVAL_IS_DOUBLE(tv)); d = DUK_TVAL_GET_DOUBLE(tv); c = (duk_small_int_t) DUK_FPCLASSIFY(d); s = (duk_small_int_t) DUK_SIGNBIT(d); DUK_UNREF(s); if (DUK_LIKELY(!(c == DUK_FP_INFINITE || c == DUK_FP_NAN))) { DUK_ASSERT(DUK_ISFINITE(d)); #if defined(DUK_USE_JX) || defined(DUK_USE_JC) /* Negative zero needs special handling in JX/JC because * it would otherwise serialize to '0', not '-0'. */ if (DUK_UNLIKELY(c == DUK_FP_ZERO && s != 0 && (js_ctx->flag_ext_custom_or_compatible))) { duk_push_hstring_stridx(thr, DUK_STRIDX_MINUS_ZERO); /* '-0' */ } else #endif /* DUK_USE_JX || DUK_USE_JC */ { n2s_flags = 0; /* [ ... number ] -> [ ... string ] */ duk_numconv_stringify(thr, 10 /*radix*/, 0 /*digits*/, n2s_flags); } h_str = duk_known_hstring(thr, -1); DUK__EMIT_HSTR(js_ctx, h_str); return; } #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (!(js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE))) { stridx = DUK_STRIDX_LC_NULL; } else if (c == DUK_FP_NAN) { stridx = js_ctx->stridx_custom_nan; } else if (s == 0) { stridx = js_ctx->stridx_custom_posinf; } else { stridx = js_ctx->stridx_custom_neginf; } #else stridx = DUK_STRIDX_LC_NULL; #endif DUK__EMIT_STRIDX(js_ctx, stridx); } #if defined(DUK_USE_FASTINT) /* Encode a fastint from duk_tval ptr, no value stack effects. */ DUK_LOCAL void duk__enc_fastint_tval(duk_json_enc_ctx *js_ctx, duk_tval *tv) { duk_int64_t v; /* Fastint range is signed 48-bit so longest value is -2^47 = -140737488355328 * (16 chars long), longest signed 64-bit value is -2^63 = -9223372036854775808 * (20 chars long). Alloc space for 64-bit range to be safe. */ duk_uint8_t buf[20 + 1]; /* Caller must ensure 'tv' is indeed a fastint! */ DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv)); v = DUK_TVAL_GET_FASTINT(tv); /* XXX: There are no format strings in duk_config.h yet, could add * one for formatting duk_int64_t. For now, assumes "%lld" and that * "long long" type exists. Could also rely on C99 directly but that * won't work for older MSVC. */ DUK_SPRINTF((char *) buf, "%lld", (long long) v); DUK__EMIT_CSTR(js_ctx, (const char *) buf); } #endif #if defined(DUK_USE_JX) || defined(DUK_USE_JC) #if defined(DUK_USE_HEX_FASTPATH) DUK_LOCAL duk_uint8_t *duk__enc_buffer_data_hex(const duk_uint8_t *src, duk_size_t src_len, duk_uint8_t *dst) { duk_uint8_t *q; duk_uint16_t *q16; duk_small_uint_t x; duk_size_t i, len_safe; #if !defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE) duk_bool_t shift_dst; #endif /* Unlike in duk_hex_encode() 'dst' is not necessarily aligned by 2. * For platforms where unaligned accesses are not allowed, shift 'dst' * ahead by 1 byte to get alignment and then duk_memmove() the result * in place. The faster encoding loop makes up the difference. * There's always space for one extra byte because a terminator always * follows the hex data and that's been accounted for by the caller. */ #if defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE) q16 = (duk_uint16_t *) (void *) dst; #else shift_dst = (duk_bool_t) (((duk_size_t) dst) & 0x01U); if (shift_dst) { DUK_DD(DUK_DDPRINT("unaligned accesses not possible, dst not aligned -> step to dst + 1")); q16 = (duk_uint16_t *) (void *) (dst + 1); } else { DUK_DD(DUK_DDPRINT("unaligned accesses not possible, dst is aligned")); q16 = (duk_uint16_t *) (void *) dst; } DUK_ASSERT((((duk_size_t) q16) & 0x01U) == 0); #endif len_safe = src_len & ~0x03U; for (i = 0; i < len_safe; i += 4) { q16[0] = duk_hex_enctab[src[i]]; q16[1] = duk_hex_enctab[src[i + 1]]; q16[2] = duk_hex_enctab[src[i + 2]]; q16[3] = duk_hex_enctab[src[i + 3]]; q16 += 4; } q = (duk_uint8_t *) q16; #if !defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE) if (shift_dst) { q--; duk_memmove((void *) dst, (const void *) (dst + 1), 2 * len_safe); DUK_ASSERT(dst + 2 * len_safe == q); } #endif for (; i < src_len; i++) { x = src[i]; *q++ = duk_lc_digits[x >> 4]; *q++ = duk_lc_digits[x & 0x0f]; } return q; } #else /* DUK_USE_HEX_FASTPATH */ DUK_LOCAL duk_uint8_t *duk__enc_buffer_data_hex(const duk_uint8_t *src, duk_size_t src_len, duk_uint8_t *dst) { const duk_uint8_t *p; const duk_uint8_t *p_end; duk_uint8_t *q; duk_small_uint_t x; p = src; p_end = src + src_len; q = dst; while (p != p_end) { x = *p++; *q++ = duk_lc_digits[x >> 4]; *q++ = duk_lc_digits[x & 0x0f]; } return q; } #endif /* DUK_USE_HEX_FASTPATH */ DUK_LOCAL void duk__enc_buffer_data(duk_json_enc_ctx *js_ctx, duk_uint8_t *buf_data, duk_size_t buf_len) { duk_hthread *thr; duk_uint8_t *q; duk_size_t space; thr = js_ctx->thr; DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible); /* caller checks */ DUK_ASSERT(js_ctx->flag_ext_custom_or_compatible); /* Buffer values are encoded in (lowercase) hex to make the * binary data readable. Base64 or similar would be more * compact but less readable, and the point of JX/JC * variants is to be as useful to a programmer as possible. */ /* The #if defined() clutter here needs to handle the three * cases: (1) JX+JC, (2) JX only, (3) JC only. */ /* Note: space must cater for both JX and JC. */ space = 9 + buf_len * 2 + 2; DUK_ASSERT(DUK_HBUFFER_MAX_BYTELEN <= 0x7ffffffeUL); DUK_ASSERT((space - 2) / 2 >= buf_len); /* overflow not possible, buffer limits */ q = DUK_BW_ENSURE_GETPTR(thr, &js_ctx->bw, space); #if defined(DUK_USE_JX) && defined(DUK_USE_JC) if (js_ctx->flag_ext_custom) #endif #if defined(DUK_USE_JX) { *q++ = DUK_ASC_PIPE; q = duk__enc_buffer_data_hex(buf_data, buf_len, q); *q++ = DUK_ASC_PIPE; } #endif #if defined(DUK_USE_JX) && defined(DUK_USE_JC) else #endif #if defined(DUK_USE_JC) { DUK_ASSERT(js_ctx->flag_ext_compatible); duk_memcpy((void *) q, (const void *) "{\"_buf\":\"", 9); /* len: 9 */ q += 9; q = duk__enc_buffer_data_hex(buf_data, buf_len, q); *q++ = DUK_ASC_DOUBLEQUOTE; *q++ = DUK_ASC_RCURLY; } #endif DUK_BW_SET_PTR(thr, &js_ctx->bw, q); } DUK_LOCAL void duk__enc_buffer_jx_jc(duk_json_enc_ctx *js_ctx, duk_hbuffer *h) { duk__enc_buffer_data(js_ctx, (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(js_ctx->thr->heap, h), (duk_size_t) DUK_HBUFFER_GET_SIZE(h)); } #endif /* DUK_USE_JX || DUK_USE_JC */ #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH) DUK_LOCAL void duk__enc_buffer_json_fastpath(duk_json_enc_ctx *js_ctx, duk_hbuffer *h) { duk_size_t i, n; const duk_uint8_t *buf; duk_uint8_t *q; n = DUK_HBUFFER_GET_SIZE(h); if (n == 0) { DUK__EMIT_2(js_ctx, DUK_ASC_LCURLY, DUK_ASC_RCURLY); return; } DUK__EMIT_1(js_ctx, DUK_ASC_LCURLY); /* Maximum encoded length with 32-bit index: 1 + 10 + 2 + 3 + 1 + 1 = 18, * with 64-bit index: 1 + 20 + 2 + 3 + 1 + 1 = 28. 32 has some slack. * * Note that because the output buffer is reallocated from time to time, * side effects (such as finalizers) affecting the buffer 'h' must be * disabled. This is the case in the JSON.stringify() fast path. */ buf = (const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(js_ctx->thr->heap, h); if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { for (i = 0; i < n; i++) { duk__enc_newline_indent(js_ctx, js_ctx->recursion_depth + 1); q = DUK_BW_ENSURE_GETPTR(js_ctx->thr, &js_ctx->bw, 32); q += DUK_SPRINTF((char *) q, "\"%lu\": %u,", (unsigned long) i, (unsigned int) buf[i]); DUK_BW_SET_PTR(js_ctx->thr, &js_ctx->bw, q); } } else { q = DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw); for (i = 0; i < n; i++) { q = DUK_BW_ENSURE_RAW(js_ctx->thr, &js_ctx->bw, 32, q); q += DUK_SPRINTF((char *) q, "\"%lu\":%u,", (unsigned long) i, (unsigned int) buf[i]); } DUK_BW_SET_PTR(js_ctx->thr, &js_ctx->bw, q); } DUK__UNEMIT_1(js_ctx); /* eat trailing comma */ if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { duk__enc_newline_indent(js_ctx, js_ctx->recursion_depth); } DUK__EMIT_1(js_ctx, DUK_ASC_RCURLY); } #endif /* DUK_USE_JSON_STRINGIFY_FASTPATH */ #if defined(DUK_USE_JX) || defined(DUK_USE_JC) DUK_LOCAL void duk__enc_pointer(duk_json_enc_ctx *js_ctx, void *ptr) { char buf[64]; /* XXX: how to figure correct size? */ const char *fmt; DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible); /* caller checks */ DUK_ASSERT(js_ctx->flag_ext_custom_or_compatible); duk_memzero(buf, sizeof(buf)); /* The #if defined() clutter here needs to handle the three * cases: (1) JX+JC, (2) JX only, (3) JC only. */ #if defined(DUK_USE_JX) && defined(DUK_USE_JC) if (js_ctx->flag_ext_custom) #endif #if defined(DUK_USE_JX) { fmt = ptr ? "(%p)" : "(null)"; } #endif #if defined(DUK_USE_JX) && defined(DUK_USE_JC) else #endif #if defined(DUK_USE_JC) { DUK_ASSERT(js_ctx->flag_ext_compatible); fmt = ptr ? "{\"_ptr\":\"%p\"}" : "{\"_ptr\":\"null\"}"; } #endif /* When ptr == NULL, the format argument is unused. */ DUK_SNPRINTF(buf, sizeof(buf) - 1, fmt, ptr); /* must not truncate */ DUK__EMIT_CSTR(js_ctx, buf); } #endif /* DUK_USE_JX || DUK_USE_JC */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) #if defined(DUK_USE_JX) || defined(DUK_USE_JC) DUK_LOCAL void duk__enc_bufobj(duk_json_enc_ctx *js_ctx, duk_hbufobj *h_bufobj) { DUK_ASSERT_HBUFOBJ_VALID(h_bufobj); if (h_bufobj->buf == NULL || !DUK_HBUFOBJ_VALID_SLICE(h_bufobj)) { DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL); } else { /* Handle both full and partial slice (as long as covered). */ duk__enc_buffer_data(js_ctx, (duk_uint8_t *) DUK_HBUFOBJ_GET_SLICE_BASE(js_ctx->thr->heap, h_bufobj), (duk_size_t) h_bufobj->length); } } #endif /* DUK_USE_JX || DUK_USE_JC */ #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ /* Indent helper. Calling code relies on js_ctx->recursion_depth also being * directly related to indent depth. */ #if defined(DUK_USE_PREFER_SIZE) DUK_LOCAL void duk__enc_newline_indent(duk_json_enc_ctx *js_ctx, duk_uint_t depth) { DUK_ASSERT(js_ctx->h_gap != NULL); DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap) > 0); /* caller guarantees */ DUK__EMIT_1(js_ctx, 0x0a); while (depth-- > 0) { DUK__EMIT_HSTR(js_ctx, js_ctx->h_gap); } } #else /* DUK_USE_PREFER_SIZE */ DUK_LOCAL void duk__enc_newline_indent(duk_json_enc_ctx *js_ctx, duk_uint_t depth) { const duk_uint8_t *gap_data; duk_size_t gap_len; duk_size_t avail_bytes; /* bytes of indent available for copying */ duk_size_t need_bytes; /* bytes of indent still needed */ duk_uint8_t *p_start; duk_uint8_t *p; DUK_ASSERT(js_ctx->h_gap != NULL); DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap) > 0); /* caller guarantees */ DUK__EMIT_1(js_ctx, 0x0a); if (DUK_UNLIKELY(depth == 0)) { return; } /* To handle deeper indents efficiently, make use of copies we've * already emitted. In effect we can emit a sequence of 1, 2, 4, * 8, etc copies, and then finish the last run. Byte counters * avoid multiply with gap_len on every loop. */ gap_data = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(js_ctx->h_gap); gap_len = (duk_size_t) DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap); DUK_ASSERT(gap_len > 0); need_bytes = gap_len * depth; p = DUK_BW_ENSURE_GETPTR(js_ctx->thr, &js_ctx->bw, need_bytes); p_start = p; duk_memcpy((void *) p, (const void *) gap_data, (size_t) gap_len); p += gap_len; avail_bytes = gap_len; DUK_ASSERT(need_bytes >= gap_len); need_bytes -= gap_len; while (need_bytes >= avail_bytes) { duk_memcpy((void *) p, (const void *) p_start, (size_t) avail_bytes); p += avail_bytes; need_bytes -= avail_bytes; avail_bytes <<= 1; } DUK_ASSERT(need_bytes < avail_bytes); /* need_bytes may be zero */ duk_memcpy((void *) p, (const void *) p_start, (size_t) need_bytes); p += need_bytes; /*avail_bytes += need_bytes*/ DUK_BW_SET_PTR(js_ctx->thr, &js_ctx->bw, p); } #endif /* DUK_USE_PREFER_SIZE */ /* Shared entry handling for object/array serialization. */ DUK_LOCAL void duk__enc_objarr_entry(duk_json_enc_ctx *js_ctx, duk_idx_t *entry_top) { duk_hthread *thr = js_ctx->thr; duk_hobject *h_target; duk_uint_fast32_t i, n; *entry_top = duk_get_top(thr); duk_require_stack(thr, DUK_JSON_ENC_REQSTACK); /* Loop check using a hybrid approach: a fixed-size visited[] array * with overflow in a loop check object. */ h_target = duk_known_hobject(thr, -1); /* object or array */ n = js_ctx->recursion_depth; if (DUK_UNLIKELY(n > DUK_JSON_ENC_LOOPARRAY)) { n = DUK_JSON_ENC_LOOPARRAY; } for (i = 0; i < n; i++) { if (DUK_UNLIKELY(js_ctx->visiting[i] == h_target)) { DUK_DD(DUK_DDPRINT("slow path loop detect")); DUK_ERROR_TYPE(thr, DUK_STR_CYCLIC_INPUT); DUK_WO_NORETURN(return;); } } if (js_ctx->recursion_depth < DUK_JSON_ENC_LOOPARRAY) { js_ctx->visiting[js_ctx->recursion_depth] = h_target; } else { duk_push_sprintf(thr, DUK_STR_FMT_PTR, (void *) h_target); duk_dup_top(thr); /* -> [ ... voidp voidp ] */ if (duk_has_prop(thr, js_ctx->idx_loop)) { DUK_ERROR_TYPE(thr, DUK_STR_CYCLIC_INPUT); DUK_WO_NORETURN(return;); } duk_push_true(thr); /* -> [ ... voidp true ] */ duk_put_prop(thr, js_ctx->idx_loop); /* -> [ ... ] */ } /* C recursion check. */ DUK_ASSERT_DISABLE(js_ctx->recursion_depth >= 0); /* unsigned */ DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); if (js_ctx->recursion_depth >= js_ctx->recursion_limit) { DUK_ERROR_RANGE(thr, DUK_STR_JSONENC_RECLIMIT); DUK_WO_NORETURN(return;); } js_ctx->recursion_depth++; DUK_DDD(DUK_DDDPRINT("shared entry finished: top=%ld, loop=%!T", (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, js_ctx->idx_loop))); } /* Shared exit handling for object/array serialization. */ DUK_LOCAL void duk__enc_objarr_exit(duk_json_enc_ctx *js_ctx, duk_idx_t *entry_top) { duk_hthread *thr = js_ctx->thr; duk_hobject *h_target; /* C recursion check. */ DUK_ASSERT(js_ctx->recursion_depth > 0); DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); js_ctx->recursion_depth--; /* Loop check. */ h_target = duk_known_hobject(thr, *entry_top - 1); /* original target at entry_top - 1 */ if (js_ctx->recursion_depth < DUK_JSON_ENC_LOOPARRAY) { /* Previous entry was inside visited[], nothing to do. */ } else { duk_push_sprintf(thr, DUK_STR_FMT_PTR, (void *) h_target); duk_del_prop(thr, js_ctx->idx_loop); /* -> [ ... ] */ } /* Restore stack top after unbalanced code paths. */ duk_set_top(thr, *entry_top); DUK_DDD(DUK_DDDPRINT("shared entry finished: top=%ld, loop=%!T", (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, js_ctx->idx_loop))); } /* The JO(value) operation: encode object. * * Stack policy: [ object ] -> [ object ]. */ DUK_LOCAL void duk__enc_object(duk_json_enc_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_hstring *h_key; duk_idx_t entry_top; duk_idx_t idx_obj; duk_idx_t idx_keys; duk_bool_t emitted; duk_uarridx_t arr_len, i; duk_size_t prev_size; DUK_DDD(DUK_DDDPRINT("duk__enc_object: obj=%!T", (duk_tval *) duk_get_tval(thr, -1))); duk__enc_objarr_entry(js_ctx, &entry_top); idx_obj = entry_top - 1; if (js_ctx->idx_proplist >= 0) { idx_keys = js_ctx->idx_proplist; } else { /* XXX: would be nice to enumerate an object at specified index */ duk_dup(thr, idx_obj); (void) duk_hobject_get_enumerated_keys(thr, DUK_ENUM_OWN_PROPERTIES_ONLY /*flags*/); /* [ ... target ] -> [ ... target keys ] */ idx_keys = duk_require_normalize_index(thr, -1); /* leave stack unbalanced on purpose */ } DUK_DDD(DUK_DDDPRINT("idx_keys=%ld, h_keys=%!T", (long) idx_keys, (duk_tval *) duk_get_tval(thr, idx_keys))); /* Steps 8-10 have been merged to avoid a "partial" variable. */ DUK__EMIT_1(js_ctx, DUK_ASC_LCURLY); /* XXX: keys is an internal object with all keys to be processed * in its (gapless) array part. Because nobody can touch the keys * object, we could iterate its array part directly (keeping in mind * that it can be reallocated). */ arr_len = (duk_uarridx_t) duk_get_length(thr, idx_keys); emitted = 0; for (i = 0; i < arr_len; i++) { duk_get_prop_index(thr, idx_keys, i); /* -> [ ... key ] */ DUK_DDD(DUK_DDDPRINT("object property loop: holder=%!T, key=%!T", (duk_tval *) duk_get_tval(thr, idx_obj), (duk_tval *) duk_get_tval(thr, -1))); h_key = duk_known_hstring(thr, -1); DUK_ASSERT(h_key != NULL); DUK_ASSERT(!DUK_HSTRING_HAS_SYMBOL(h_key)); /* proplist filtering; enum options */ prev_size = DUK_BW_GET_SIZE(js_ctx->thr, &js_ctx->bw); if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { duk__enc_newline_indent(js_ctx, js_ctx->recursion_depth); duk__enc_key_autoquote(js_ctx, h_key); DUK__EMIT_2(js_ctx, DUK_ASC_COLON, DUK_ASC_SPACE); } else { duk__enc_key_autoquote(js_ctx, h_key); DUK__EMIT_1(js_ctx, DUK_ASC_COLON); } /* [ ... key ] */ if (DUK_UNLIKELY(duk__enc_value(js_ctx, idx_obj) == 0)) { /* Value would yield 'undefined', so skip key altogether. * Side effects have already happened. */ DUK_BW_SET_SIZE(js_ctx->thr, &js_ctx->bw, prev_size); } else { DUK__EMIT_1(js_ctx, DUK_ASC_COMMA); emitted = 1; } /* [ ... ] */ } if (emitted) { DUK_ASSERT(*((duk_uint8_t *) DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw) - 1) == DUK_ASC_COMMA); DUK__UNEMIT_1(js_ctx); /* eat trailing comma */ if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { DUK_ASSERT(js_ctx->recursion_depth >= 1); duk__enc_newline_indent(js_ctx, js_ctx->recursion_depth - 1U); } } DUK__EMIT_1(js_ctx, DUK_ASC_RCURLY); duk__enc_objarr_exit(js_ctx, &entry_top); DUK_ASSERT_TOP(thr, entry_top); } /* The JA(value) operation: encode array. * * Stack policy: [ array ] -> [ array ]. */ DUK_LOCAL void duk__enc_array(duk_json_enc_ctx *js_ctx) { duk_hthread *thr = js_ctx->thr; duk_idx_t entry_top; duk_idx_t idx_arr; duk_bool_t emitted; duk_uarridx_t i, arr_len; DUK_DDD(DUK_DDDPRINT("duk__enc_array: array=%!T", (duk_tval *) duk_get_tval(thr, -1))); duk__enc_objarr_entry(js_ctx, &entry_top); idx_arr = entry_top - 1; /* Steps 8-10 have been merged to avoid a "partial" variable. */ DUK__EMIT_1(js_ctx, DUK_ASC_LBRACKET); arr_len = (duk_uarridx_t) duk_get_length(thr, idx_arr); emitted = 0; for (i = 0; i < arr_len; i++) { DUK_DDD(DUK_DDDPRINT("array entry loop: array=%!T, index=%ld, arr_len=%ld", (duk_tval *) duk_get_tval(thr, idx_arr), (long) i, (long) arr_len)); if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { DUK_ASSERT(js_ctx->recursion_depth >= 1); duk__enc_newline_indent(js_ctx, js_ctx->recursion_depth); } (void) duk_push_uint_to_hstring(thr, (duk_uint_t) i); /* -> [ ... key ] */ /* [ ... key ] */ if (DUK_UNLIKELY(duk__enc_value(js_ctx, idx_arr) == 0)) { /* Value would normally be omitted, replace with 'null'. */ DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL); } else { ; } /* [ ... ] */ DUK__EMIT_1(js_ctx, DUK_ASC_COMMA); emitted = 1; } if (emitted) { DUK_ASSERT(*((duk_uint8_t *) DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw) - 1) == DUK_ASC_COMMA); DUK__UNEMIT_1(js_ctx); /* eat trailing comma */ if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { DUK_ASSERT(js_ctx->recursion_depth >= 1); duk__enc_newline_indent(js_ctx, js_ctx->recursion_depth - 1U); } } DUK__EMIT_1(js_ctx, DUK_ASC_RBRACKET); duk__enc_objarr_exit(js_ctx, &entry_top); DUK_ASSERT_TOP(thr, entry_top); } /* The Str(key, holder) operation. * * Stack policy: [ ... key ] -> [ ... ] */ DUK_LOCAL duk_bool_t duk__enc_value(duk_json_enc_ctx *js_ctx, duk_idx_t idx_holder) { duk_hthread *thr = js_ctx->thr; duk_tval *tv; duk_tval *tv_holder; duk_tval *tv_key; duk_small_int_t c; DUK_DDD(DUK_DDDPRINT("duk__enc_value: idx_holder=%ld, holder=%!T, key=%!T", (long) idx_holder, (duk_tval *) duk_get_tval(thr, idx_holder), (duk_tval *) duk_get_tval(thr, -1))); tv_holder = DUK_GET_TVAL_POSIDX(thr, idx_holder); DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_holder)); tv_key = DUK_GET_TVAL_NEGIDX(thr, -1); DUK_ASSERT(DUK_TVAL_IS_STRING(tv_key)); DUK_ASSERT(!DUK_HSTRING_HAS_SYMBOL(DUK_TVAL_GET_STRING(tv_key))); /* Caller responsible. */ (void) duk_hobject_getprop(thr, tv_holder, tv_key); /* -> [ ... key val ] */ DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(thr, -1))); /* Standard JSON checks for .toJSON() only for actual objects; for * example, setting Number.prototype.toJSON and then serializing a * number won't invoke the .toJSON() method. However, lightfuncs and * plain buffers mimic objects so we check for their .toJSON() method. */ if (duk_check_type_mask(thr, -1, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER)) { duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_TO_JSON); if (duk_is_callable(thr, -1)) { /* toJSON() can also be a lightfunc */ DUK_DDD(DUK_DDDPRINT("value is object, has callable toJSON() -> call it")); /* XXX: duk_dup_unvalidated(thr, -2) etc. */ duk_dup_m2(thr); /* -> [ ... key val toJSON val ] */ duk_dup_m4(thr); /* -> [ ... key val toJSON val key ] */ duk_call_method(thr, 1); /* -> [ ... key val val' ] */ duk_remove_m2(thr); /* -> [ ... key val' ] */ } else { duk_pop(thr); /* -> [ ... key val ] */ } } /* [ ... key val ] */ DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(thr, -1))); if (js_ctx->h_replacer) { /* XXX: Here a "slice copy" would be useful. */ DUK_DDD(DUK_DDDPRINT("replacer is set, call replacer")); duk_push_hobject(thr, js_ctx->h_replacer); /* -> [ ... key val replacer ] */ duk_dup(thr, idx_holder); /* -> [ ... key val replacer holder ] */ duk_dup_m4(thr); /* -> [ ... key val replacer holder key ] */ duk_dup_m4(thr); /* -> [ ... key val replacer holder key val ] */ duk_call_method(thr, 2); /* -> [ ... key val val' ] */ duk_remove_m2(thr); /* -> [ ... key val' ] */ } /* [ ... key val ] */ DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(thr, -1))); tv = DUK_GET_TVAL_NEGIDX(thr, -1); if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (DUK_HOBJECT_IS_BUFOBJ(h) && js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE)) { /* With JX/JC a bufferobject gets serialized specially. */ duk_hbufobj *h_bufobj; h_bufobj = (duk_hbufobj *) h; DUK_ASSERT_HBUFOBJ_VALID(h_bufobj); duk__enc_bufobj(js_ctx, h_bufobj); goto pop2_emitted; } /* Otherwise bufferobjects get serialized as normal objects. */ #endif /* JX || JC */ #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */ c = (duk_small_int_t) DUK_HOBJECT_GET_CLASS_NUMBER(h); switch (c) { case DUK_HOBJECT_CLASS_NUMBER: { DUK_DDD(DUK_DDDPRINT("value is a Number object -> coerce with ToNumber()")); duk_to_number_m1(thr); /* The coercion potentially invokes user .valueOf() and .toString() * but can't result in a function value because ToPrimitive() would * reject such a result: test-dev-json-stringify-coercion-1.js. */ DUK_ASSERT(!duk_is_callable(thr, -1)); break; } case DUK_HOBJECT_CLASS_STRING: { DUK_DDD(DUK_DDDPRINT("value is a String object -> coerce with ToString()")); duk_to_string(thr, -1); /* Same coercion behavior as for Number. */ DUK_ASSERT(!duk_is_callable(thr, -1)); break; } #if defined(DUK_USE_JX) || defined(DUK_USE_JC) case DUK_HOBJECT_CLASS_POINTER: #endif case DUK_HOBJECT_CLASS_BOOLEAN: { DUK_DDD(DUK_DDDPRINT("value is a Boolean/Buffer/Pointer object -> get internal value")); duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_INT_VALUE); duk_remove_m2(thr); break; } default: { /* Normal object which doesn't get automatically coerced to a * primitive value. Functions are checked for specially. The * primitive value coercions for Number, String, Pointer, and * Boolean can't result in functions so suffices to check here. * Symbol objects are handled like plain objects (their primitive * value is NOT looked up like for e.g. String objects). */ DUK_ASSERT(h != NULL); if (DUK_HOBJECT_IS_CALLABLE(h)) { #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE)) { /* We only get here when doing non-standard JSON encoding */ DUK_DDD(DUK_DDDPRINT("-> function allowed, serialize to custom format")); DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible); DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_function); goto pop2_emitted; } else { DUK_DDD(DUK_DDDPRINT("-> will result in undefined (function)")); goto pop2_undef; } #else /* DUK_USE_JX || DUK_USE_JC */ DUK_DDD(DUK_DDDPRINT("-> will result in undefined (function)")); goto pop2_undef; #endif /* DUK_USE_JX || DUK_USE_JC */ } } } /* end switch */ } /* [ ... key val ] */ DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(thr, -1))); if (duk_check_type_mask(thr, -1, js_ctx->mask_for_undefined)) { /* will result in undefined */ DUK_DDD(DUK_DDDPRINT("-> will result in undefined (type mask check)")); goto pop2_undef; } tv = DUK_GET_TVAL_NEGIDX(thr, -1); switch (DUK_TVAL_GET_TAG(tv)) { #if defined(DUK_USE_JX) || defined(DUK_USE_JC) /* When JX/JC not in use, the type mask above will avoid this case if needed. */ case DUK_TAG_UNDEFINED: { DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_undefined); break; } #endif case DUK_TAG_NULL: { DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL); break; } case DUK_TAG_BOOLEAN: { DUK__EMIT_STRIDX(js_ctx, DUK_TVAL_GET_BOOLEAN(tv) ? DUK_STRIDX_TRUE : DUK_STRIDX_FALSE); break; } #if defined(DUK_USE_JX) || defined(DUK_USE_JC) /* When JX/JC not in use, the type mask above will avoid this case if needed. */ case DUK_TAG_POINTER: { duk__enc_pointer(js_ctx, DUK_TVAL_GET_POINTER(tv)); break; } #endif /* DUK_USE_JX || DUK_USE_JC */ case DUK_TAG_STRING: { duk_hstring *h = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h != NULL); if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) { goto pop2_undef; } duk__enc_quote_string(js_ctx, h); break; } case DUK_TAG_OBJECT: { duk_hobject *h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); /* Function values are handled completely above (including * coercion results): */ DUK_ASSERT(!DUK_HOBJECT_IS_CALLABLE(h)); if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAY) { duk__enc_array(js_ctx); } else { duk__enc_object(js_ctx); } break; } /* Because plain buffers mimics Uint8Array, they have enumerable * index properties [0,byteLength[. Because JSON only serializes * enumerable own properties, no properties can be serialized for * plain buffers (all virtual properties are non-enumerable). However, * there may be a .toJSON() method which was already handled above. */ case DUK_TAG_BUFFER: { #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flag_ext_custom_or_compatible) { duk__enc_buffer_jx_jc(js_ctx, DUK_TVAL_GET_BUFFER(tv)); break; } #endif /* Could implement a fastpath, but the fast path would need * to handle realloc side effects correctly. */ duk_to_object(thr, -1); duk__enc_object(js_ctx); break; } case DUK_TAG_LIGHTFUNC: { #if defined(DUK_USE_JX) || defined(DUK_USE_JC) /* We only get here when doing non-standard JSON encoding */ DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible); DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_function); #else /* Standard JSON omits functions */ DUK_UNREACHABLE(); #endif break; } #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: /* Number serialization has a significant impact relative to * other fast path code, so careful fast path for fastints. */ duk__enc_fastint_tval(js_ctx, tv); break; #endif default: { /* number */ DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); /* XXX: A fast path for usual integers would be useful when * fastint support is not enabled. */ duk__enc_double(js_ctx); break; } } #if defined(DUK_USE_JX) || defined(DUK_USE_JC) pop2_emitted: #endif duk_pop_2(thr); /* [ ... key val ] -> [ ... ] */ return 1; /* emitted */ pop2_undef: duk_pop_2(thr); /* [ ... key val ] -> [ ... ] */ return 0; /* not emitted */ } /* E5 Section 15.12.3, main algorithm, step 4.b.ii steps 1-4. */ DUK_LOCAL duk_bool_t duk__enc_allow_into_proplist(duk_tval *tv) { duk_small_int_t c; /* XXX: some kind of external internal type checker? * - type mask; symbol flag; class mask */ DUK_ASSERT(tv != NULL); if (DUK_TVAL_IS_STRING(tv)) { duk_hstring *h; h = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h != NULL); if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) { return 0; } return 1; } else if (DUK_TVAL_IS_NUMBER(tv)) { return 1; } else if (DUK_TVAL_IS_OBJECT(tv)) { duk_hobject *h; h = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(h != NULL); c = (duk_small_int_t) DUK_HOBJECT_GET_CLASS_NUMBER(h); if (c == DUK_HOBJECT_CLASS_STRING || c == DUK_HOBJECT_CLASS_NUMBER) { return 1; } } return 0; } /* * JSON.stringify() fast path * * Otherwise supports full JSON, JX, and JC features, but bails out on any * possible side effect which might change the value being serialized. The * fast path can take advantage of the fact that the value being serialized * is unchanged so that we can walk directly through property tables etc. */ #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH) DUK_LOCAL duk_bool_t duk__json_stringify_fast_value(duk_json_enc_ctx *js_ctx, duk_tval *tv) { duk_uint_fast32_t i, n; DUK_DDD(DUK_DDDPRINT("stringify fast: %!T", tv)); DUK_ASSERT(js_ctx != NULL); DUK_ASSERT(js_ctx->thr != NULL); #if 0 /* disabled for now */ restart_match: #endif DUK_ASSERT(tv != NULL); switch (DUK_TVAL_GET_TAG(tv)) { case DUK_TAG_UNDEFINED: { #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible) { DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_undefined); break; } else { goto emit_undefined; } #else goto emit_undefined; #endif } case DUK_TAG_NULL: { DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL); break; } case DUK_TAG_BOOLEAN: { DUK__EMIT_STRIDX(js_ctx, DUK_TVAL_GET_BOOLEAN(tv) ? DUK_STRIDX_TRUE : DUK_STRIDX_FALSE); break; } case DUK_TAG_STRING: { duk_hstring *h; h = DUK_TVAL_GET_STRING(tv); DUK_ASSERT(h != NULL); if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) { goto emit_undefined; } duk__enc_quote_string(js_ctx, h); break; } case DUK_TAG_OBJECT: { duk_hobject *obj; duk_tval *tv_val; duk_bool_t emitted = 0; duk_uint32_t c_bit, c_all, c_array, c_unbox, c_undef, c_func, c_bufobj, c_object, c_abort; /* For objects JSON.stringify() only looks for own, enumerable * properties which is nice for the fast path here. * * For arrays JSON.stringify() uses [[Get]] so it will actually * inherit properties during serialization! This fast path * supports gappy arrays as long as there's no actual inherited * property (which might be a getter etc). * * Since recursion only happens for objects, we can have both * recursion and loop checks here. We use a simple, depth-limited * loop check in the fast path because the object-based tracking * is very slow (when tested, it accounted for 50% of fast path * execution time for input data with a lot of small objects!). */ /* XXX: for real world code, could just ignore array inheritance * and only look at array own properties. */ /* We rely on a few object flag / class number relationships here, * assert for them. */ obj = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(obj != NULL); DUK_ASSERT_HOBJECT_VALID(obj); /* Once recursion depth is increased, exit path must decrease * it (though it's OK to abort the fast path). */ DUK_ASSERT_DISABLE(js_ctx->recursion_depth >= 0); /* unsigned */ DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); if (js_ctx->recursion_depth >= js_ctx->recursion_limit) { DUK_DD(DUK_DDPRINT("fast path recursion limit")); DUK_ERROR_RANGE(js_ctx->thr, DUK_STR_JSONDEC_RECLIMIT); DUK_WO_NORETURN(return 0;); } for (i = 0, n = (duk_uint_fast32_t) js_ctx->recursion_depth; i < n; i++) { if (DUK_UNLIKELY(js_ctx->visiting[i] == obj)) { DUK_DD(DUK_DDPRINT("fast path loop detect")); DUK_ERROR_TYPE(js_ctx->thr, DUK_STR_CYCLIC_INPUT); DUK_WO_NORETURN(return 0;); } } /* Guaranteed by recursion_limit setup so we don't have to * check twice. */ DUK_ASSERT(js_ctx->recursion_depth < DUK_JSON_ENC_LOOPARRAY); js_ctx->visiting[js_ctx->recursion_depth] = obj; js_ctx->recursion_depth++; /* If object has a .toJSON() property, we can't be certain * that it wouldn't mutate any value arbitrarily, so bail * out of the fast path. * * If an object is a Proxy we also can't avoid side effects * so abandon. */ /* XXX: non-callable .toJSON() doesn't need to cause an abort * but does at the moment, probably not worth fixing. */ if (duk_hobject_hasprop_raw(js_ctx->thr, obj, DUK_HTHREAD_STRING_TO_JSON(js_ctx->thr)) || DUK_HOBJECT_IS_PROXY(obj)) { DUK_DD(DUK_DDPRINT("object has a .toJSON property or object is a Proxy, abort fast path")); goto abort_fastpath; } /* We could use a switch-case for the class number but it turns out * a small if-else ladder on class masks is better. The if-ladder * should be in order of relevancy. */ /* XXX: move masks to js_ctx? they don't change during one * fast path invocation. */ DUK_ASSERT(DUK_HOBJECT_CLASS_MAX <= 31); #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flag_ext_custom_or_compatible) { c_all = DUK_HOBJECT_CMASK_ALL; c_array = DUK_HOBJECT_CMASK_ARRAY; c_unbox = DUK_HOBJECT_CMASK_NUMBER | DUK_HOBJECT_CMASK_STRING | DUK_HOBJECT_CMASK_BOOLEAN | DUK_HOBJECT_CMASK_POINTER; /* Symbols are not unboxed. */ c_func = DUK_HOBJECT_CMASK_FUNCTION; c_bufobj = DUK_HOBJECT_CMASK_ALL_BUFOBJS; c_undef = 0; c_abort = 0; c_object = c_all & ~(c_array | c_unbox | c_func | c_bufobj | c_undef | c_abort); } else #endif { c_all = DUK_HOBJECT_CMASK_ALL; c_array = DUK_HOBJECT_CMASK_ARRAY; c_unbox = DUK_HOBJECT_CMASK_NUMBER | DUK_HOBJECT_CMASK_STRING | DUK_HOBJECT_CMASK_BOOLEAN; /* Symbols are not unboxed. */ c_func = 0; c_bufobj = 0; c_undef = DUK_HOBJECT_CMASK_FUNCTION | DUK_HOBJECT_CMASK_POINTER; /* As the fast path doesn't currently properly support * duk_hbufobj virtual properties, abort fast path if * we encounter them in plain JSON mode. */ c_abort = DUK_HOBJECT_CMASK_ALL_BUFOBJS; c_object = c_all & ~(c_array | c_unbox | c_func | c_bufobj | c_undef | c_abort); } c_bit = (duk_uint32_t) DUK_HOBJECT_GET_CLASS_MASK(obj); if (c_bit & c_object) { /* All other object types. */ DUK__EMIT_1(js_ctx, DUK_ASC_LCURLY); /* A non-Array object should not have an array part in practice. * But since it is supported internally (and perhaps used at some * point), check and abandon if that's the case. */ if (DUK_HOBJECT_HAS_ARRAY_PART(obj)) { DUK_DD(DUK_DDPRINT("non-Array object has array part, abort fast path")); goto abort_fastpath; } for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(obj); i++) { duk_hstring *k; duk_size_t prev_size; k = DUK_HOBJECT_E_GET_KEY(js_ctx->thr->heap, obj, i); if (!k) { continue; } if (DUK_HSTRING_HAS_ARRIDX(k)) { /* If an object has array index keys we would need * to sort them into the ES2015 enumeration order to * be consistent with the slow path. Abort the fast * path and handle in the slow path for now. */ DUK_DD(DUK_DDPRINT("property key is an array index, abort fast path")); goto abort_fastpath; } if (!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(js_ctx->thr->heap, obj, i)) { continue; } if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(js_ctx->thr->heap, obj, i)) { /* Getter might have arbitrary side effects, * so bail out. */ DUK_DD(DUK_DDPRINT("property is an accessor, abort fast path")); goto abort_fastpath; } if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(k))) { continue; } tv_val = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(js_ctx->thr->heap, obj, i); prev_size = DUK_BW_GET_SIZE(js_ctx->thr, &js_ctx->bw); if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { duk__enc_newline_indent(js_ctx, js_ctx->recursion_depth); duk__enc_key_autoquote(js_ctx, k); DUK__EMIT_2(js_ctx, DUK_ASC_COLON, DUK_ASC_SPACE); } else { duk__enc_key_autoquote(js_ctx, k); DUK__EMIT_1(js_ctx, DUK_ASC_COLON); } if (duk__json_stringify_fast_value(js_ctx, tv_val) == 0) { DUK_DD(DUK_DDPRINT("prop value not supported, rewind key and colon")); DUK_BW_SET_SIZE(js_ctx->thr, &js_ctx->bw, prev_size); } else { DUK__EMIT_1(js_ctx, DUK_ASC_COMMA); emitted = 1; } } /* If any non-Array value had enumerable virtual own * properties, they should be serialized here (actually, * before the explicit properties). Standard types don't. */ if (emitted) { DUK_ASSERT(*((duk_uint8_t *) DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw) - 1) == DUK_ASC_COMMA); DUK__UNEMIT_1(js_ctx); /* eat trailing comma */ if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { DUK_ASSERT(js_ctx->recursion_depth >= 1); duk__enc_newline_indent(js_ctx, js_ctx->recursion_depth - 1U); } } DUK__EMIT_1(js_ctx, DUK_ASC_RCURLY); } else if (c_bit & c_array) { duk_uint_fast32_t arr_len; duk_uint_fast32_t asize; DUK__EMIT_1(js_ctx, DUK_ASC_LBRACKET); /* Assume arrays are dense in the fast path. */ if (!DUK_HOBJECT_HAS_ARRAY_PART(obj)) { DUK_DD(DUK_DDPRINT("Array object is sparse, abort fast path")); goto abort_fastpath; } arr_len = (duk_uint_fast32_t) ((duk_harray *) obj)->length; asize = (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(obj); /* Array part may be larger than 'length'; if so, iterate * only up to array 'length'. Array part may also be smaller * than 'length' in some cases. */ for (i = 0; i < arr_len; i++) { duk_tval *tv_arrval; duk_hstring *h_tmp; duk_bool_t has_inherited; if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { duk__enc_newline_indent(js_ctx, js_ctx->recursion_depth); } if (DUK_LIKELY(i < asize)) { tv_arrval = DUK_HOBJECT_A_GET_VALUE_PTR(js_ctx->thr->heap, obj, i); if (DUK_LIKELY(!DUK_TVAL_IS_UNUSED(tv_arrval))) { /* Expected case: element is present. */ if (duk__json_stringify_fast_value(js_ctx, tv_arrval) == 0) { DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL); } goto elem_done; } } /* Gap in array; check for inherited property, * bail out if one exists. This should be enough * to support gappy arrays for all practical code. */ h_tmp = duk_push_uint_to_hstring(js_ctx->thr, (duk_uint_t) i); has_inherited = duk_hobject_hasprop_raw(js_ctx->thr, obj, h_tmp); duk_pop(js_ctx->thr); if (has_inherited) { DUK_D(DUK_DPRINT("gap in array, conflicting inherited property, abort fast path")); goto abort_fastpath; } /* Ordinary gap, undefined encodes to 'null' in * standard JSON, but JX/JC use their form for * undefined to better preserve the typing. */ DUK_D(DUK_DPRINT("gap in array, no conflicting inherited property, remain on fast path")); #if defined(DUK_USE_JX) DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_undefined); #else DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL); #endif /* fall through */ elem_done: DUK__EMIT_1(js_ctx, DUK_ASC_COMMA); emitted = 1; } if (emitted) { DUK_ASSERT(*((duk_uint8_t *) DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw) - 1) == DUK_ASC_COMMA); DUK__UNEMIT_1(js_ctx); /* eat trailing comma */ if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) { DUK_ASSERT(js_ctx->recursion_depth >= 1); duk__enc_newline_indent(js_ctx, js_ctx->recursion_depth - 1U); } } DUK__EMIT_1(js_ctx, DUK_ASC_RBRACKET); } else if (c_bit & c_unbox) { /* Certain boxed types are required to go through * automatic unboxing. Rely on internal value being * sane (to avoid infinite recursion). */ DUK_ASSERT((c_bit & DUK_HOBJECT_CMASK_SYMBOL) == 0); /* Symbols are not unboxed. */ #if 1 /* The code below is incorrect if .toString() or .valueOf() have * have been overridden. The correct approach would be to look up * the method(s) and if they resolve to the built-in function we * can safely bypass it and look up the internal value directly. * Unimplemented for now, abort fast path for boxed values. */ goto abort_fastpath; #else /* disabled */ /* Disabled until fixed, see above. */ duk_tval *tv_internal; DUK_DD(DUK_DDPRINT("auto unboxing in fast path")); tv_internal = duk_hobject_get_internal_value_tval_ptr(js_ctx->thr->heap, obj); DUK_ASSERT(tv_internal != NULL); DUK_ASSERT(DUK_TVAL_IS_STRING(tv_internal) || DUK_TVAL_IS_NUMBER(tv_internal) || DUK_TVAL_IS_BOOLEAN(tv_internal) || DUK_TVAL_IS_POINTER(tv_internal)); tv = tv_internal; DUK_ASSERT(js_ctx->recursion_depth > 0); js_ctx->recursion_depth--; /* required to keep recursion depth correct */ goto restart_match; #endif /* disabled */ #if defined(DUK_USE_JX) || defined(DUK_USE_JC) } else if (c_bit & c_func) { DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_function); #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) } else if (c_bit & c_bufobj) { duk__enc_bufobj(js_ctx, (duk_hbufobj *) obj); #endif #endif } else if (c_bit & c_abort) { DUK_DD(DUK_DDPRINT("abort fast path for unsupported type")); goto abort_fastpath; } else { DUK_ASSERT((c_bit & c_undef) != 0); /* Must decrease recursion depth before returning. */ DUK_ASSERT(js_ctx->recursion_depth > 0); DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); js_ctx->recursion_depth--; goto emit_undefined; } DUK_ASSERT(js_ctx->recursion_depth > 0); DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit); js_ctx->recursion_depth--; break; } case DUK_TAG_BUFFER: { /* Plain buffers are treated like Uint8Arrays: they have * enumerable indices. Other virtual properties are not * enumerable, and inherited properties are not serialized. * However, there can be a replacer (not relevant here) or * a .toJSON() method (which we need to check for explicitly). */ #if defined(DUK_USE_BUFFEROBJECT_SUPPORT) if (duk_hobject_hasprop_raw(js_ctx->thr, js_ctx->thr->builtins[DUK_BIDX_UINT8ARRAY_PROTOTYPE], DUK_HTHREAD_STRING_TO_JSON(js_ctx->thr))) { DUK_DD(DUK_DDPRINT("value is a plain buffer and there's an inherited .toJSON, abort fast path")); goto abort_fastpath; } #endif #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flag_ext_custom_or_compatible) { duk__enc_buffer_jx_jc(js_ctx, DUK_TVAL_GET_BUFFER(tv)); break; } #endif /* Plain buffers mimic Uint8Arrays, and have enumerable index * properties. */ duk__enc_buffer_json_fastpath(js_ctx, DUK_TVAL_GET_BUFFER(tv)); break; } case DUK_TAG_POINTER: { #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flag_ext_custom_or_compatible) { duk__enc_pointer(js_ctx, DUK_TVAL_GET_POINTER(tv)); break; } else { goto emit_undefined; } #else goto emit_undefined; #endif } case DUK_TAG_LIGHTFUNC: { /* A lightfunc might also inherit a .toJSON() so just bail out. */ /* XXX: Could just lookup .toJSON() and continue in fast path, * as it would almost never be defined. */ DUK_DD(DUK_DDPRINT("value is a lightfunc, abort fast path")); goto abort_fastpath; } #if defined(DUK_USE_FASTINT) case DUK_TAG_FASTINT: { /* Number serialization has a significant impact relative to * other fast path code, so careful fast path for fastints. */ duk__enc_fastint_tval(js_ctx, tv); break; } #endif default: { /* XXX: A fast path for usual integers would be useful when * fastint support is not enabled. */ DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv)); /* XXX: Stack discipline is annoying, could be changed in numconv. */ duk_push_tval(js_ctx->thr, tv); duk__enc_double(js_ctx); duk_pop(js_ctx->thr); #if 0 /* Could also rely on native sprintf(), but it will handle * values like NaN, Infinity, -0, exponent notation etc in * a JSON-incompatible way. */ duk_double_t d; char buf[64]; DUK_ASSERT(DUK_TVAL_IS_DOUBLE(tv)); d = DUK_TVAL_GET_DOUBLE(tv); DUK_SPRINTF(buf, "%lg", d); DUK__EMIT_CSTR(js_ctx, buf); #endif } } return 1; /* not undefined */ emit_undefined: return 0; /* value was undefined/unsupported */ abort_fastpath: /* Error message doesn't matter: the error is ignored anyway. */ DUK_DD(DUK_DDPRINT("aborting fast path")); DUK_ERROR_INTERNAL(js_ctx->thr); DUK_WO_NORETURN(return 0;); } DUK_LOCAL duk_ret_t duk__json_stringify_fast(duk_hthread *thr, void *udata) { duk_json_enc_ctx *js_ctx; duk_tval *tv; DUK_ASSERT(thr != NULL); DUK_ASSERT(udata != NULL); js_ctx = (duk_json_enc_ctx *) udata; DUK_ASSERT(js_ctx != NULL); tv = DUK_GET_TVAL_NEGIDX(thr, -1); if (duk__json_stringify_fast_value(js_ctx, tv) == 0) { DUK_DD(DUK_DDPRINT("top level value not supported, fail fast path")); DUK_DCERROR_TYPE_INVALID_ARGS(thr); /* Error message is ignored, so doesn't matter. */ } return 0; } #endif /* DUK_USE_JSON_STRINGIFY_FASTPATH */ /* * Top level wrappers */ DUK_INTERNAL void duk_bi_json_parse_helper(duk_hthread *thr, duk_idx_t idx_value, duk_idx_t idx_reviver, duk_small_uint_t flags) { duk_json_dec_ctx js_ctx_alloc; duk_json_dec_ctx *js_ctx = &js_ctx_alloc; duk_hstring *h_text; #if defined(DUK_USE_ASSERTIONS) duk_idx_t entry_top = duk_get_top(thr); #endif /* negative top-relative indices not allowed now */ DUK_ASSERT(idx_value == DUK_INVALID_INDEX || idx_value >= 0); DUK_ASSERT(idx_reviver == DUK_INVALID_INDEX || idx_reviver >= 0); DUK_DDD(DUK_DDDPRINT("JSON parse start: text=%!T, reviver=%!T, flags=0x%08lx, stack_top=%ld", (duk_tval *) duk_get_tval(thr, idx_value), (duk_tval *) duk_get_tval(thr, idx_reviver), (unsigned long) flags, (long) duk_get_top(thr))); duk_memzero(&js_ctx_alloc, sizeof(js_ctx_alloc)); js_ctx->thr = thr; #if defined(DUK_USE_EXPLICIT_NULL_INIT) /* nothing now */ #endif js_ctx->recursion_limit = DUK_USE_JSON_DEC_RECLIMIT; DUK_ASSERT(js_ctx->recursion_depth == 0); /* Flag handling currently assumes that flags are consistent. This is OK * because the call sites are now strictly controlled. */ js_ctx->flags = flags; #if defined(DUK_USE_JX) js_ctx->flag_ext_custom = flags & DUK_JSON_FLAG_EXT_CUSTOM; #endif #if defined(DUK_USE_JC) js_ctx->flag_ext_compatible = flags & DUK_JSON_FLAG_EXT_COMPATIBLE; #endif #if defined(DUK_USE_JX) || defined(DUK_USE_JC) js_ctx->flag_ext_custom_or_compatible = flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE); #endif h_text = duk_to_hstring(thr, idx_value); /* coerce in-place; rejects Symbols */ DUK_ASSERT(h_text != NULL); /* JSON parsing code is allowed to read [p_start,p_end]: p_end is * valid and points to the string NUL terminator (which is always * guaranteed for duk_hstrings. */ js_ctx->p_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_text); js_ctx->p = js_ctx->p_start; js_ctx->p_end = ((const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_text)) + DUK_HSTRING_GET_BYTELEN(h_text); DUK_ASSERT(*(js_ctx->p_end) == 0x00); duk__dec_value(js_ctx); /* -> [ ... value ] */ /* Trailing whitespace has been eaten by duk__dec_value(), so if * we're not at end of input here, it's a SyntaxError. */ if (js_ctx->p != js_ctx->p_end) { duk__dec_syntax_error(js_ctx); } if (duk_is_callable(thr, idx_reviver)) { DUK_DDD(DUK_DDDPRINT("applying reviver: %!T", (duk_tval *) duk_get_tval(thr, idx_reviver))); js_ctx->idx_reviver = idx_reviver; duk_push_object(thr); duk_dup_m2(thr); /* -> [ ... val root val ] */ duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_EMPTY_STRING); /* default attrs ok */ duk_push_hstring_stridx(thr, DUK_STRIDX_EMPTY_STRING); /* -> [ ... val root "" ] */ DUK_DDD(DUK_DDDPRINT("start reviver walk, root=%!T, name=%!T", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); duk__dec_reviver_walk(js_ctx); /* [ ... val root "" ] -> [ ... val val' ] */ duk_remove_m2(thr); /* -> [ ... val' ] */ } else { DUK_DDD(DUK_DDDPRINT("reviver does not exist or is not callable: %!T", (duk_tval *) duk_get_tval(thr, idx_reviver))); } /* Final result is at stack top. */ DUK_DDD(DUK_DDDPRINT("JSON parse end: text=%!T, reviver=%!T, flags=0x%08lx, result=%!T, stack_top=%ld", (duk_tval *) duk_get_tval(thr, idx_value), (duk_tval *) duk_get_tval(thr, idx_reviver), (unsigned long) flags, (duk_tval *) duk_get_tval(thr, -1), (long) duk_get_top(thr))); DUK_ASSERT(duk_get_top(thr) == entry_top + 1); } DUK_INTERNAL void duk_bi_json_stringify_helper(duk_hthread *thr, duk_idx_t idx_value, duk_idx_t idx_replacer, duk_idx_t idx_space, duk_small_uint_t flags) { duk_json_enc_ctx js_ctx_alloc; duk_json_enc_ctx *js_ctx = &js_ctx_alloc; duk_hobject *h; duk_idx_t idx_holder; duk_idx_t entry_top; /* negative top-relative indices not allowed now */ DUK_ASSERT(idx_value == DUK_INVALID_INDEX || idx_value >= 0); DUK_ASSERT(idx_replacer == DUK_INVALID_INDEX || idx_replacer >= 0); DUK_ASSERT(idx_space == DUK_INVALID_INDEX || idx_space >= 0); DUK_DDD(DUK_DDDPRINT("JSON stringify start: value=%!T, replacer=%!T, space=%!T, flags=0x%08lx, stack_top=%ld", (duk_tval *) duk_get_tval(thr, idx_value), (duk_tval *) duk_get_tval(thr, idx_replacer), (duk_tval *) duk_get_tval(thr, idx_space), (unsigned long) flags, (long) duk_get_top(thr))); entry_top = duk_get_top(thr); /* * Context init */ duk_memzero(&js_ctx_alloc, sizeof(js_ctx_alloc)); js_ctx->thr = thr; #if defined(DUK_USE_EXPLICIT_NULL_INIT) js_ctx->h_replacer = NULL; js_ctx->h_gap = NULL; #endif js_ctx->idx_proplist = -1; /* Flag handling currently assumes that flags are consistent. This is OK * because the call sites are now strictly controlled. */ js_ctx->flags = flags; js_ctx->flag_ascii_only = flags & DUK_JSON_FLAG_ASCII_ONLY; js_ctx->flag_avoid_key_quotes = flags & DUK_JSON_FLAG_AVOID_KEY_QUOTES; #if defined(DUK_USE_JX) js_ctx->flag_ext_custom = flags & DUK_JSON_FLAG_EXT_CUSTOM; #endif #if defined(DUK_USE_JC) js_ctx->flag_ext_compatible = flags & DUK_JSON_FLAG_EXT_COMPATIBLE; #endif #if defined(DUK_USE_JX) || defined(DUK_USE_JC) js_ctx->flag_ext_custom_or_compatible = flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE); #endif /* The #if defined() clutter here handles the JX/JC enable/disable * combinations properly. */ #if defined(DUK_USE_JX) || defined(DUK_USE_JC) js_ctx->stridx_custom_undefined = DUK_STRIDX_LC_NULL; /* standard JSON; array gaps */ #if defined(DUK_USE_JX) if (flags & DUK_JSON_FLAG_EXT_CUSTOM) { js_ctx->stridx_custom_undefined = DUK_STRIDX_LC_UNDEFINED; js_ctx->stridx_custom_nan = DUK_STRIDX_NAN; js_ctx->stridx_custom_neginf = DUK_STRIDX_MINUS_INFINITY; js_ctx->stridx_custom_posinf = DUK_STRIDX_INFINITY; js_ctx->stridx_custom_function = (flags & DUK_JSON_FLAG_AVOID_KEY_QUOTES) ? DUK_STRIDX_JSON_EXT_FUNCTION2 : DUK_STRIDX_JSON_EXT_FUNCTION1; } #endif /* DUK_USE_JX */ #if defined(DUK_USE_JX) && defined(DUK_USE_JC) else #endif /* DUK_USE_JX && DUK_USE_JC */ #if defined(DUK_USE_JC) if (js_ctx->flags & DUK_JSON_FLAG_EXT_COMPATIBLE) { js_ctx->stridx_custom_undefined = DUK_STRIDX_JSON_EXT_UNDEFINED; js_ctx->stridx_custom_nan = DUK_STRIDX_JSON_EXT_NAN; js_ctx->stridx_custom_neginf = DUK_STRIDX_JSON_EXT_NEGINF; js_ctx->stridx_custom_posinf = DUK_STRIDX_JSON_EXT_POSINF; js_ctx->stridx_custom_function = DUK_STRIDX_JSON_EXT_FUNCTION1; } #endif /* DUK_USE_JC */ #endif /* DUK_USE_JX || DUK_USE_JC */ #if defined(DUK_USE_JX) || defined(DUK_USE_JC) if (js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE)) { DUK_ASSERT(js_ctx->mask_for_undefined == 0); /* already zero */ } else #endif /* DUK_USE_JX || DUK_USE_JC */ { /* Plain buffer is treated like ArrayBuffer and serialized. * Lightfuncs are treated like objects, but JSON explicitly * skips serializing Function objects so we can just reject * lightfuncs here. */ js_ctx->mask_for_undefined = DUK_TYPE_MASK_UNDEFINED | DUK_TYPE_MASK_POINTER | DUK_TYPE_MASK_LIGHTFUNC; } DUK_BW_INIT_PUSHBUF(thr, &js_ctx->bw, DUK__JSON_STRINGIFY_BUFSIZE); js_ctx->idx_loop = duk_push_bare_object(thr); DUK_ASSERT(js_ctx->idx_loop >= 0); /* [ ... buf loop ] */ /* * Process replacer/proplist (2nd argument to JSON.stringify) */ h = duk_get_hobject(thr, idx_replacer); if (h != NULL) { if (DUK_HOBJECT_IS_CALLABLE(h)) { js_ctx->h_replacer = h; } else if (DUK_HOBJECT_GET_CLASS_NUMBER(h) == DUK_HOBJECT_CLASS_ARRAY) { /* Here the specification requires correct array index enumeration * which is a bit tricky for sparse arrays (it is handled by the * enum setup code). We now enumerate ancestors too, although the * specification is not very clear on whether that is required. */ duk_uarridx_t plist_idx = 0; duk_small_uint_t enum_flags; js_ctx->idx_proplist = duk_push_array(thr); /* XXX: array internal? */ enum_flags = DUK_ENUM_ARRAY_INDICES_ONLY | DUK_ENUM_SORT_ARRAY_INDICES; /* expensive flag */ duk_enum(thr, idx_replacer, enum_flags); while (duk_next(thr, -1 /*enum_index*/, 1 /*get_value*/)) { /* [ ... proplist enum_obj key val ] */ if (duk__enc_allow_into_proplist(duk_get_tval(thr, -1))) { /* XXX: duplicates should be eliminated here */ DUK_DDD(DUK_DDDPRINT("proplist enum: key=%!T, val=%!T --> accept", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); duk_to_string(thr, -1); /* extra coercion of strings is OK */ duk_put_prop_index(thr, -4, plist_idx); /* -> [ ... proplist enum_obj key ] */ plist_idx++; duk_pop(thr); } else { DUK_DDD(DUK_DDDPRINT("proplist enum: key=%!T, val=%!T --> reject", (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1))); duk_pop_2(thr); } } duk_pop(thr); /* pop enum */ /* [ ... proplist ] */ } } /* [ ... buf loop (proplist) ] */ /* * Process space (3rd argument to JSON.stringify) */ h = duk_get_hobject(thr, idx_space); if (h != NULL) { duk_small_uint_t c = DUK_HOBJECT_GET_CLASS_NUMBER(h); if (c == DUK_HOBJECT_CLASS_NUMBER) { duk_to_number(thr, idx_space); } else if (c == DUK_HOBJECT_CLASS_STRING) { duk_to_string(thr, idx_space); } } if (duk_is_number(thr, idx_space)) { duk_small_int_t nspace; /* spaces[] must be static to allow initializer with old compilers like BCC */ static const char spaces[10] = { DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE }; /* XXX: helper */ /* ToInteger() coercion; NaN -> 0, infinities are clamped to 0 and 10 */ nspace = (duk_small_int_t) duk_to_int_clamped(thr, idx_space, 0 /*minval*/, 10 /*maxval*/); DUK_ASSERT(nspace >= 0 && nspace <= 10); duk_push_lstring(thr, spaces, (duk_size_t) nspace); js_ctx->h_gap = duk_known_hstring(thr, -1); DUK_ASSERT(js_ctx->h_gap != NULL); } else if (duk_is_string_notsymbol(thr, idx_space)) { duk_dup(thr, idx_space); duk_substring(thr, -1, 0, 10); /* clamp to 10 chars */ js_ctx->h_gap = duk_known_hstring(thr, -1); } else { /* nop */ } if (js_ctx->h_gap != NULL) { /* If gap is empty, behave as if not given at all. Check * against byte length because character length is more * expensive. */ if (DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap) == 0) { js_ctx->h_gap = NULL; } } /* [ ... buf loop (proplist) (gap) ] */ /* * Fast path: assume no mutation, iterate object property tables * directly; bail out if that assumption doesn't hold. */ #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH) if (js_ctx->h_replacer == NULL && /* replacer is a mutation risk */ js_ctx->idx_proplist == -1) { /* proplist is very rare */ duk_int_t pcall_rc; duk_small_uint_t prev_ms_base_flags; DUK_DD(DUK_DDPRINT("try JSON.stringify() fast path")); /* Use recursion_limit to ensure we don't overwrite js_ctx->visiting[] * array so we don't need two counter checks in the fast path. The * slow path has a much larger recursion limit which we'll use if * necessary. */ DUK_ASSERT(DUK_USE_JSON_ENC_RECLIMIT >= DUK_JSON_ENC_LOOPARRAY); js_ctx->recursion_limit = DUK_JSON_ENC_LOOPARRAY; DUK_ASSERT(js_ctx->recursion_depth == 0); /* Execute the fast path in a protected call. If any error is thrown, * fall back to the slow path. This includes e.g. recursion limit * because the fast path has a smaller recursion limit (and simpler, * limited loop detection). */ duk_dup(thr, idx_value); /* Must prevent finalizers which may have arbitrary side effects. */ prev_ms_base_flags = thr->heap->ms_base_flags; thr->heap->ms_base_flags |= DUK_MS_FLAG_NO_OBJECT_COMPACTION; /* Avoid attempt to compact any objects. */ thr->heap->pf_prevent_count++; /* Prevent finalizers. */ DUK_ASSERT(thr->heap->pf_prevent_count != 0); /* Wrap. */ pcall_rc = duk_safe_call(thr, duk__json_stringify_fast, (void *) js_ctx /*udata*/, 1 /*nargs*/, 0 /*nret*/); DUK_ASSERT(thr->heap->pf_prevent_count > 0); thr->heap->pf_prevent_count--; thr->heap->ms_base_flags = prev_ms_base_flags; if (pcall_rc == DUK_EXEC_SUCCESS) { DUK_DD(DUK_DDPRINT("fast path successful")); DUK_BW_PUSH_AS_STRING(thr, &js_ctx->bw); goto replace_finished; } /* We come here for actual aborts (like encountering .toJSON()) * but also for recursion/loop errors. Bufwriter size can be * kept because we'll probably need at least as much as we've * allocated so far. */ DUK_D(DUK_DPRINT("fast path failed, serialize using slow path instead")); DUK_BW_RESET_SIZE(thr, &js_ctx->bw); js_ctx->recursion_depth = 0; } #endif /* * Create wrapper object and serialize */ idx_holder = duk_push_object(thr); duk_dup(thr, idx_value); duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_EMPTY_STRING); DUK_DDD(DUK_DDDPRINT("before: flags=0x%08lx, loop=%!T, replacer=%!O, " "proplist=%!T, gap=%!O, holder=%!T", (unsigned long) js_ctx->flags, (duk_tval *) duk_get_tval(thr, js_ctx->idx_loop), (duk_heaphdr *) js_ctx->h_replacer, (duk_tval *) (js_ctx->idx_proplist >= 0 ? duk_get_tval(thr, js_ctx->idx_proplist) : NULL), (duk_heaphdr *) js_ctx->h_gap, (duk_tval *) duk_get_tval(thr, -1))); /* serialize the wrapper with empty string key */ duk_push_hstring_empty(thr); /* [ ... buf loop (proplist) (gap) holder "" ] */ js_ctx->recursion_limit = DUK_USE_JSON_ENC_RECLIMIT; DUK_ASSERT(js_ctx->recursion_depth == 0); if (DUK_UNLIKELY(duk__enc_value(js_ctx, idx_holder) == 0)) { /* [ ... holder key ] -> [ ... holder ] */ /* Result is undefined. */ duk_push_undefined(thr); } else { /* Convert buffer to result string. */ DUK_BW_PUSH_AS_STRING(thr, &js_ctx->bw); } DUK_DDD(DUK_DDDPRINT("after: flags=0x%08lx, loop=%!T, replacer=%!O, " "proplist=%!T, gap=%!O, holder=%!T", (unsigned long) js_ctx->flags, (duk_tval *) duk_get_tval(thr, js_ctx->idx_loop), (duk_heaphdr *) js_ctx->h_replacer, (duk_tval *) (js_ctx->idx_proplist >= 0 ? duk_get_tval(thr, js_ctx->idx_proplist) : NULL), (duk_heaphdr *) js_ctx->h_gap, (duk_tval *) duk_get_tval(thr, idx_holder))); /* The stack has a variable shape here, so force it to the * desired one explicitly. */ #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH) replace_finished: #endif duk_replace(thr, entry_top); duk_set_top(thr, entry_top + 1); DUK_DDD(DUK_DDDPRINT("JSON stringify end: value=%!T, replacer=%!T, space=%!T, " "flags=0x%08lx, result=%!T, stack_top=%ld", (duk_tval *) duk_get_tval(thr, idx_value), (duk_tval *) duk_get_tval(thr, idx_replacer), (duk_tval *) duk_get_tval(thr, idx_space), (unsigned long) flags, (duk_tval *) duk_get_tval(thr, -1), (long) duk_get_top(thr))); DUK_ASSERT(duk_get_top(thr) == entry_top + 1); } #if defined(DUK_USE_JSON_BUILTIN) /* * Entry points */ DUK_INTERNAL duk_ret_t duk_bi_json_object_parse(duk_hthread *thr) { duk_bi_json_parse_helper(thr, 0 /*idx_value*/, 1 /*idx_replacer*/, 0 /*flags*/); return 1; } DUK_INTERNAL duk_ret_t duk_bi_json_object_stringify(duk_hthread *thr) { duk_bi_json_stringify_helper(thr, 0 /*idx_value*/, 1 /*idx_replacer*/, 2 /*idx_space*/, 0 /*flags*/); return 1; } #endif /* DUK_USE_JSON_BUILTIN */ #endif /* DUK_USE_JSON_SUPPORT */