1 /*
2 * JSON built-ins.
3 *
4 * See doc/json.rst.
5 *
6 * Codepoints are handled as duk_uint_fast32_t to ensure that the full
7 * unsigned 32-bit range is supported. This matters to e.g. JX.
8 *
9 * Input parsing doesn't do an explicit end-of-input check at all. This is
10 * safe: input string data is always NUL-terminated (0x00) and valid JSON
11 * inputs never contain plain NUL characters, so that as long as syntax checks
12 * are correct, we'll never read past the NUL. This approach reduces code size
13 * and improves parsing performance, but it's critical that syntax checks are
14 * indeed correct!
15 */
16
17 #include "duk_internal.h"
18
19 #if defined(DUK_USE_JSON_SUPPORT)
20
21 /*
22 * Local defines and forward declarations.
23 */
24
25 #define DUK__JSON_DECSTR_BUFSIZE 128
26 #define DUK__JSON_DECSTR_CHUNKSIZE 64
27 #define DUK__JSON_ENCSTR_CHUNKSIZE 64
28 #define DUK__JSON_STRINGIFY_BUFSIZE 128
29 #define DUK__JSON_MAX_ESC_LEN 10 /* '\Udeadbeef' */
30
31 DUK_LOCAL_DECL void duk__json_dec_syntax_error(duk_json_dec_ctx *js_ctx);
32 DUK_LOCAL_DECL void duk__json_dec_eat_white(duk_json_dec_ctx *js_ctx);
33 #if defined(DUK_USE_JX)
34 DUK_LOCAL_DECL duk_uint8_t duk__json_dec_peek(duk_json_dec_ctx *js_ctx);
35 #endif
36 DUK_LOCAL_DECL duk_uint8_t duk__json_dec_get(duk_json_dec_ctx *js_ctx);
37 DUK_LOCAL_DECL duk_uint8_t duk__json_dec_get_nonwhite(duk_json_dec_ctx *js_ctx);
38 DUK_LOCAL_DECL duk_uint_fast32_t duk__json_dec_decode_hex_escape(duk_json_dec_ctx *js_ctx, duk_small_uint_t n);
39 DUK_LOCAL_DECL void duk__json_dec_req_stridx(duk_json_dec_ctx *js_ctx, duk_small_uint_t stridx);
40 DUK_LOCAL_DECL void duk__json_dec_string(duk_json_dec_ctx *js_ctx);
41 #if defined(DUK_USE_JX)
42 DUK_LOCAL_DECL void duk__json_dec_plain_string(duk_json_dec_ctx *js_ctx);
43 DUK_LOCAL_DECL void duk__json_dec_pointer(duk_json_dec_ctx *js_ctx);
44 DUK_LOCAL_DECL void duk__json_dec_buffer(duk_json_dec_ctx *js_ctx);
45 #endif
46 DUK_LOCAL_DECL void duk__json_dec_number(duk_json_dec_ctx *js_ctx);
47 DUK_LOCAL_DECL void duk__json_dec_objarr_entry(duk_json_dec_ctx *js_ctx);
48 DUK_LOCAL_DECL void duk__json_dec_objarr_exit(duk_json_dec_ctx *js_ctx);
49 DUK_LOCAL_DECL void duk__json_dec_object(duk_json_dec_ctx *js_ctx);
50 DUK_LOCAL_DECL void duk__json_dec_array(duk_json_dec_ctx *js_ctx);
51 DUK_LOCAL_DECL void duk__json_dec_value(duk_json_dec_ctx *js_ctx);
52 DUK_LOCAL_DECL void duk__json_dec_reviver_walk(duk_json_dec_ctx *js_ctx);
53
54 DUK_LOCAL_DECL void duk__emit_1(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch);
55 DUK_LOCAL_DECL void duk__emit_2(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch1, duk_uint_fast8_t ch2);
56 DUK_LOCAL_DECL void duk__unemit_1(duk_json_enc_ctx *js_ctx);
57 DUK_LOCAL_DECL void duk__emit_hstring(duk_json_enc_ctx *js_ctx, duk_hstring *h);
58 #if defined(DUK_USE_FASTINT)
59 DUK_LOCAL_DECL void duk__emit_cstring(duk_json_enc_ctx *js_ctx, const char *p);
60 #endif
61 DUK_LOCAL_DECL void duk__emit_stridx(duk_json_enc_ctx *js_ctx, duk_small_uint_t stridx);
62 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);
63 DUK_LOCAL_DECL void duk__json_enc_key_autoquote(duk_json_enc_ctx *js_ctx, duk_hstring *k);
64 DUK_LOCAL_DECL void duk__json_enc_quote_string(duk_json_enc_ctx *js_ctx, duk_hstring *h_str);
65 DUK_LOCAL_DECL void duk__json_enc_objarr_entry(duk_json_enc_ctx *js_ctx, duk_idx_t *entry_top);
66 DUK_LOCAL_DECL void duk__json_enc_objarr_exit(duk_json_enc_ctx *js_ctx, duk_idx_t *entry_top);
67 DUK_LOCAL_DECL void duk__json_enc_object(duk_json_enc_ctx *js_ctx);
68 DUK_LOCAL_DECL void duk__json_enc_array(duk_json_enc_ctx *js_ctx);
69 DUK_LOCAL_DECL duk_bool_t duk__json_enc_value(duk_json_enc_ctx *js_ctx, duk_idx_t idx_holder);
70 DUK_LOCAL_DECL duk_bool_t duk__json_enc_allow_into_proplist(duk_tval *tv);
71 DUK_LOCAL_DECL void duk__json_enc_double(duk_json_enc_ctx *js_ctx);
72 #if defined(DUK_USE_FASTINT)
73 DUK_LOCAL_DECL void duk__json_enc_fastint_tval(duk_json_enc_ctx *js_ctx, duk_tval *tv);
74 #endif
75 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
76 DUK_LOCAL_DECL void duk__json_enc_buffer_jx_jc(duk_json_enc_ctx *js_ctx, duk_hbuffer *h);
77 DUK_LOCAL_DECL void duk__json_enc_pointer(duk_json_enc_ctx *js_ctx, void *ptr);
78 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
79 DUK_LOCAL_DECL void duk__json_enc_bufobj(duk_json_enc_ctx *js_ctx, duk_hbufobj *h_bufobj);
80 #endif
81 #endif
82 #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH)
83 DUK_LOCAL_DECL void duk__json_enc_buffer_json_fastpath(duk_json_enc_ctx *js_ctx, duk_hbuffer *h);
84 #endif
85 DUK_LOCAL_DECL void duk__json_enc_newline_indent(duk_json_enc_ctx *js_ctx, duk_uint_t depth);
86
87 /*
88 * Helper tables
89 */
90
91 #if defined(DUK_USE_JSON_QUOTESTRING_FASTPATH)
92 DUK_LOCAL const duk_uint8_t duk__json_quotestr_lookup[256] = {
93 /* 0x00 ... 0x7f: as is
94 * 0x80: escape generically
95 * 0x81: slow path
96 * 0xa0 ... 0xff: backslash + one char
97 */
98
99 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xe2, 0xf4, 0xee, 0x80, 0xe6, 0xf2, 0x80, 0x80,
100 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
101 0x20, 0x21, 0xa2, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
102 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
103 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
104 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0xdc, 0x5d, 0x5e, 0x5f,
105 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
106 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x81,
107 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
108 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
109 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
110 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
111 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
112 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
113 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
114 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81
115 };
116 #else /* DUK_USE_JSON_QUOTESTRING_FASTPATH */
117 DUK_LOCAL const duk_uint8_t duk__json_quotestr_esc[14] = {
118 DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL,
119 DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL, DUK_ASC_NUL,
120 DUK_ASC_LC_B, DUK_ASC_LC_T, DUK_ASC_LC_N, DUK_ASC_NUL,
121 DUK_ASC_LC_F, DUK_ASC_LC_R
122 };
123 #endif /* DUK_USE_JSON_QUOTESTRING_FASTPATH */
124
125 #if defined(DUK_USE_JSON_DECSTRING_FASTPATH)
126 DUK_LOCAL const duk_uint8_t duk__json_decstr_lookup[256] = {
127 /* 0x00: slow path
128 * other: as is
129 */
130 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
131 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
132 0x20, 0x21, 0x00, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
133 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
134 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
135 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x00, 0x5d, 0x5e, 0x5f,
136 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
137 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
138 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
139 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
140 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
141 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
142 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
143 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
144 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
145 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
146 };
147 #endif /* DUK_USE_JSON_DECSTRING_FASTPATH */
148
149 #if defined(DUK_USE_JSON_EATWHITE_FASTPATH)
150 DUK_LOCAL const duk_uint8_t duk__json_eatwhite_lookup[256] = {
151 /* 0x00: finish (non-white)
152 * 0x01: continue
153 */
154 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00,
155 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
156 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
157 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
158 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
159 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
160 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
161 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
162 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
163 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
164 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
165 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
166 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
167 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
168 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
169 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
170 };
171 #endif /* DUK_USE_JSON_EATWHITE_FASTPATH */
172
173 #if defined(DUK_USE_JSON_DECNUMBER_FASTPATH)
174 DUK_LOCAL const duk_uint8_t duk__json_decnumber_lookup[256] = {
175 /* 0x00: finish (not part of number)
176 * 0x01: continue
177 */
178 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
179 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
180 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00,
181 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
182 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
183 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
184 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
185 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
186 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
187 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
188 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
189 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
190 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
191 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
192 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
193 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
194 };
195 #endif /* DUK_USE_JSON_DECNUMBER_FASTPATH */
196
197 /*
198 * Parsing implementation.
199 *
200 * JSON lexer is now separate from duk_lexer.c because there are numerous
201 * small differences making it difficult to share the lexer.
202 *
203 * The parser here works with raw bytes directly; this works because all
204 * JSON delimiters are ASCII characters. Invalid xUTF-8 encoded values
205 * inside strings will be passed on without normalization; this is not a
206 * compliance concern because compliant inputs will always be valid
207 * CESU-8 encodings.
208 */
209
duk__json_dec_syntax_error(duk_json_dec_ctx * js_ctx)210 DUK_LOCAL void duk__json_dec_syntax_error(duk_json_dec_ctx *js_ctx) {
211 /* Shared handler to minimize parser size. Cause will be
212 * hidden, unfortunately, but we'll have an offset which
213 * is often quite enough.
214 */
215 DUK_ERROR_FMT1(js_ctx->thr, DUK_ERR_SYNTAX_ERROR, DUK_STR_FMT_INVALID_JSON,
216 (long) (js_ctx->p - js_ctx->p_start));
217 DUK_WO_NORETURN(return;);
218 }
219
duk__json_dec_eat_white(duk_json_dec_ctx * js_ctx)220 DUK_LOCAL void duk__json_dec_eat_white(duk_json_dec_ctx *js_ctx) {
221 const duk_uint8_t *p;
222 duk_uint8_t t;
223
224 p = js_ctx->p;
225 for (;;) {
226 DUK_ASSERT(p <= js_ctx->p_end);
227 t = *p;
228
229 #if defined(DUK_USE_JSON_EATWHITE_FASTPATH)
230 /* This fast path is pretty marginal in practice.
231 * XXX: candidate for removal.
232 */
233 DUK_ASSERT(duk__json_eatwhite_lookup[0x00] == 0x00); /* end-of-input breaks */
234 if (duk__json_eatwhite_lookup[t] == 0) {
235 break;
236 }
237 #else /* DUK_USE_JSON_EATWHITE_FASTPATH */
238 if (!(t == 0x20 || t == 0x0a || t == 0x0d || t == 0x09)) {
239 /* NUL also comes here. Comparison order matters, 0x20
240 * is most common whitespace.
241 */
242 break;
243 }
244 #endif /* DUK_USE_JSON_EATWHITE_FASTPATH */
245 p++;
246 }
247 js_ctx->p = p;
248 }
249
250 #if defined(DUK_USE_JX)
duk__json_dec_peek(duk_json_dec_ctx * js_ctx)251 DUK_LOCAL duk_uint8_t duk__json_dec_peek(duk_json_dec_ctx *js_ctx) {
252 DUK_ASSERT(js_ctx->p <= js_ctx->p_end);
253 return *js_ctx->p;
254 }
255 #endif
256
duk__json_dec_get(duk_json_dec_ctx * js_ctx)257 DUK_LOCAL duk_uint8_t duk__json_dec_get(duk_json_dec_ctx *js_ctx) {
258 DUK_ASSERT(js_ctx->p <= js_ctx->p_end);
259 return *js_ctx->p++;
260 }
261
duk__json_dec_get_nonwhite(duk_json_dec_ctx * js_ctx)262 DUK_LOCAL duk_uint8_t duk__json_dec_get_nonwhite(duk_json_dec_ctx *js_ctx) {
263 duk__json_dec_eat_white(js_ctx);
264 return duk__json_dec_get(js_ctx);
265 }
266
267 /* For JX, expressing the whole unsigned 32-bit range matters. */
duk__json_dec_decode_hex_escape(duk_json_dec_ctx * js_ctx,duk_small_uint_t n)268 DUK_LOCAL duk_uint_fast32_t duk__json_dec_decode_hex_escape(duk_json_dec_ctx *js_ctx, duk_small_uint_t n) {
269 duk_small_uint_t i;
270 duk_uint_fast32_t res = 0;
271 duk_uint8_t x;
272 duk_small_int_t t;
273
274 for (i = 0; i < n; i++) {
275 /* XXX: share helper from lexer; duk_lexer.c / hexval(). */
276
277 x = duk__json_dec_get(js_ctx);
278 DUK_DDD(DUK_DDDPRINT("decode_hex_escape: i=%ld, n=%ld, res=%ld, x=%ld",
279 (long) i, (long) n, (long) res, (long) x));
280
281 /* x == 0x00 (EOF) causes syntax_error */
282 DUK_ASSERT(duk_hex_dectab[0] == -1);
283 t = duk_hex_dectab[x & 0xff];
284 if (DUK_LIKELY(t >= 0)) {
285 res = (res * 16) + (duk_uint_fast32_t) t;
286 } else {
287 /* catches EOF and invalid digits */
288 goto syntax_error;
289 }
290 }
291
292 DUK_DDD(DUK_DDDPRINT("final hex decoded value: %ld", (long) res));
293 return res;
294
295 syntax_error:
296 duk__json_dec_syntax_error(js_ctx);
297 DUK_UNREACHABLE();
298 return 0;
299 }
300
duk__json_dec_req_stridx(duk_json_dec_ctx * js_ctx,duk_small_uint_t stridx)301 DUK_LOCAL void duk__json_dec_req_stridx(duk_json_dec_ctx *js_ctx, duk_small_uint_t stridx) {
302 duk_hstring *h;
303 const duk_uint8_t *p;
304 duk_uint8_t x, y;
305
306 /* First character has already been eaten and checked by the caller.
307 * We can scan until a NUL in stridx string because no built-in strings
308 * have internal NULs.
309 */
310
311 DUK_ASSERT_STRIDX_VALID(stridx);
312 h = DUK_HTHREAD_GET_STRING(js_ctx->thr, stridx);
313 DUK_ASSERT(h != NULL);
314
315 p = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h) + 1;
316 DUK_ASSERT(*(js_ctx->p - 1) == *(p - 1)); /* first character has been matched */
317
318 for (;;) {
319 x = *p;
320 if (x == 0) {
321 break;
322 }
323 y = duk__json_dec_get(js_ctx);
324 if (x != y) {
325 /* Catches EOF of JSON input. */
326 goto syntax_error;
327 }
328 p++;
329 }
330
331 return;
332
333 syntax_error:
334 duk__json_dec_syntax_error(js_ctx);
335 DUK_UNREACHABLE();
336 }
337
duk__json_dec_string_escape(duk_json_dec_ctx * js_ctx,duk_uint8_t ** ext_p)338 DUK_LOCAL duk_small_int_t duk__json_dec_string_escape(duk_json_dec_ctx *js_ctx, duk_uint8_t **ext_p) {
339 duk_uint_fast32_t cp;
340
341 /* EOF (-1) will be cast to an unsigned value first
342 * and then re-cast for the switch. In any case, it
343 * will match the default case (syntax error).
344 */
345 cp = (duk_uint_fast32_t) duk__json_dec_get(js_ctx);
346 switch (cp) {
347 case DUK_ASC_BACKSLASH: break;
348 case DUK_ASC_DOUBLEQUOTE: break;
349 case DUK_ASC_SLASH: break;
350 case DUK_ASC_LC_T: cp = 0x09; break;
351 case DUK_ASC_LC_N: cp = 0x0a; break;
352 case DUK_ASC_LC_R: cp = 0x0d; break;
353 case DUK_ASC_LC_F: cp = 0x0c; break;
354 case DUK_ASC_LC_B: cp = 0x08; break;
355 case DUK_ASC_LC_U: {
356 cp = duk__json_dec_decode_hex_escape(js_ctx, 4);
357 break;
358 }
359 #if defined(DUK_USE_JX)
360 case DUK_ASC_UC_U: {
361 if (js_ctx->flag_ext_custom) {
362 cp = duk__json_dec_decode_hex_escape(js_ctx, 8);
363 } else {
364 return 1; /* syntax error */
365 }
366 break;
367 }
368 case DUK_ASC_LC_X: {
369 if (js_ctx->flag_ext_custom) {
370 cp = duk__json_dec_decode_hex_escape(js_ctx, 2);
371 } else {
372 return 1; /* syntax error */
373 }
374 break;
375 }
376 #endif /* DUK_USE_JX */
377 default:
378 /* catches EOF (0x00) */
379 return 1; /* syntax error */
380 }
381
382 DUK_RAW_WRITEINC_XUTF8(*ext_p, cp);
383
384 return 0;
385 }
386
duk__json_dec_string(duk_json_dec_ctx * js_ctx)387 DUK_LOCAL void duk__json_dec_string(duk_json_dec_ctx *js_ctx) {
388 duk_hthread *thr = js_ctx->thr;
389 duk_bufwriter_ctx bw_alloc;
390 duk_bufwriter_ctx *bw;
391 duk_uint8_t *q;
392
393 /* '"' was eaten by caller */
394
395 /* Note that we currently parse -bytes-, not codepoints.
396 * All non-ASCII extended UTF-8 will encode to bytes >= 0x80,
397 * so they'll simply pass through (valid UTF-8 or not).
398 */
399
400 bw = &bw_alloc;
401 DUK_BW_INIT_PUSHBUF(js_ctx->thr, bw, DUK__JSON_DECSTR_BUFSIZE);
402 q = DUK_BW_GET_PTR(js_ctx->thr, bw);
403
404 #if defined(DUK_USE_JSON_DECSTRING_FASTPATH)
405 for (;;) {
406 duk_small_uint_t safe;
407 duk_uint8_t b, x;
408 const duk_uint8_t *p;
409
410 /* Select a safe loop count where no output checks are
411 * needed assuming we won't encounter escapes. Input
412 * bound checks are not necessary as a NUL (guaranteed)
413 * will cause a SyntaxError before we read out of bounds.
414 */
415
416 safe = DUK__JSON_DECSTR_CHUNKSIZE;
417
418 /* Ensure space for 1:1 output plus one escape. */
419 q = DUK_BW_ENSURE_RAW(js_ctx->thr, bw, safe + DUK_UNICODE_MAX_XUTF8_LENGTH, q);
420
421 p = js_ctx->p; /* temp copy, write back for next loop */
422 for (;;) {
423 if (safe == 0) {
424 js_ctx->p = p;
425 break;
426 }
427 safe--;
428
429 /* End of input (NUL) goes through slow path and causes SyntaxError. */
430 DUK_ASSERT(duk__json_decstr_lookup[0] == 0x00);
431
432 b = *p++;
433 x = (duk_small_int_t) duk__json_decstr_lookup[b];
434 if (DUK_LIKELY(x != 0)) {
435 /* Fast path, decode as is. */
436 *q++ = b;
437 } else if (b == DUK_ASC_DOUBLEQUOTE) {
438 js_ctx->p = p;
439 goto found_quote;
440 } else if (b == DUK_ASC_BACKSLASH) {
441 /* We've ensured space for one escaped input; then
442 * bail out and recheck (this makes escape handling
443 * quite slow but it's uncommon).
444 */
445 js_ctx->p = p;
446 if (duk__json_dec_string_escape(js_ctx, &q) != 0) {
447 goto syntax_error;
448 }
449 break;
450 } else {
451 js_ctx->p = p;
452 goto syntax_error;
453 }
454 }
455 }
456 found_quote:
457 #else /* DUK_USE_JSON_DECSTRING_FASTPATH */
458 for (;;) {
459 duk_uint8_t x;
460
461 q = DUK_BW_ENSURE_RAW(js_ctx->thr, bw, DUK_UNICODE_MAX_XUTF8_LENGTH, q);
462
463 x = duk__json_dec_get(js_ctx);
464
465 if (x == DUK_ASC_DOUBLEQUOTE) {
466 break;
467 } else if (x == DUK_ASC_BACKSLASH) {
468 if (duk__json_dec_string_escape(js_ctx, &q) != 0) {
469 goto syntax_error;
470 }
471 } else if (x < 0x20) {
472 /* catches EOF (NUL) */
473 goto syntax_error;
474 } else {
475 *q++ = (duk_uint8_t) x;
476 }
477 }
478 #endif /* DUK_USE_JSON_DECSTRING_FASTPATH */
479
480 DUK_BW_SETPTR_AND_COMPACT(js_ctx->thr, bw, q);
481 (void) duk_buffer_to_string(thr, -1); /* Safe if input string is safe. */
482
483 /* [ ... str ] */
484
485 return;
486
487 syntax_error:
488 duk__json_dec_syntax_error(js_ctx);
489 DUK_UNREACHABLE();
490 }
491
492 #if defined(DUK_USE_JX)
493 /* Decode a plain string consisting entirely of identifier characters.
494 * Used to parse plain keys (e.g. "foo: 123").
495 */
duk__json_dec_plain_string(duk_json_dec_ctx * js_ctx)496 DUK_LOCAL void duk__json_dec_plain_string(duk_json_dec_ctx *js_ctx) {
497 duk_hthread *thr = js_ctx->thr;
498 const duk_uint8_t *p;
499 duk_small_int_t x;
500
501 /* Caller has already eaten the first char so backtrack one byte. */
502
503 js_ctx->p--; /* safe */
504 p = js_ctx->p;
505
506 /* Here again we parse bytes, and non-ASCII UTF-8 will cause end of
507 * parsing (which is correct except if there are non-shortest encodings).
508 * There is also no need to check explicitly for end of input buffer as
509 * the input is NUL padded and NUL will exit the parsing loop.
510 *
511 * Because no unescaping takes place, we can just scan to the end of the
512 * plain string and intern from the input buffer.
513 */
514
515 for (;;) {
516 x = *p;
517
518 /* There is no need to check the first character specially here
519 * (i.e. reject digits): the caller only accepts valid initial
520 * characters and won't call us if the first character is a digit.
521 * This also ensures that the plain string won't be empty.
522 */
523
524 if (!duk_unicode_is_identifier_part((duk_codepoint_t) x)) {
525 break;
526 }
527 p++;
528 }
529
530 duk_push_lstring(thr, (const char *) js_ctx->p, (duk_size_t) (p - js_ctx->p));
531 js_ctx->p = p;
532
533 /* [ ... str ] */
534 }
535 #endif /* DUK_USE_JX */
536
537 #if defined(DUK_USE_JX)
duk__json_dec_pointer(duk_json_dec_ctx * js_ctx)538 DUK_LOCAL void duk__json_dec_pointer(duk_json_dec_ctx *js_ctx) {
539 duk_hthread *thr = js_ctx->thr;
540 const duk_uint8_t *p;
541 duk_small_int_t x;
542 void *voidptr;
543
544 /* Caller has already eaten the first character ('(') which we don't need. */
545
546 p = js_ctx->p;
547
548 for (;;) {
549 x = *p;
550
551 /* Assume that the native representation never contains a closing
552 * parenthesis.
553 */
554
555 if (x == DUK_ASC_RPAREN) {
556 break;
557 } else if (x <= 0) {
558 /* NUL term or -1 (EOF), NUL check would suffice */
559 goto syntax_error;
560 }
561 p++;
562 }
563
564 /* There is no need to NUL delimit the sscanf() call: trailing garbage is
565 * ignored and there is always a NUL terminator which will force an error
566 * if no error is encountered before it. It's possible that the scan
567 * would scan further than between [js_ctx->p,p[ though and we'd advance
568 * by less than the scanned value.
569 *
570 * Because pointers are platform specific, a failure to scan a pointer
571 * results in a null pointer which is a better placeholder than a missing
572 * value or an error.
573 */
574
575 voidptr = NULL;
576 (void) DUK_SSCANF((const char *) js_ctx->p, DUK_STR_FMT_PTR, &voidptr);
577 duk_push_pointer(thr, voidptr);
578 js_ctx->p = p + 1; /* skip ')' */
579
580 /* [ ... ptr ] */
581
582 return;
583
584 syntax_error:
585 duk__json_dec_syntax_error(js_ctx);
586 DUK_UNREACHABLE();
587 }
588 #endif /* DUK_USE_JX */
589
590 #if defined(DUK_USE_JX)
duk__json_dec_buffer(duk_json_dec_ctx * js_ctx)591 DUK_LOCAL void duk__json_dec_buffer(duk_json_dec_ctx *js_ctx) {
592 duk_hthread *thr = js_ctx->thr;
593 const duk_uint8_t *p;
594 duk_uint8_t *buf;
595 duk_size_t src_len;
596 duk_small_int_t x;
597
598 /* Caller has already eaten the first character ('|') which we don't need. */
599
600 p = js_ctx->p;
601
602 /* XXX: Would be nice to share the fast path loop from duk_hex_decode()
603 * and avoid creating a temporary buffer. However, there are some
604 * differences which prevent trivial sharing:
605 *
606 * - Pipe char detection
607 * - EOF detection
608 * - Unknown length of input and output
609 *
610 * The best approach here would be a bufwriter and a reasonaly sized
611 * safe inner loop (e.g. 64 output bytes at a time).
612 */
613
614 for (;;) {
615 x = *p;
616
617 /* This loop intentionally does not ensure characters are valid
618 * ([0-9a-fA-F]) because the hex decode call below will do that.
619 */
620 if (x == DUK_ASC_PIPE) {
621 break;
622 } else if (x <= 0) {
623 /* NUL term or -1 (EOF), NUL check would suffice */
624 goto syntax_error;
625 }
626 p++;
627 }
628
629 /* XXX: this is not very nice; unnecessary copy is made. */
630 src_len = (duk_size_t) (p - js_ctx->p);
631 buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, src_len);
632 DUK_ASSERT(buf != NULL);
633 duk_memcpy((void *) buf, (const void *) js_ctx->p, src_len);
634 duk_hex_decode(thr, -1);
635
636 js_ctx->p = p + 1; /* skip '|' */
637
638 /* [ ... buf ] */
639
640 return;
641
642 syntax_error:
643 duk__json_dec_syntax_error(js_ctx);
644 DUK_UNREACHABLE();
645 }
646 #endif /* DUK_USE_JX */
647
648 /* Parse a number, other than NaN or +/- Infinity */
duk__json_dec_number(duk_json_dec_ctx * js_ctx)649 DUK_LOCAL void duk__json_dec_number(duk_json_dec_ctx *js_ctx) {
650 duk_hthread *thr = js_ctx->thr;
651 const duk_uint8_t *p_start;
652 const duk_uint8_t *p;
653 duk_uint8_t x;
654 duk_small_uint_t s2n_flags;
655
656 DUK_DDD(DUK_DDDPRINT("parse_number"));
657
658 p_start = js_ctx->p;
659
660 /* First pass parse is very lenient (e.g. allows '1.2.3') and extracts a
661 * string for strict number parsing.
662 */
663
664 p = js_ctx->p;
665 for (;;) {
666 x = *p;
667
668 DUK_DDD(DUK_DDDPRINT("parse_number: p_start=%p, p=%p, p_end=%p, x=%ld",
669 (const void *) p_start, (const void *) p,
670 (const void *) js_ctx->p_end, (long) x));
671
672 #if defined(DUK_USE_JSON_DECNUMBER_FASTPATH)
673 /* This fast path is pretty marginal in practice.
674 * XXX: candidate for removal.
675 */
676 DUK_ASSERT(duk__json_decnumber_lookup[0x00] == 0x00); /* end-of-input breaks */
677 if (duk__json_decnumber_lookup[x] == 0) {
678 break;
679 }
680 #else /* DUK_USE_JSON_DECNUMBER_FASTPATH */
681 if (!((x >= DUK_ASC_0 && x <= DUK_ASC_9) ||
682 (x == DUK_ASC_PERIOD || x == DUK_ASC_LC_E ||
683 x == DUK_ASC_UC_E || x == DUK_ASC_MINUS || x == DUK_ASC_PLUS))) {
684 /* Plus sign must be accepted for positive exponents
685 * (e.g. '1.5e+2'). This clause catches NULs.
686 */
687 break;
688 }
689 #endif /* DUK_USE_JSON_DECNUMBER_FASTPATH */
690 p++; /* safe, because matched (NUL causes a break) */
691 }
692 js_ctx->p = p;
693
694 DUK_ASSERT(js_ctx->p > p_start);
695 duk_push_lstring(thr, (const char *) p_start, (duk_size_t) (p - p_start));
696
697 s2n_flags = DUK_S2N_FLAG_ALLOW_EXP |
698 DUK_S2N_FLAG_ALLOW_MINUS | /* but don't allow leading plus */
699 DUK_S2N_FLAG_ALLOW_FRAC;
700
701 DUK_DDD(DUK_DDDPRINT("parse_number: string before parsing: %!T",
702 (duk_tval *) duk_get_tval(thr, -1)));
703 duk_numconv_parse(thr, 10 /*radix*/, s2n_flags);
704 if (duk_is_nan(thr, -1)) {
705 duk__json_dec_syntax_error(js_ctx);
706 }
707 DUK_ASSERT(duk_is_number(thr, -1));
708 DUK_DDD(DUK_DDDPRINT("parse_number: final number: %!T",
709 (duk_tval *) duk_get_tval(thr, -1)));
710
711 /* [ ... num ] */
712 }
713
duk__json_dec_objarr_entry(duk_json_dec_ctx * js_ctx)714 DUK_LOCAL void duk__json_dec_objarr_entry(duk_json_dec_ctx *js_ctx) {
715 duk_hthread *thr = js_ctx->thr;
716 duk_require_stack(thr, DUK_JSON_DEC_REQSTACK);
717
718 /* c recursion check */
719
720 duk_native_stack_check(thr);
721
722 DUK_ASSERT_DISABLE(js_ctx->recursion_depth >= 0); /* unsigned */
723 DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
724 if (js_ctx->recursion_depth >= js_ctx->recursion_limit) {
725 DUK_ERROR_RANGE(thr, DUK_STR_DEC_RECLIMIT);
726 DUK_WO_NORETURN(return;);
727 }
728 js_ctx->recursion_depth++;
729 }
730
duk__json_dec_objarr_exit(duk_json_dec_ctx * js_ctx)731 DUK_LOCAL void duk__json_dec_objarr_exit(duk_json_dec_ctx *js_ctx) {
732 /* c recursion check */
733
734 DUK_ASSERT(js_ctx->recursion_depth > 0);
735 DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
736 js_ctx->recursion_depth--;
737 }
738
duk__json_dec_object(duk_json_dec_ctx * js_ctx)739 DUK_LOCAL void duk__json_dec_object(duk_json_dec_ctx *js_ctx) {
740 duk_hthread *thr = js_ctx->thr;
741 duk_int_t key_count; /* XXX: a "first" flag would suffice */
742 duk_uint8_t x;
743
744 DUK_DDD(DUK_DDDPRINT("parse_object"));
745
746 duk__json_dec_objarr_entry(js_ctx);
747
748 duk_push_object(thr);
749
750 /* Initial '{' has been checked and eaten by caller. */
751
752 key_count = 0;
753 for (;;) {
754 x = duk__json_dec_get_nonwhite(js_ctx);
755
756 DUK_DDD(DUK_DDDPRINT("parse_object: obj=%!T, x=%ld, key_count=%ld",
757 (duk_tval *) duk_get_tval(thr, -1),
758 (long) x, (long) key_count));
759
760 /* handle comma and closing brace */
761
762 if (x == DUK_ASC_COMMA && key_count > 0) {
763 /* accept comma, expect new value */
764 x = duk__json_dec_get_nonwhite(js_ctx);
765 } else if (x == DUK_ASC_RCURLY) {
766 /* eat closing brace */
767 break;
768 } else if (key_count == 0) {
769 /* accept anything, expect first value (EOF will be
770 * caught by key parsing below.
771 */
772 ;
773 } else {
774 /* catches EOF (NUL) and initial comma */
775 goto syntax_error;
776 }
777
778 /* parse key and value */
779
780 if (x == DUK_ASC_DOUBLEQUOTE) {
781 duk__json_dec_string(js_ctx);
782 #if defined(DUK_USE_JX)
783 } else if (js_ctx->flag_ext_custom &&
784 duk_unicode_is_identifier_start((duk_codepoint_t) x)) {
785 duk__json_dec_plain_string(js_ctx);
786 #endif
787 } else {
788 goto syntax_error;
789 }
790
791 /* [ ... obj key ] */
792
793 x = duk__json_dec_get_nonwhite(js_ctx);
794 if (x != DUK_ASC_COLON) {
795 goto syntax_error;
796 }
797
798 duk__json_dec_value(js_ctx);
799
800 /* [ ... obj key val ] */
801
802 duk_xdef_prop_wec(thr, -3);
803
804 /* [ ... obj ] */
805
806 key_count++;
807 }
808
809 /* [ ... obj ] */
810
811 DUK_DDD(DUK_DDDPRINT("parse_object: final object is %!T",
812 (duk_tval *) duk_get_tval(thr, -1)));
813
814 duk__json_dec_objarr_exit(js_ctx);
815 return;
816
817 syntax_error:
818 duk__json_dec_syntax_error(js_ctx);
819 DUK_UNREACHABLE();
820 }
821
duk__json_dec_array(duk_json_dec_ctx * js_ctx)822 DUK_LOCAL void duk__json_dec_array(duk_json_dec_ctx *js_ctx) {
823 duk_hthread *thr = js_ctx->thr;
824 duk_uarridx_t arr_idx;
825 duk_uint8_t x;
826
827 DUK_DDD(DUK_DDDPRINT("parse_array"));
828
829 duk__json_dec_objarr_entry(js_ctx);
830
831 duk_push_array(thr);
832
833 /* Initial '[' has been checked and eaten by caller. */
834
835 arr_idx = 0;
836 for (;;) {
837 x = duk__json_dec_get_nonwhite(js_ctx);
838
839 DUK_DDD(DUK_DDDPRINT("parse_array: arr=%!T, x=%ld, arr_idx=%ld",
840 (duk_tval *) duk_get_tval(thr, -1),
841 (long) x, (long) arr_idx));
842
843 /* handle comma and closing bracket */
844
845 if ((x == DUK_ASC_COMMA) && (arr_idx != 0)) {
846 /* accept comma, expect new value */
847 ;
848 } else if (x == DUK_ASC_RBRACKET) {
849 /* eat closing bracket */
850 break;
851 } else if (arr_idx == 0) {
852 /* accept anything, expect first value (EOF will be
853 * caught by duk__json_dec_value() below.
854 */
855 js_ctx->p--; /* backtrack (safe) */
856 } else {
857 /* catches EOF (NUL) and initial comma */
858 goto syntax_error;
859 }
860
861 /* parse value */
862
863 duk__json_dec_value(js_ctx);
864
865 /* [ ... arr val ] */
866
867 duk_xdef_prop_index_wec(thr, -2, arr_idx);
868 arr_idx++;
869 }
870
871 /* Must set 'length' explicitly when using duk_xdef_prop_xxx() to
872 * set the values.
873 */
874
875 duk_set_length(thr, -1, arr_idx);
876
877 /* [ ... arr ] */
878
879 DUK_DDD(DUK_DDDPRINT("parse_array: final array is %!T",
880 (duk_tval *) duk_get_tval(thr, -1)));
881
882 duk__json_dec_objarr_exit(js_ctx);
883 return;
884
885 syntax_error:
886 duk__json_dec_syntax_error(js_ctx);
887 DUK_UNREACHABLE();
888 }
889
duk__json_dec_value(duk_json_dec_ctx * js_ctx)890 DUK_LOCAL void duk__json_dec_value(duk_json_dec_ctx *js_ctx) {
891 duk_hthread *thr = js_ctx->thr;
892 duk_uint8_t x;
893
894 x = duk__json_dec_get_nonwhite(js_ctx);
895
896 DUK_DDD(DUK_DDDPRINT("parse_value: initial x=%ld", (long) x));
897
898 /* Note: duk__json_dec_req_stridx() backtracks one char */
899
900 if (x == DUK_ASC_DOUBLEQUOTE) {
901 duk__json_dec_string(js_ctx);
902 } else if ((x >= DUK_ASC_0 && x <= DUK_ASC_9) || (x == DUK_ASC_MINUS)) {
903 #if defined(DUK_USE_JX)
904 if (js_ctx->flag_ext_custom && x == DUK_ASC_MINUS && duk__json_dec_peek(js_ctx) == DUK_ASC_UC_I) {
905 duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_MINUS_INFINITY); /* "-Infinity", '-' has been eaten */
906 duk_push_number(thr, -DUK_DOUBLE_INFINITY);
907 } else {
908 #else
909 { /* unconditional block */
910 #endif
911 /* We already ate 'x', so backup one byte. */
912 js_ctx->p--; /* safe */
913 duk__json_dec_number(js_ctx);
914 }
915 } else if (x == DUK_ASC_LC_T) {
916 duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_TRUE);
917 duk_push_true(thr);
918 } else if (x == DUK_ASC_LC_F) {
919 duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_FALSE);
920 duk_push_false(thr);
921 } else if (x == DUK_ASC_LC_N) {
922 duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_LC_NULL);
923 duk_push_null(thr);
924 #if defined(DUK_USE_JX)
925 } else if (js_ctx->flag_ext_custom && x == DUK_ASC_LC_U) {
926 duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_LC_UNDEFINED);
927 duk_push_undefined(thr);
928 } else if (js_ctx->flag_ext_custom && x == DUK_ASC_UC_N) {
929 duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_NAN);
930 duk_push_nan(thr);
931 } else if (js_ctx->flag_ext_custom && x == DUK_ASC_UC_I) {
932 duk__json_dec_req_stridx(js_ctx, DUK_STRIDX_INFINITY);
933 duk_push_number(thr, DUK_DOUBLE_INFINITY);
934 } else if (js_ctx->flag_ext_custom && x == DUK_ASC_LPAREN) {
935 duk__json_dec_pointer(js_ctx);
936 } else if (js_ctx->flag_ext_custom && x == DUK_ASC_PIPE) {
937 duk__json_dec_buffer(js_ctx);
938 #endif
939 } else if (x == DUK_ASC_LCURLY) {
940 duk__json_dec_object(js_ctx);
941 } else if (x == DUK_ASC_LBRACKET) {
942 duk__json_dec_array(js_ctx);
943 } else {
944 /* catches EOF (NUL) */
945 goto syntax_error;
946 }
947
948 duk__json_dec_eat_white(js_ctx);
949
950 /* [ ... val ] */
951 return;
952
953 syntax_error:
954 duk__json_dec_syntax_error(js_ctx);
955 DUK_UNREACHABLE();
956 }
957
958 /* Recursive value reviver, implements the Walk() algorithm. The parsing
959 * step ensures there is a reasonable depth limit to the input. However,
960 * the reviver may create more depth by editing object or array entries, so
961 * we have both C recursion limit and native stack checks here.
962 */
963 DUK_LOCAL void duk__json_dec_reviver_walk(duk_json_dec_ctx *js_ctx) {
964 duk_hthread *thr = js_ctx->thr;
965 duk_hobject *h;
966 duk_uarridx_t i, arr_len;
967
968 duk__json_dec_objarr_entry(js_ctx);
969
970 DUK_DDD(DUK_DDDPRINT("walk: top=%ld, holder=%!T, name=%!T",
971 (long) duk_get_top(thr),
972 (duk_tval *) duk_get_tval(thr, -2),
973 (duk_tval *) duk_get_tval(thr, -1)));
974
975 duk_dup_top(thr);
976 duk_get_prop(thr, -3); /* -> [ ... holder name val ] */
977
978 h = duk_get_hobject(thr, -1);
979 if (h != NULL) {
980 if (duk_js_isarray_hobject(h)) {
981 arr_len = (duk_uarridx_t) duk_get_length(thr, -1);
982 for (i = 0; i < arr_len; i++) {
983 /* [ ... holder name val ] */
984
985 DUK_DDD(DUK_DDDPRINT("walk: array, top=%ld, i=%ld, arr_len=%ld, holder=%!T, name=%!T, val=%!T",
986 (long) duk_get_top(thr), (long) i, (long) arr_len,
987 (duk_tval *) duk_get_tval(thr, -3), (duk_tval *) duk_get_tval(thr, -2),
988 (duk_tval *) duk_get_tval(thr, -1)));
989
990 duk_dup_top(thr);
991 (void) duk_push_uint_to_hstring(thr, (duk_uint_t) i); /* -> [ ... holder name val val ToString(i) ] */
992 duk__json_dec_reviver_walk(js_ctx); /* -> [ ... holder name val new_elem ] */
993
994 if (duk_is_undefined(thr, -1)) {
995 duk_pop(thr);
996 duk_del_prop_index(thr, -1, i);
997 } else {
998 /* XXX: duk_xdef_prop_index_wec() would be more appropriate
999 * here but it currently makes some assumptions that might
1000 * not hold (e.g. that previous property is not an accessor).
1001 */
1002 duk_put_prop_index(thr, -2, i);
1003 }
1004 }
1005 } else {
1006 /* [ ... holder name val ] */
1007 duk_enum(thr, -1, DUK_ENUM_OWN_PROPERTIES_ONLY /*flags*/);
1008 while (duk_next(thr, -1 /*enum_index*/, 0 /*get_value*/)) {
1009 DUK_DDD(DUK_DDDPRINT("walk: object, top=%ld, holder=%!T, name=%!T, val=%!T, enum=%!iT, obj_key=%!T",
1010 (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, -5),
1011 (duk_tval *) duk_get_tval(thr, -4), (duk_tval *) duk_get_tval(thr, -3),
1012 (duk_tval *) duk_get_tval(thr, -2), (duk_tval *) duk_get_tval(thr, -1)));
1013
1014 /* [ ... holder name val enum obj_key ] */
1015 duk_dup_m3(thr);
1016 duk_dup_m2(thr);
1017
1018 /* [ ... holder name val enum obj_key val obj_key ] */
1019 duk__json_dec_reviver_walk(js_ctx);
1020
1021 /* [ ... holder name val enum obj_key new_elem ] */
1022 if (duk_is_undefined(thr, -1)) {
1023 duk_pop(thr);
1024 duk_del_prop(thr, -3);
1025 } else {
1026 /* XXX: duk_xdef_prop_index_wec() would be more appropriate
1027 * here but it currently makes some assumptions that might
1028 * not hold (e.g. that previous property is not an accessor).
1029 *
1030 * Using duk_put_prop() works incorrectly with '__proto__'
1031 * if the own property with that name has been deleted. This
1032 * does not happen normally, but a clever reviver can trigger
1033 * that, see complex reviver case in: test-bug-json-parse-__proto__.js.
1034 */
1035 duk_put_prop(thr, -4);
1036 }
1037 }
1038 duk_pop(thr); /* pop enum */
1039 }
1040 }
1041
1042 /* [ ... holder name val ] */
1043
1044 duk_dup(thr, js_ctx->idx_reviver);
1045 duk_insert(thr, -4); /* -> [ ... reviver holder name val ] */
1046 duk_call_method(thr, 2); /* -> [ ... res ] */
1047
1048 duk__json_dec_objarr_exit(js_ctx);
1049
1050 DUK_DDD(DUK_DDDPRINT("walk: top=%ld, result=%!T",
1051 (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, -1)));
1052 }
1053
1054 /*
1055 * Stringify implementation.
1056 */
1057
1058 #define DUK__EMIT_1(js_ctx,ch) duk__emit_1((js_ctx), (duk_uint_fast8_t) (ch))
1059 #define DUK__EMIT_2(js_ctx,ch1,ch2) duk__emit_2((js_ctx), (duk_uint_fast8_t) (ch1), (duk_uint_fast8_t) (ch2))
1060 #define DUK__EMIT_HSTR(js_ctx,h) duk__emit_hstring((js_ctx), (h))
1061 #if defined(DUK_USE_FASTINT) || defined(DUK_USE_JX) || defined(DUK_USE_JC)
1062 #define DUK__EMIT_CSTR(js_ctx,p) duk__emit_cstring((js_ctx), (p))
1063 #endif
1064 #define DUK__EMIT_STRIDX(js_ctx,i) duk__emit_stridx((js_ctx), (i))
1065 #define DUK__UNEMIT_1(js_ctx) duk__unemit_1((js_ctx))
1066
1067 DUK_LOCAL void duk__emit_1(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch) {
1068 DUK_BW_WRITE_ENSURE_U8(js_ctx->thr, &js_ctx->bw, ch);
1069 }
1070
1071 DUK_LOCAL void duk__emit_2(duk_json_enc_ctx *js_ctx, duk_uint_fast8_t ch1, duk_uint_fast8_t ch2) {
1072 DUK_BW_WRITE_ENSURE_U8_2(js_ctx->thr, &js_ctx->bw, ch1, ch2);
1073 }
1074
1075 DUK_LOCAL void duk__emit_hstring(duk_json_enc_ctx *js_ctx, duk_hstring *h) {
1076 DUK_BW_WRITE_ENSURE_HSTRING(js_ctx->thr, &js_ctx->bw, h);
1077 }
1078
1079 #if defined(DUK_USE_FASTINT) || defined(DUK_USE_JX) || defined(DUK_USE_JC)
1080 DUK_LOCAL void duk__emit_cstring(duk_json_enc_ctx *js_ctx, const char *str) {
1081 DUK_BW_WRITE_ENSURE_CSTRING(js_ctx->thr, &js_ctx->bw, str);
1082 }
1083 #endif
1084
1085 DUK_LOCAL void duk__emit_stridx(duk_json_enc_ctx *js_ctx, duk_small_uint_t stridx) {
1086 duk_hstring *h;
1087
1088 DUK_ASSERT_STRIDX_VALID(stridx);
1089 h = DUK_HTHREAD_GET_STRING(js_ctx->thr, stridx);
1090 DUK_ASSERT(h != NULL);
1091
1092 DUK_BW_WRITE_ENSURE_HSTRING(js_ctx->thr, &js_ctx->bw, h);
1093 }
1094
1095 DUK_LOCAL void duk__unemit_1(duk_json_enc_ctx *js_ctx) {
1096 DUK_ASSERT(DUK_BW_GET_SIZE(js_ctx->thr, &js_ctx->bw) >= 1);
1097 DUK_BW_ADD_PTR(js_ctx->thr, &js_ctx->bw, -1);
1098 }
1099
1100 #define DUK__MKESC(nybbles,esc1,esc2) \
1101 (((duk_uint_fast32_t) (nybbles)) << 16) | \
1102 (((duk_uint_fast32_t) (esc1)) << 8) | \
1103 ((duk_uint_fast32_t) (esc2))
1104
1105 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) {
1106 duk_uint_fast32_t tmp;
1107 duk_small_uint_t dig;
1108
1109 DUK_UNREF(js_ctx);
1110
1111 /* Caller ensures space for at least DUK__JSON_MAX_ESC_LEN. */
1112
1113 /* Select appropriate escape format automatically, and set 'tmp' to a
1114 * value encoding both the escape format character and the nybble count:
1115 *
1116 * (nybble_count << 16) | (escape_char1) | (escape_char2)
1117 */
1118
1119 #if defined(DUK_USE_JX)
1120 if (DUK_LIKELY(cp < 0x100UL)) {
1121 if (DUK_UNLIKELY(js_ctx->flag_ext_custom != 0U)) {
1122 tmp = DUK__MKESC(2, DUK_ASC_BACKSLASH, DUK_ASC_LC_X);
1123 } else {
1124 tmp = DUK__MKESC(4, DUK_ASC_BACKSLASH, DUK_ASC_LC_U);
1125 }
1126 } else
1127 #endif
1128 if (DUK_LIKELY(cp < 0x10000UL)) {
1129 tmp = DUK__MKESC(4, DUK_ASC_BACKSLASH, DUK_ASC_LC_U);
1130 } else {
1131 #if defined(DUK_USE_JX)
1132 if (DUK_LIKELY(js_ctx->flag_ext_custom != 0U)) {
1133 tmp = DUK__MKESC(8, DUK_ASC_BACKSLASH, DUK_ASC_UC_U);
1134 } else
1135 #endif
1136 {
1137 /* In compatible mode and standard JSON mode, output
1138 * something useful for non-BMP characters. This won't
1139 * roundtrip but will still be more or less readable and
1140 * more useful than an error.
1141 */
1142 tmp = DUK__MKESC(8, DUK_ASC_UC_U, DUK_ASC_PLUS);
1143 }
1144 }
1145
1146 *q++ = (duk_uint8_t) ((tmp >> 8) & 0xff);
1147 *q++ = (duk_uint8_t) (tmp & 0xff);
1148
1149 tmp = tmp >> 16;
1150 while (tmp > 0) {
1151 tmp--;
1152 dig = (duk_small_uint_t) ((cp >> (4 * tmp)) & 0x0f);
1153 *q++ = duk_lc_digits[dig];
1154 }
1155
1156 return q;
1157 }
1158
1159 DUK_LOCAL void duk__json_enc_key_autoquote(duk_json_enc_ctx *js_ctx, duk_hstring *k) {
1160 const duk_int8_t *p, *p_start, *p_end; /* Note: intentionally signed. */
1161 duk_size_t k_len;
1162 duk_codepoint_t cp;
1163
1164 DUK_ASSERT(k != NULL);
1165
1166 /* Accept ASCII strings which conform to identifier requirements
1167 * as being emitted without key quotes. Since we only accept ASCII
1168 * there's no need for actual decoding: 'p' is intentionally signed
1169 * so that bytes >= 0x80 extend to negative values and are rejected
1170 * as invalid identifier codepoints.
1171 */
1172
1173 if (js_ctx->flag_avoid_key_quotes) {
1174 k_len = DUK_HSTRING_GET_BYTELEN(k);
1175 p_start = (const duk_int8_t *) DUK_HSTRING_GET_DATA(k);
1176 p_end = p_start + k_len;
1177 p = p_start;
1178
1179 if (p == p_end) {
1180 /* Zero length string is not accepted without quotes */
1181 goto quote_normally;
1182 }
1183 cp = (duk_codepoint_t) (*p++);
1184 if (DUK_UNLIKELY(!duk_unicode_is_identifier_start(cp))) {
1185 goto quote_normally;
1186 }
1187 while (p < p_end) {
1188 cp = (duk_codepoint_t) (*p++);
1189 if (DUK_UNLIKELY(!duk_unicode_is_identifier_part(cp))) {
1190 goto quote_normally;
1191 }
1192 }
1193
1194 /* This seems faster than emitting bytes one at a time and
1195 * then potentially rewinding.
1196 */
1197 DUK__EMIT_HSTR(js_ctx, k);
1198 return;
1199 }
1200
1201 quote_normally:
1202 duk__json_enc_quote_string(js_ctx, k);
1203 }
1204
1205 /* The Quote(value) operation: quote a string.
1206 *
1207 * Stack policy: [ ] -> [ ].
1208 */
1209
1210 DUK_LOCAL void duk__json_enc_quote_string(duk_json_enc_ctx *js_ctx, duk_hstring *h_str) {
1211 duk_hthread *thr = js_ctx->thr;
1212 const duk_uint8_t *p, *p_start, *p_end, *p_now, *p_tmp;
1213 duk_uint8_t *q;
1214 duk_ucodepoint_t cp; /* typed for duk_unicode_decode_xutf8() */
1215
1216 DUK_DDD(DUK_DDDPRINT("duk__json_enc_quote_string: h_str=%!O", (duk_heaphdr *) h_str));
1217
1218 DUK_ASSERT(h_str != NULL);
1219 p_start = DUK_HSTRING_GET_DATA(h_str);
1220 p_end = p_start + DUK_HSTRING_GET_BYTELEN(h_str);
1221 p = p_start;
1222
1223 DUK__EMIT_1(js_ctx, DUK_ASC_DOUBLEQUOTE);
1224
1225 /* Encode string in small chunks, estimating the maximum expansion so that
1226 * there's no need to ensure space while processing the chunk.
1227 */
1228
1229 while (p < p_end) {
1230 duk_size_t left, now, space;
1231
1232 left = (duk_size_t) (p_end - p);
1233 now = (left > DUK__JSON_ENCSTR_CHUNKSIZE ?
1234 DUK__JSON_ENCSTR_CHUNKSIZE : left);
1235
1236 /* Maximum expansion per input byte is 6:
1237 * - invalid UTF-8 byte causes "\uXXXX" to be emitted (6/1 = 6).
1238 * - 2-byte UTF-8 encodes as "\uXXXX" (6/2 = 3).
1239 * - 4-byte UTF-8 encodes as "\Uxxxxxxxx" (10/4 = 2.5).
1240 */
1241 space = now * 6;
1242 q = DUK_BW_ENSURE_GETPTR(thr, &js_ctx->bw, space);
1243
1244 p_now = p + now;
1245
1246 while (p < p_now) {
1247 #if defined(DUK_USE_JSON_QUOTESTRING_FASTPATH)
1248 duk_uint8_t b;
1249
1250 b = duk__json_quotestr_lookup[*p++];
1251 if (DUK_LIKELY(b < 0x80)) {
1252 /* Most input bytes go through here. */
1253 *q++ = b;
1254 } else if (b >= 0xa0) {
1255 *q++ = DUK_ASC_BACKSLASH;
1256 *q++ = (duk_uint8_t) (b - 0x80);
1257 } else if (b == 0x80) {
1258 cp = (duk_ucodepoint_t) (*(p - 1));
1259 q = duk__emit_esc_auto_fast(js_ctx, cp, q);
1260 } else if (b == 0x7f && js_ctx->flag_ascii_only) {
1261 /* 0x7F is special */
1262 DUK_ASSERT(b == 0x81);
1263 cp = (duk_ucodepoint_t) 0x7f;
1264 q = duk__emit_esc_auto_fast(js_ctx, cp, q);
1265 } else {
1266 DUK_ASSERT(b == 0x81);
1267 p--;
1268
1269 /* slow path is shared */
1270 #else /* DUK_USE_JSON_QUOTESTRING_FASTPATH */
1271 cp = *p;
1272
1273 if (DUK_LIKELY(cp <= 0x7f)) {
1274 /* ascii fast path: avoid decoding utf-8 */
1275 p++;
1276 if (cp == 0x22 || cp == 0x5c) {
1277 /* double quote or backslash */
1278 *q++ = DUK_ASC_BACKSLASH;
1279 *q++ = (duk_uint8_t) cp;
1280 } else if (cp < 0x20) {
1281 duk_uint_fast8_t esc_char;
1282
1283 /* This approach is a bit shorter than a straight
1284 * if-else-ladder and also a bit faster.
1285 */
1286 if (cp < (sizeof(duk__json_quotestr_esc) / sizeof(duk_uint8_t)) &&
1287 (esc_char = duk__json_quotestr_esc[cp]) != 0) {
1288 *q++ = DUK_ASC_BACKSLASH;
1289 *q++ = (duk_uint8_t) esc_char;
1290 } else {
1291 q = duk__emit_esc_auto_fast(js_ctx, cp, q);
1292 }
1293 } else if (cp == 0x7f && js_ctx->flag_ascii_only) {
1294 q = duk__emit_esc_auto_fast(js_ctx, cp, q);
1295 } else {
1296 /* any other printable -> as is */
1297 *q++ = (duk_uint8_t) cp;
1298 }
1299 } else {
1300 /* slow path is shared */
1301 #endif /* DUK_USE_JSON_QUOTESTRING_FASTPATH */
1302
1303 /* slow path decode */
1304
1305 /* If XUTF-8 decoding fails, treat the offending byte as a codepoint directly
1306 * and go forward one byte. This is of course very lossy, but allows some kind
1307 * of output to be produced even for internal strings which don't conform to
1308 * XUTF-8. All standard ECMAScript strings are always CESU-8, so this behavior
1309 * does not violate the ECMAScript specification. The behavior is applied to
1310 * all modes, including ECMAScript standard JSON. Because the current XUTF-8
1311 * decoding is not very strict, this behavior only really affects initial bytes
1312 * and truncated codepoints.
1313 *
1314 * Another alternative would be to scan forwards to start of next codepoint
1315 * (or end of input) and emit just one replacement codepoint.
1316 */
1317
1318 p_tmp = p;
1319 if (!duk_unicode_decode_xutf8(thr, &p, p_start, p_end, &cp)) {
1320 /* Decode failed. */
1321 cp = *p_tmp;
1322 p = p_tmp + 1;
1323 }
1324
1325 #if defined(DUK_USE_NONSTD_JSON_ESC_U2028_U2029)
1326 if (js_ctx->flag_ascii_only || cp == 0x2028 || cp == 0x2029) {
1327 #else
1328 if (js_ctx->flag_ascii_only) {
1329 #endif
1330 q = duk__emit_esc_auto_fast(js_ctx, cp, q);
1331 } else {
1332 /* as is */
1333 DUK_RAW_WRITEINC_XUTF8(q, cp);
1334 }
1335 }
1336 }
1337
1338 DUK_BW_SET_PTR(thr, &js_ctx->bw, q);
1339 }
1340
1341 DUK__EMIT_1(js_ctx, DUK_ASC_DOUBLEQUOTE);
1342 }
1343
1344 /* Encode a double (checked by caller) from stack top. Stack top may be
1345 * replaced by serialized string but is not popped (caller does that).
1346 */
1347 DUK_LOCAL void duk__json_enc_double(duk_json_enc_ctx *js_ctx) {
1348 duk_hthread *thr;
1349 duk_tval *tv;
1350 duk_double_t d;
1351 duk_small_int_t c;
1352 duk_small_int_t s;
1353 duk_small_uint_t stridx;
1354 duk_small_uint_t n2s_flags;
1355 duk_hstring *h_str;
1356
1357 DUK_ASSERT(js_ctx != NULL);
1358 thr = js_ctx->thr;
1359 DUK_ASSERT(thr != NULL);
1360
1361 /* Caller must ensure 'tv' is indeed a double and not a fastint! */
1362 tv = DUK_GET_TVAL_NEGIDX(thr, -1);
1363 DUK_ASSERT(DUK_TVAL_IS_DOUBLE(tv));
1364 d = DUK_TVAL_GET_DOUBLE(tv);
1365
1366 c = (duk_small_int_t) DUK_FPCLASSIFY(d);
1367 s = (duk_small_int_t) DUK_SIGNBIT(d);
1368 DUK_UNREF(s);
1369
1370 if (DUK_LIKELY(!(c == DUK_FP_INFINITE || c == DUK_FP_NAN))) {
1371 DUK_ASSERT(DUK_ISFINITE(d));
1372
1373 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
1374 /* Negative zero needs special handling in JX/JC because
1375 * it would otherwise serialize to '0', not '-0'.
1376 */
1377 if (DUK_UNLIKELY(c == DUK_FP_ZERO && s != 0 &&
1378 (js_ctx->flag_ext_custom_or_compatible))) {
1379 duk_push_hstring_stridx(thr, DUK_STRIDX_MINUS_ZERO); /* '-0' */
1380 } else
1381 #endif /* DUK_USE_JX || DUK_USE_JC */
1382 {
1383 n2s_flags = 0;
1384 /* [ ... number ] -> [ ... string ] */
1385 duk_numconv_stringify(thr, 10 /*radix*/, 0 /*digits*/, n2s_flags);
1386 }
1387 h_str = duk_known_hstring(thr, -1);
1388 DUK__EMIT_HSTR(js_ctx, h_str);
1389 return;
1390 }
1391
1392 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
1393 if (!(js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM |
1394 DUK_JSON_FLAG_EXT_COMPATIBLE))) {
1395 stridx = DUK_STRIDX_LC_NULL;
1396 } else if (c == DUK_FP_NAN) {
1397 stridx = js_ctx->stridx_custom_nan;
1398 } else if (s == 0) {
1399 stridx = js_ctx->stridx_custom_posinf;
1400 } else {
1401 stridx = js_ctx->stridx_custom_neginf;
1402 }
1403 #else
1404 stridx = DUK_STRIDX_LC_NULL;
1405 #endif
1406 DUK__EMIT_STRIDX(js_ctx, stridx);
1407 }
1408
1409 #if defined(DUK_USE_FASTINT)
1410 /* Encode a fastint from duk_tval ptr, no value stack effects. */
1411 DUK_LOCAL void duk__json_enc_fastint_tval(duk_json_enc_ctx *js_ctx, duk_tval *tv) {
1412 duk_int64_t v;
1413
1414 /* Fastint range is signed 48-bit so longest value is -2^47 = -140737488355328
1415 * (16 chars long), longest signed 64-bit value is -2^63 = -9223372036854775808
1416 * (20 chars long). Alloc space for 64-bit range to be safe.
1417 */
1418 duk_uint8_t buf[20 + 1];
1419
1420 /* Caller must ensure 'tv' is indeed a fastint! */
1421 DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv));
1422 v = DUK_TVAL_GET_FASTINT(tv);
1423
1424 /* XXX: There are no format strings in duk_config.h yet, could add
1425 * one for formatting duk_int64_t. For now, assumes "%lld" and that
1426 * "long long" type exists. Could also rely on C99 directly but that
1427 * won't work for older MSVC.
1428 */
1429 DUK_SPRINTF((char *) buf, "%lld", (long long) v);
1430 DUK__EMIT_CSTR(js_ctx, (const char *) buf);
1431 }
1432 #endif
1433
1434 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
1435 #if defined(DUK_USE_HEX_FASTPATH)
1436 DUK_LOCAL duk_uint8_t *duk__json_enc_buffer_data_hex(const duk_uint8_t *src, duk_size_t src_len, duk_uint8_t *dst) {
1437 duk_uint8_t *q;
1438 duk_uint16_t *q16;
1439 duk_small_uint_t x;
1440 duk_size_t i, len_safe;
1441 #if !defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE)
1442 duk_bool_t shift_dst;
1443 #endif
1444
1445 /* Unlike in duk_hex_encode() 'dst' is not necessarily aligned by 2.
1446 * For platforms where unaligned accesses are not allowed, shift 'dst'
1447 * ahead by 1 byte to get alignment and then duk_memmove() the result
1448 * in place. The faster encoding loop makes up the difference.
1449 * There's always space for one extra byte because a terminator always
1450 * follows the hex data and that's been accounted for by the caller.
1451 */
1452
1453 #if defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE)
1454 q16 = (duk_uint16_t *) (void *) dst;
1455 #else
1456 shift_dst = (duk_bool_t) (((duk_size_t) dst) & 0x01U);
1457 if (shift_dst) {
1458 DUK_DD(DUK_DDPRINT("unaligned accesses not possible, dst not aligned -> step to dst + 1"));
1459 q16 = (duk_uint16_t *) (void *) (dst + 1);
1460 } else {
1461 DUK_DD(DUK_DDPRINT("unaligned accesses not possible, dst is aligned"));
1462 q16 = (duk_uint16_t *) (void *) dst;
1463 }
1464 DUK_ASSERT((((duk_size_t) q16) & 0x01U) == 0);
1465 #endif
1466
1467 len_safe = src_len & ~0x03U;
1468 for (i = 0; i < len_safe; i += 4) {
1469 q16[0] = duk_hex_enctab[src[i]];
1470 q16[1] = duk_hex_enctab[src[i + 1]];
1471 q16[2] = duk_hex_enctab[src[i + 2]];
1472 q16[3] = duk_hex_enctab[src[i + 3]];
1473 q16 += 4;
1474 }
1475 q = (duk_uint8_t *) q16;
1476
1477 #if !defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE)
1478 if (shift_dst) {
1479 q--;
1480 duk_memmove((void *) dst, (const void *) (dst + 1), 2 * len_safe);
1481 DUK_ASSERT(dst + 2 * len_safe == q);
1482 }
1483 #endif
1484
1485 for (; i < src_len; i++) {
1486 x = src[i];
1487 *q++ = duk_lc_digits[x >> 4];
1488 *q++ = duk_lc_digits[x & 0x0f];
1489 }
1490
1491 return q;
1492 }
1493 #else /* DUK_USE_HEX_FASTPATH */
1494 DUK_LOCAL duk_uint8_t *duk__json_enc_buffer_data_hex(const duk_uint8_t *src, duk_size_t src_len, duk_uint8_t *dst) {
1495 const duk_uint8_t *p;
1496 const duk_uint8_t *p_end;
1497 duk_uint8_t *q;
1498 duk_small_uint_t x;
1499
1500 p = src;
1501 p_end = src + src_len;
1502 q = dst;
1503 while (p != p_end) {
1504 x = *p++;
1505 *q++ = duk_lc_digits[x >> 4];
1506 *q++ = duk_lc_digits[x & 0x0f];
1507 }
1508
1509 return q;
1510 }
1511 #endif /* DUK_USE_HEX_FASTPATH */
1512
1513 DUK_LOCAL void duk__json_enc_buffer_data(duk_json_enc_ctx *js_ctx, duk_uint8_t *buf_data, duk_size_t buf_len) {
1514 duk_hthread *thr;
1515 duk_uint8_t *q;
1516 duk_size_t space;
1517
1518 thr = js_ctx->thr;
1519
1520 DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible); /* caller checks */
1521 DUK_ASSERT(js_ctx->flag_ext_custom_or_compatible);
1522
1523 /* Buffer values are encoded in (lowercase) hex to make the
1524 * binary data readable. Base64 or similar would be more
1525 * compact but less readable, and the point of JX/JC
1526 * variants is to be as useful to a programmer as possible.
1527 */
1528
1529 /* The #if defined() clutter here needs to handle the three
1530 * cases: (1) JX+JC, (2) JX only, (3) JC only.
1531 */
1532
1533 /* Note: space must cater for both JX and JC. */
1534 space = 9 + buf_len * 2 + 2;
1535 DUK_ASSERT(DUK_HBUFFER_MAX_BYTELEN <= 0x7ffffffeUL);
1536 DUK_ASSERT((space - 2) / 2 >= buf_len); /* overflow not possible, buffer limits */
1537 q = DUK_BW_ENSURE_GETPTR(thr, &js_ctx->bw, space);
1538
1539 #if defined(DUK_USE_JX) && defined(DUK_USE_JC)
1540 if (js_ctx->flag_ext_custom)
1541 #endif
1542 #if defined(DUK_USE_JX)
1543 {
1544 *q++ = DUK_ASC_PIPE;
1545 q = duk__json_enc_buffer_data_hex(buf_data, buf_len, q);
1546 *q++ = DUK_ASC_PIPE;
1547
1548 }
1549 #endif
1550 #if defined(DUK_USE_JX) && defined(DUK_USE_JC)
1551 else
1552 #endif
1553 #if defined(DUK_USE_JC)
1554 {
1555 DUK_ASSERT(js_ctx->flag_ext_compatible);
1556 duk_memcpy((void *) q, (const void *) "{\"_buf\":\"", 9); /* len: 9 */
1557 q += 9;
1558 q = duk__json_enc_buffer_data_hex(buf_data, buf_len, q);
1559 *q++ = DUK_ASC_DOUBLEQUOTE;
1560 *q++ = DUK_ASC_RCURLY;
1561 }
1562 #endif
1563
1564 DUK_BW_SET_PTR(thr, &js_ctx->bw, q);
1565 }
1566
1567 DUK_LOCAL void duk__json_enc_buffer_jx_jc(duk_json_enc_ctx *js_ctx, duk_hbuffer *h) {
1568 duk__json_enc_buffer_data(js_ctx,
1569 (duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(js_ctx->thr->heap, h),
1570 (duk_size_t) DUK_HBUFFER_GET_SIZE(h));
1571 }
1572 #endif /* DUK_USE_JX || DUK_USE_JC */
1573
1574 #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH)
1575 DUK_LOCAL void duk__json_enc_buffer_json_fastpath(duk_json_enc_ctx *js_ctx, duk_hbuffer *h) {
1576 duk_size_t i, n;
1577 const duk_uint8_t *buf;
1578 duk_uint8_t *q;
1579
1580 n = DUK_HBUFFER_GET_SIZE(h);
1581 if (n == 0) {
1582 DUK__EMIT_2(js_ctx, DUK_ASC_LCURLY, DUK_ASC_RCURLY);
1583 return;
1584 }
1585
1586 DUK__EMIT_1(js_ctx, DUK_ASC_LCURLY);
1587
1588 /* Maximum encoded length with 32-bit index: 1 + 10 + 2 + 3 + 1 + 1 = 18,
1589 * with 64-bit index: 1 + 20 + 2 + 3 + 1 + 1 = 28. 32 has some slack.
1590 *
1591 * Note that because the output buffer is reallocated from time to time,
1592 * side effects (such as finalizers) affecting the buffer 'h' must be
1593 * disabled. This is the case in the JSON.stringify() fast path.
1594 */
1595
1596 buf = (const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(js_ctx->thr->heap, h);
1597 if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) {
1598 for (i = 0; i < n; i++) {
1599 duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth + 1);
1600 q = DUK_BW_ENSURE_GETPTR(js_ctx->thr, &js_ctx->bw, 32);
1601 q += DUK_SPRINTF((char *) q, "\"%lu\": %u,", (unsigned long) i, (unsigned int) buf[i]);
1602 DUK_BW_SET_PTR(js_ctx->thr, &js_ctx->bw, q);
1603 }
1604 } else {
1605 q = DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw);
1606 for (i = 0; i < n; i++) {
1607 q = DUK_BW_ENSURE_RAW(js_ctx->thr, &js_ctx->bw, 32, q);
1608 q += DUK_SPRINTF((char *) q, "\"%lu\":%u,", (unsigned long) i, (unsigned int) buf[i]);
1609 }
1610 DUK_BW_SET_PTR(js_ctx->thr, &js_ctx->bw, q);
1611 }
1612 DUK__UNEMIT_1(js_ctx); /* eat trailing comma */
1613
1614 if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) {
1615 duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth);
1616 }
1617 DUK__EMIT_1(js_ctx, DUK_ASC_RCURLY);
1618 }
1619 #endif /* DUK_USE_JSON_STRINGIFY_FASTPATH */
1620
1621 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
1622 DUK_LOCAL void duk__json_enc_pointer(duk_json_enc_ctx *js_ctx, void *ptr) {
1623 char buf[64]; /* XXX: how to figure correct size? */
1624 const char *fmt;
1625
1626 DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible); /* caller checks */
1627 DUK_ASSERT(js_ctx->flag_ext_custom_or_compatible);
1628
1629 duk_memzero(buf, sizeof(buf));
1630
1631 /* The #if defined() clutter here needs to handle the three
1632 * cases: (1) JX+JC, (2) JX only, (3) JC only.
1633 */
1634 #if defined(DUK_USE_JX) && defined(DUK_USE_JC)
1635 if (js_ctx->flag_ext_custom)
1636 #endif
1637 #if defined(DUK_USE_JX)
1638 {
1639 fmt = ptr ? "(%p)" : "(null)";
1640 }
1641 #endif
1642 #if defined(DUK_USE_JX) && defined(DUK_USE_JC)
1643 else
1644 #endif
1645 #if defined(DUK_USE_JC)
1646 {
1647 DUK_ASSERT(js_ctx->flag_ext_compatible);
1648 fmt = ptr ? "{\"_ptr\":\"%p\"}" : "{\"_ptr\":\"null\"}";
1649 }
1650 #endif
1651
1652 /* When ptr == NULL, the format argument is unused. */
1653 DUK_SNPRINTF(buf, sizeof(buf) - 1, fmt, ptr); /* must not truncate */
1654 DUK__EMIT_CSTR(js_ctx, buf);
1655 }
1656 #endif /* DUK_USE_JX || DUK_USE_JC */
1657
1658 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
1659 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
1660 DUK_LOCAL void duk__json_enc_bufobj(duk_json_enc_ctx *js_ctx, duk_hbufobj *h_bufobj) {
1661 DUK_HBUFOBJ_ASSERT_VALID(h_bufobj);
1662
1663 if (h_bufobj->buf == NULL || !DUK_HBUFOBJ_VALID_SLICE(h_bufobj)) {
1664 DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL);
1665 } else {
1666 /* Handle both full and partial slice (as long as covered). */
1667 duk__json_enc_buffer_data(js_ctx,
1668 (duk_uint8_t *) DUK_HBUFOBJ_GET_SLICE_BASE(js_ctx->thr->heap, h_bufobj),
1669 (duk_size_t) h_bufobj->length);
1670 }
1671 }
1672 #endif /* DUK_USE_JX || DUK_USE_JC */
1673 #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */
1674
1675 /* Indent helper. Calling code relies on js_ctx->recursion_depth also being
1676 * directly related to indent depth.
1677 */
1678 #if defined(DUK_USE_PREFER_SIZE)
1679 DUK_LOCAL void duk__json_enc_newline_indent(duk_json_enc_ctx *js_ctx, duk_uint_t depth) {
1680 DUK_ASSERT(js_ctx->h_gap != NULL);
1681 DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap) > 0); /* caller guarantees */
1682
1683 DUK__EMIT_1(js_ctx, 0x0a);
1684 while (depth-- > 0) {
1685 DUK__EMIT_HSTR(js_ctx, js_ctx->h_gap);
1686 }
1687 }
1688 #else /* DUK_USE_PREFER_SIZE */
1689 DUK_LOCAL void duk__json_enc_newline_indent(duk_json_enc_ctx *js_ctx, duk_uint_t depth) {
1690 const duk_uint8_t *gap_data;
1691 duk_size_t gap_len;
1692 duk_size_t avail_bytes; /* bytes of indent available for copying */
1693 duk_size_t need_bytes; /* bytes of indent still needed */
1694 duk_uint8_t *p_start;
1695 duk_uint8_t *p;
1696
1697 DUK_ASSERT(js_ctx->h_gap != NULL);
1698 DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap) > 0); /* caller guarantees */
1699
1700 DUK__EMIT_1(js_ctx, 0x0a);
1701 if (DUK_UNLIKELY(depth == 0)) {
1702 return;
1703 }
1704
1705 /* To handle deeper indents efficiently, make use of copies we've
1706 * already emitted. In effect we can emit a sequence of 1, 2, 4,
1707 * 8, etc copies, and then finish the last run. Byte counters
1708 * avoid multiply with gap_len on every loop.
1709 */
1710
1711 gap_data = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(js_ctx->h_gap);
1712 gap_len = (duk_size_t) DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap);
1713 DUK_ASSERT(gap_len > 0);
1714
1715 need_bytes = gap_len * depth;
1716 p = DUK_BW_ENSURE_GETPTR(js_ctx->thr, &js_ctx->bw, need_bytes);
1717 p_start = p;
1718
1719 duk_memcpy((void *) p, (const void *) gap_data, (size_t) gap_len);
1720 p += gap_len;
1721 avail_bytes = gap_len;
1722 DUK_ASSERT(need_bytes >= gap_len);
1723 need_bytes -= gap_len;
1724
1725 while (need_bytes >= avail_bytes) {
1726 duk_memcpy((void *) p, (const void *) p_start, (size_t) avail_bytes);
1727 p += avail_bytes;
1728 need_bytes -= avail_bytes;
1729 avail_bytes <<= 1;
1730 }
1731
1732 DUK_ASSERT(need_bytes < avail_bytes); /* need_bytes may be zero */
1733 duk_memcpy((void *) p, (const void *) p_start, (size_t) need_bytes);
1734 p += need_bytes;
1735 /*avail_bytes += need_bytes*/
1736
1737 DUK_BW_SET_PTR(js_ctx->thr, &js_ctx->bw, p);
1738 }
1739 #endif /* DUK_USE_PREFER_SIZE */
1740
1741 /* Shared entry handling for object/array serialization. */
1742 DUK_LOCAL void duk__json_enc_objarr_entry(duk_json_enc_ctx *js_ctx, duk_idx_t *entry_top) {
1743 duk_hthread *thr = js_ctx->thr;
1744 duk_hobject *h_target;
1745 duk_uint_fast32_t i, n;
1746
1747 *entry_top = duk_get_top(thr);
1748
1749 duk_native_stack_check(thr);
1750 duk_require_stack(thr, DUK_JSON_ENC_REQSTACK);
1751
1752 /* Loop check using a hybrid approach: a fixed-size visited[] array
1753 * with overflow in a loop check object.
1754 */
1755
1756 h_target = duk_known_hobject(thr, -1); /* object or array */
1757
1758 n = js_ctx->recursion_depth;
1759 if (DUK_UNLIKELY(n > DUK_JSON_ENC_LOOPARRAY)) {
1760 n = DUK_JSON_ENC_LOOPARRAY;
1761 }
1762 for (i = 0; i < n; i++) {
1763 if (DUK_UNLIKELY(js_ctx->visiting[i] == h_target)) {
1764 DUK_DD(DUK_DDPRINT("slow path loop detect"));
1765 DUK_ERROR_TYPE(thr, DUK_STR_CYCLIC_INPUT);
1766 DUK_WO_NORETURN(return;);
1767 }
1768 }
1769 if (js_ctx->recursion_depth < DUK_JSON_ENC_LOOPARRAY) {
1770 js_ctx->visiting[js_ctx->recursion_depth] = h_target;
1771 } else {
1772 duk_push_sprintf(thr, DUK_STR_FMT_PTR, (void *) h_target);
1773 duk_dup_top(thr); /* -> [ ... voidp voidp ] */
1774 if (duk_has_prop(thr, js_ctx->idx_loop)) {
1775 DUK_ERROR_TYPE(thr, DUK_STR_CYCLIC_INPUT);
1776 DUK_WO_NORETURN(return;);
1777 }
1778 duk_push_true(thr); /* -> [ ... voidp true ] */
1779 duk_put_prop(thr, js_ctx->idx_loop); /* -> [ ... ] */
1780 }
1781
1782 /* C recursion check. */
1783
1784 DUK_ASSERT_DISABLE(js_ctx->recursion_depth >= 0); /* unsigned */
1785 DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
1786 if (js_ctx->recursion_depth >= js_ctx->recursion_limit) {
1787 DUK_ERROR_RANGE(thr, DUK_STR_ENC_RECLIMIT);
1788 DUK_WO_NORETURN(return;);
1789 }
1790 js_ctx->recursion_depth++;
1791
1792 DUK_DDD(DUK_DDDPRINT("shared entry finished: top=%ld, loop=%!T",
1793 (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, js_ctx->idx_loop)));
1794 }
1795
1796 /* Shared exit handling for object/array serialization. */
1797 DUK_LOCAL void duk__json_enc_objarr_exit(duk_json_enc_ctx *js_ctx, duk_idx_t *entry_top) {
1798 duk_hthread *thr = js_ctx->thr;
1799 duk_hobject *h_target;
1800
1801 /* C recursion check. */
1802
1803 DUK_ASSERT(js_ctx->recursion_depth > 0);
1804 DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
1805 js_ctx->recursion_depth--;
1806
1807 /* Loop check. */
1808
1809 h_target = duk_known_hobject(thr, *entry_top - 1); /* original target at entry_top - 1 */
1810
1811 if (js_ctx->recursion_depth < DUK_JSON_ENC_LOOPARRAY) {
1812 /* Previous entry was inside visited[], nothing to do. */
1813 } else {
1814 duk_push_sprintf(thr, DUK_STR_FMT_PTR, (void *) h_target);
1815 duk_del_prop(thr, js_ctx->idx_loop); /* -> [ ... ] */
1816 }
1817
1818 /* Restore stack top after unbalanced code paths. */
1819 duk_set_top(thr, *entry_top);
1820
1821 DUK_DDD(DUK_DDDPRINT("shared entry finished: top=%ld, loop=%!T",
1822 (long) duk_get_top(thr), (duk_tval *) duk_get_tval(thr, js_ctx->idx_loop)));
1823 }
1824
1825 /* The JO(value) operation: encode object.
1826 *
1827 * Stack policy: [ object ] -> [ object ].
1828 */
1829 DUK_LOCAL void duk__json_enc_object(duk_json_enc_ctx *js_ctx) {
1830 duk_hthread *thr = js_ctx->thr;
1831 duk_hstring *h_key;
1832 duk_idx_t entry_top;
1833 duk_idx_t idx_obj;
1834 duk_idx_t idx_keys;
1835 duk_bool_t emitted;
1836 duk_uarridx_t arr_len, i;
1837 duk_size_t prev_size;
1838
1839 DUK_DDD(DUK_DDDPRINT("duk__json_enc_object: obj=%!T", (duk_tval *) duk_get_tval(thr, -1)));
1840
1841 duk__json_enc_objarr_entry(js_ctx, &entry_top);
1842
1843 idx_obj = entry_top - 1;
1844
1845 if (js_ctx->idx_proplist >= 0) {
1846 idx_keys = js_ctx->idx_proplist;
1847 } else {
1848 /* XXX: would be nice to enumerate an object at specified index */
1849 duk_dup(thr, idx_obj);
1850 (void) duk_hobject_get_enumerated_keys(thr, DUK_ENUM_OWN_PROPERTIES_ONLY /*flags*/); /* [ ... target ] -> [ ... target keys ] */
1851 idx_keys = duk_require_normalize_index(thr, -1);
1852 /* leave stack unbalanced on purpose */
1853 }
1854
1855 DUK_DDD(DUK_DDDPRINT("idx_keys=%ld, h_keys=%!T",
1856 (long) idx_keys, (duk_tval *) duk_get_tval(thr, idx_keys)));
1857
1858 /* Steps 8-10 have been merged to avoid a "partial" variable. */
1859
1860 DUK__EMIT_1(js_ctx, DUK_ASC_LCURLY);
1861
1862 /* XXX: keys is an internal object with all keys to be processed
1863 * in its (gapless) array part. Because nobody can touch the keys
1864 * object, we could iterate its array part directly (keeping in mind
1865 * that it can be reallocated).
1866 */
1867
1868 arr_len = (duk_uarridx_t) duk_get_length(thr, idx_keys);
1869 emitted = 0;
1870 for (i = 0; i < arr_len; i++) {
1871 duk_get_prop_index(thr, idx_keys, i); /* -> [ ... key ] */
1872
1873 DUK_DDD(DUK_DDDPRINT("object property loop: holder=%!T, key=%!T",
1874 (duk_tval *) duk_get_tval(thr, idx_obj),
1875 (duk_tval *) duk_get_tval(thr, -1)));
1876
1877 h_key = duk_known_hstring(thr, -1);
1878 DUK_ASSERT(h_key != NULL);
1879 DUK_ASSERT(!DUK_HSTRING_HAS_SYMBOL(h_key)); /* proplist filtering; enum options */
1880
1881 prev_size = DUK_BW_GET_SIZE(js_ctx->thr, &js_ctx->bw);
1882 if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) {
1883 duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth);
1884 duk__json_enc_key_autoquote(js_ctx, h_key);
1885 DUK__EMIT_2(js_ctx, DUK_ASC_COLON, DUK_ASC_SPACE);
1886 } else {
1887 duk__json_enc_key_autoquote(js_ctx, h_key);
1888 DUK__EMIT_1(js_ctx, DUK_ASC_COLON);
1889 }
1890
1891 /* [ ... key ] */
1892
1893 if (DUK_UNLIKELY(duk__json_enc_value(js_ctx, idx_obj) == 0)) {
1894 /* Value would yield 'undefined', so skip key altogether.
1895 * Side effects have already happened.
1896 */
1897 DUK_BW_SET_SIZE(js_ctx->thr, &js_ctx->bw, prev_size);
1898 } else {
1899 DUK__EMIT_1(js_ctx, DUK_ASC_COMMA);
1900 emitted = 1;
1901 }
1902
1903 /* [ ... ] */
1904 }
1905
1906 if (emitted) {
1907 DUK_ASSERT(*((duk_uint8_t *) DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw) - 1) == DUK_ASC_COMMA);
1908 DUK__UNEMIT_1(js_ctx); /* eat trailing comma */
1909 if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) {
1910 DUK_ASSERT(js_ctx->recursion_depth >= 1);
1911 duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth - 1U);
1912 }
1913 }
1914 DUK__EMIT_1(js_ctx, DUK_ASC_RCURLY);
1915
1916 duk__json_enc_objarr_exit(js_ctx, &entry_top);
1917
1918 DUK_ASSERT_TOP(thr, entry_top);
1919 }
1920
1921 /* The JA(value) operation: encode array.
1922 *
1923 * Stack policy: [ array ] -> [ array ].
1924 */
1925 DUK_LOCAL void duk__json_enc_array(duk_json_enc_ctx *js_ctx) {
1926 duk_hthread *thr = js_ctx->thr;
1927 duk_idx_t entry_top;
1928 duk_idx_t idx_arr;
1929 duk_bool_t emitted;
1930 duk_uarridx_t i, arr_len;
1931
1932 DUK_DDD(DUK_DDDPRINT("duk__json_enc_array: array=%!T",
1933 (duk_tval *) duk_get_tval(thr, -1)));
1934
1935 duk__json_enc_objarr_entry(js_ctx, &entry_top);
1936
1937 idx_arr = entry_top - 1;
1938
1939 /* Steps 8-10 have been merged to avoid a "partial" variable. */
1940
1941 DUK__EMIT_1(js_ctx, DUK_ASC_LBRACKET);
1942
1943 arr_len = (duk_uarridx_t) duk_get_length(thr, idx_arr);
1944 emitted = 0;
1945 for (i = 0; i < arr_len; i++) {
1946 DUK_DDD(DUK_DDDPRINT("array entry loop: array=%!T, index=%ld, arr_len=%ld",
1947 (duk_tval *) duk_get_tval(thr, idx_arr),
1948 (long) i, (long) arr_len));
1949
1950 if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) {
1951 DUK_ASSERT(js_ctx->recursion_depth >= 1);
1952 duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth);
1953 }
1954
1955 (void) duk_push_uint_to_hstring(thr, (duk_uint_t) i); /* -> [ ... key ] */
1956
1957 /* [ ... key ] */
1958
1959 if (DUK_UNLIKELY(duk__json_enc_value(js_ctx, idx_arr) == 0)) {
1960 /* Value would normally be omitted, replace with 'null'. */
1961 DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL);
1962 } else {
1963 ;
1964 }
1965
1966 /* [ ... ] */
1967
1968 DUK__EMIT_1(js_ctx, DUK_ASC_COMMA);
1969 emitted = 1;
1970 }
1971
1972 if (emitted) {
1973 DUK_ASSERT(*((duk_uint8_t *) DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw) - 1) == DUK_ASC_COMMA);
1974 DUK__UNEMIT_1(js_ctx); /* eat trailing comma */
1975 if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) {
1976 DUK_ASSERT(js_ctx->recursion_depth >= 1);
1977 duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth - 1U);
1978 }
1979 }
1980 DUK__EMIT_1(js_ctx, DUK_ASC_RBRACKET);
1981
1982 duk__json_enc_objarr_exit(js_ctx, &entry_top);
1983
1984 DUK_ASSERT_TOP(thr, entry_top);
1985 }
1986
1987 /* The Str(key, holder) operation.
1988 *
1989 * Stack policy: [ ... key ] -> [ ... ]
1990 */
1991 DUK_LOCAL duk_bool_t duk__json_enc_value(duk_json_enc_ctx *js_ctx, duk_idx_t idx_holder) {
1992 duk_hthread *thr = js_ctx->thr;
1993 duk_tval *tv;
1994 duk_tval *tv_holder;
1995 duk_tval *tv_key;
1996 duk_small_int_t c;
1997
1998 DUK_DDD(DUK_DDDPRINT("duk__json_enc_value: idx_holder=%ld, holder=%!T, key=%!T",
1999 (long) idx_holder, (duk_tval *) duk_get_tval(thr, idx_holder),
2000 (duk_tval *) duk_get_tval(thr, -1)));
2001
2002 tv_holder = DUK_GET_TVAL_POSIDX(thr, idx_holder);
2003 DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_holder));
2004 tv_key = DUK_GET_TVAL_NEGIDX(thr, -1);
2005 DUK_ASSERT(DUK_TVAL_IS_STRING(tv_key));
2006 DUK_ASSERT(!DUK_HSTRING_HAS_SYMBOL(DUK_TVAL_GET_STRING(tv_key))); /* Caller responsible. */
2007 (void) duk_hobject_getprop(thr, tv_holder, tv_key);
2008
2009 /* -> [ ... key val ] */
2010
2011 DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(thr, -1)));
2012
2013 /* Standard JSON checks for .toJSON() only for actual objects; for
2014 * example, setting Number.prototype.toJSON and then serializing a
2015 * number won't invoke the .toJSON() method. However, lightfuncs and
2016 * plain buffers mimic objects so we check for their .toJSON() method.
2017 */
2018 if (duk_check_type_mask(thr, -1, DUK_TYPE_MASK_OBJECT |
2019 DUK_TYPE_MASK_LIGHTFUNC |
2020 DUK_TYPE_MASK_BUFFER)) {
2021 duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_TO_JSON);
2022 if (duk_is_callable(thr, -1)) { /* toJSON() can also be a lightfunc */
2023 DUK_DDD(DUK_DDDPRINT("value is object, has callable toJSON() -> call it"));
2024 /* XXX: duk_dup_unvalidated(thr, -2) etc. */
2025 duk_dup_m2(thr); /* -> [ ... key val toJSON val ] */
2026 duk_dup_m4(thr); /* -> [ ... key val toJSON val key ] */
2027 duk_call_method(thr, 1); /* -> [ ... key val val' ] */
2028 duk_remove_m2(thr); /* -> [ ... key val' ] */
2029 } else {
2030 duk_pop(thr); /* -> [ ... key val ] */
2031 }
2032 }
2033
2034 /* [ ... key val ] */
2035
2036 DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(thr, -1)));
2037
2038 if (js_ctx->h_replacer) {
2039 /* XXX: Here a "slice copy" would be useful. */
2040 DUK_DDD(DUK_DDDPRINT("replacer is set, call replacer"));
2041 duk_push_hobject(thr, js_ctx->h_replacer); /* -> [ ... key val replacer ] */
2042 duk_dup(thr, idx_holder); /* -> [ ... key val replacer holder ] */
2043 duk_dup_m4(thr); /* -> [ ... key val replacer holder key ] */
2044 duk_dup_m4(thr); /* -> [ ... key val replacer holder key val ] */
2045 duk_call_method(thr, 2); /* -> [ ... key val val' ] */
2046 duk_remove_m2(thr); /* -> [ ... key val' ] */
2047 }
2048
2049 /* [ ... key val ] */
2050
2051 DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(thr, -1)));
2052
2053 tv = DUK_GET_TVAL_NEGIDX(thr, -1);
2054 if (DUK_TVAL_IS_OBJECT(tv)) {
2055 duk_hobject *h;
2056
2057 h = DUK_TVAL_GET_OBJECT(tv);
2058 DUK_ASSERT(h != NULL);
2059
2060 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
2061 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2062 if (DUK_HOBJECT_IS_BUFOBJ(h) &&
2063 js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE)) {
2064 /* With JX/JC a bufferobject gets serialized specially. */
2065 duk_hbufobj *h_bufobj;
2066 h_bufobj = (duk_hbufobj *) h;
2067 DUK_HBUFOBJ_ASSERT_VALID(h_bufobj);
2068 duk__json_enc_bufobj(js_ctx, h_bufobj);
2069 goto pop2_emitted;
2070 }
2071 /* Otherwise bufferobjects get serialized as normal objects. */
2072 #endif /* JX || JC */
2073 #endif /* DUK_USE_BUFFEROBJECT_SUPPORT */
2074 c = (duk_small_int_t) DUK_HOBJECT_GET_CLASS_NUMBER(h);
2075 switch (c) {
2076 case DUK_HOBJECT_CLASS_NUMBER: {
2077 DUK_DDD(DUK_DDDPRINT("value is a Number object -> coerce with ToNumber()"));
2078 duk_to_number_m1(thr);
2079 /* The coercion potentially invokes user .valueOf() and .toString()
2080 * but can't result in a function value because ToPrimitive() would
2081 * reject such a result: test-dev-json-stringify-coercion-1.js.
2082 */
2083 DUK_ASSERT(!duk_is_callable(thr, -1));
2084 break;
2085 }
2086 case DUK_HOBJECT_CLASS_STRING: {
2087 DUK_DDD(DUK_DDDPRINT("value is a String object -> coerce with ToString()"));
2088 duk_to_string(thr, -1);
2089 /* Same coercion behavior as for Number. */
2090 DUK_ASSERT(!duk_is_callable(thr, -1));
2091 break;
2092 }
2093 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2094 case DUK_HOBJECT_CLASS_POINTER:
2095 #endif
2096 case DUK_HOBJECT_CLASS_BOOLEAN: {
2097 DUK_DDD(DUK_DDDPRINT("value is a Boolean/Buffer/Pointer object -> get internal value"));
2098 duk_xget_owndataprop_stridx_short(thr, -1, DUK_STRIDX_INT_VALUE);
2099 duk_remove_m2(thr);
2100 break;
2101 }
2102 default: {
2103 /* Normal object which doesn't get automatically coerced to a
2104 * primitive value. Functions are checked for specially. The
2105 * primitive value coercions for Number, String, Pointer, and
2106 * Boolean can't result in functions so suffices to check here.
2107 * Symbol objects are handled like plain objects (their primitive
2108 * value is NOT looked up like for e.g. String objects).
2109 */
2110 DUK_ASSERT(h != NULL);
2111 if (DUK_HOBJECT_IS_CALLABLE(h)) {
2112 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2113 if (js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM |
2114 DUK_JSON_FLAG_EXT_COMPATIBLE)) {
2115 /* We only get here when doing non-standard JSON encoding */
2116 DUK_DDD(DUK_DDDPRINT("-> function allowed, serialize to custom format"));
2117 DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible);
2118 DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_function);
2119 goto pop2_emitted;
2120 } else {
2121 DUK_DDD(DUK_DDDPRINT("-> will result in undefined (function)"));
2122 goto pop2_undef;
2123 }
2124 #else /* DUK_USE_JX || DUK_USE_JC */
2125 DUK_DDD(DUK_DDDPRINT("-> will result in undefined (function)"));
2126 goto pop2_undef;
2127 #endif /* DUK_USE_JX || DUK_USE_JC */
2128 }
2129 }
2130 } /* end switch */
2131 }
2132
2133 /* [ ... key val ] */
2134
2135 DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval *) duk_get_tval(thr, -1)));
2136
2137 if (duk_check_type_mask(thr, -1, js_ctx->mask_for_undefined)) {
2138 /* will result in undefined */
2139 DUK_DDD(DUK_DDDPRINT("-> will result in undefined (type mask check)"));
2140 goto pop2_undef;
2141 }
2142 tv = DUK_GET_TVAL_NEGIDX(thr, -1);
2143
2144 switch (DUK_TVAL_GET_TAG(tv)) {
2145 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2146 /* When JX/JC not in use, the type mask above will avoid this case if needed. */
2147 case DUK_TAG_UNDEFINED: {
2148 DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_undefined);
2149 break;
2150 }
2151 #endif
2152 case DUK_TAG_NULL: {
2153 DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL);
2154 break;
2155 }
2156 case DUK_TAG_BOOLEAN: {
2157 DUK__EMIT_STRIDX(js_ctx, DUK_TVAL_GET_BOOLEAN(tv) ?
2158 DUK_STRIDX_TRUE : DUK_STRIDX_FALSE);
2159 break;
2160 }
2161 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2162 /* When JX/JC not in use, the type mask above will avoid this case if needed. */
2163 case DUK_TAG_POINTER: {
2164 duk__json_enc_pointer(js_ctx, DUK_TVAL_GET_POINTER(tv));
2165 break;
2166 }
2167 #endif /* DUK_USE_JX || DUK_USE_JC */
2168 case DUK_TAG_STRING: {
2169 duk_hstring *h = DUK_TVAL_GET_STRING(tv);
2170 DUK_ASSERT(h != NULL);
2171 if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
2172 goto pop2_undef;
2173 }
2174 duk__json_enc_quote_string(js_ctx, h);
2175 break;
2176 }
2177 case DUK_TAG_OBJECT: {
2178 duk_hobject *h = DUK_TVAL_GET_OBJECT(tv);
2179 DUK_ASSERT(h != NULL);
2180
2181 /* Function values are handled completely above (including
2182 * coercion results):
2183 */
2184 DUK_ASSERT(!DUK_HOBJECT_IS_CALLABLE(h));
2185
2186 if (duk_js_isarray_hobject(h)) {
2187 duk__json_enc_array(js_ctx);
2188 } else {
2189 duk__json_enc_object(js_ctx);
2190 }
2191 break;
2192 }
2193 /* Because plain buffers mimics Uint8Array, they have enumerable
2194 * index properties [0,byteLength[. Because JSON only serializes
2195 * enumerable own properties, no properties can be serialized for
2196 * plain buffers (all virtual properties are non-enumerable). However,
2197 * there may be a .toJSON() method which was already handled above.
2198 */
2199 case DUK_TAG_BUFFER: {
2200 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2201 if (js_ctx->flag_ext_custom_or_compatible) {
2202 duk__json_enc_buffer_jx_jc(js_ctx, DUK_TVAL_GET_BUFFER(tv));
2203 break;
2204 }
2205 #endif
2206
2207 /* Could implement a fastpath, but the fast path would need
2208 * to handle realloc side effects correctly.
2209 */
2210 duk_to_object(thr, -1);
2211 duk__json_enc_object(js_ctx);
2212 break;
2213 }
2214 case DUK_TAG_LIGHTFUNC: {
2215 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2216 /* We only get here when doing non-standard JSON encoding */
2217 DUK_ASSERT(js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible);
2218 DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_function);
2219 #else
2220 /* Standard JSON omits functions */
2221 DUK_UNREACHABLE();
2222 #endif
2223 break;
2224 }
2225 #if defined(DUK_USE_FASTINT)
2226 case DUK_TAG_FASTINT:
2227 /* Number serialization has a significant impact relative to
2228 * other fast path code, so careful fast path for fastints.
2229 */
2230 duk__json_enc_fastint_tval(js_ctx, tv);
2231 break;
2232 #endif
2233 default: {
2234 /* number */
2235 DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv));
2236 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
2237 /* XXX: A fast path for usual integers would be useful when
2238 * fastint support is not enabled.
2239 */
2240 duk__json_enc_double(js_ctx);
2241 break;
2242 }
2243 }
2244
2245 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2246 pop2_emitted:
2247 #endif
2248 duk_pop_2(thr); /* [ ... key val ] -> [ ... ] */
2249 return 1; /* emitted */
2250
2251 pop2_undef:
2252 duk_pop_2(thr); /* [ ... key val ] -> [ ... ] */
2253 return 0; /* not emitted */
2254 }
2255
2256 /* E5 Section 15.12.3, main algorithm, step 4.b.ii steps 1-4. */
2257 DUK_LOCAL duk_bool_t duk__json_enc_allow_into_proplist(duk_tval *tv) {
2258 duk_small_int_t c;
2259
2260 /* XXX: some kind of external internal type checker?
2261 * - type mask; symbol flag; class mask
2262 */
2263 DUK_ASSERT(tv != NULL);
2264 if (DUK_TVAL_IS_STRING(tv)) {
2265 duk_hstring *h;
2266 h = DUK_TVAL_GET_STRING(tv);
2267 DUK_ASSERT(h != NULL);
2268 if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
2269 return 0;
2270 }
2271 return 1;
2272 } else if (DUK_TVAL_IS_NUMBER(tv)) {
2273 return 1;
2274 } else if (DUK_TVAL_IS_OBJECT(tv)) {
2275 duk_hobject *h;
2276 h = DUK_TVAL_GET_OBJECT(tv);
2277 DUK_ASSERT(h != NULL);
2278 c = (duk_small_int_t) DUK_HOBJECT_GET_CLASS_NUMBER(h);
2279 if (c == DUK_HOBJECT_CLASS_STRING || c == DUK_HOBJECT_CLASS_NUMBER) {
2280 return 1;
2281 }
2282 }
2283
2284 return 0;
2285 }
2286
2287 /*
2288 * JSON.stringify() fast path
2289 *
2290 * Otherwise supports full JSON, JX, and JC features, but bails out on any
2291 * possible side effect which might change the value being serialized. The
2292 * fast path can take advantage of the fact that the value being serialized
2293 * is unchanged so that we can walk directly through property tables etc.
2294 */
2295
2296 #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH)
2297 DUK_LOCAL duk_bool_t duk__json_stringify_fast_value(duk_json_enc_ctx *js_ctx, duk_tval *tv) {
2298 duk_uint_fast32_t i, n;
2299
2300 DUK_DDD(DUK_DDDPRINT("stringify fast: %!T", tv));
2301
2302 DUK_ASSERT(js_ctx != NULL);
2303 DUK_ASSERT(js_ctx->thr != NULL);
2304
2305 #if 0 /* disabled for now */
2306 restart_match:
2307 #endif
2308
2309 DUK_ASSERT(tv != NULL);
2310
2311 switch (DUK_TVAL_GET_TAG(tv)) {
2312 case DUK_TAG_UNDEFINED: {
2313 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2314 if (js_ctx->flag_ext_custom || js_ctx->flag_ext_compatible) {
2315 DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_undefined);
2316 break;
2317 } else {
2318 goto emit_undefined;
2319 }
2320 #else
2321 goto emit_undefined;
2322 #endif
2323 }
2324 case DUK_TAG_NULL: {
2325 DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL);
2326 break;
2327 }
2328 case DUK_TAG_BOOLEAN: {
2329 DUK__EMIT_STRIDX(js_ctx, DUK_TVAL_GET_BOOLEAN(tv) ?
2330 DUK_STRIDX_TRUE : DUK_STRIDX_FALSE);
2331 break;
2332 }
2333 case DUK_TAG_STRING: {
2334 duk_hstring *h;
2335 h = DUK_TVAL_GET_STRING(tv);
2336 DUK_ASSERT(h != NULL);
2337 if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(h))) {
2338 goto emit_undefined;
2339 }
2340 duk__json_enc_quote_string(js_ctx, h);
2341 break;
2342 }
2343 case DUK_TAG_OBJECT: {
2344 duk_hobject *obj;
2345 duk_tval *tv_val;
2346 duk_bool_t emitted = 0;
2347 duk_uint32_t c_bit, c_all, c_array, c_unbox, c_undef,
2348 c_func, c_bufobj, c_object, c_abort;
2349
2350 /* For objects JSON.stringify() only looks for own, enumerable
2351 * properties which is nice for the fast path here.
2352 *
2353 * For arrays JSON.stringify() uses [[Get]] so it will actually
2354 * inherit properties during serialization! This fast path
2355 * supports gappy arrays as long as there's no actual inherited
2356 * property (which might be a getter etc).
2357 *
2358 * Since recursion only happens for objects, we can have both
2359 * recursion and loop checks here. We use a simple, depth-limited
2360 * loop check in the fast path because the object-based tracking
2361 * is very slow (when tested, it accounted for 50% of fast path
2362 * execution time for input data with a lot of small objects!).
2363 */
2364
2365 /* XXX: for real world code, could just ignore array inheritance
2366 * and only look at array own properties.
2367 */
2368
2369 /* We rely on a few object flag / class number relationships here,
2370 * assert for them.
2371 */
2372
2373 obj = DUK_TVAL_GET_OBJECT(tv);
2374 DUK_ASSERT(obj != NULL);
2375 DUK_HOBJECT_ASSERT_VALID(obj);
2376
2377 /* Once recursion depth is increased, exit path must decrease
2378 * it (though it's OK to abort the fast path).
2379 */
2380
2381 DUK_ASSERT_DISABLE(js_ctx->recursion_depth >= 0); /* unsigned */
2382 DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
2383 if (js_ctx->recursion_depth >= js_ctx->recursion_limit) {
2384 DUK_DD(DUK_DDPRINT("fast path recursion limit"));
2385 DUK_ERROR_RANGE(js_ctx->thr, DUK_STR_DEC_RECLIMIT);
2386 DUK_WO_NORETURN(return 0;);
2387 }
2388
2389 for (i = 0, n = (duk_uint_fast32_t) js_ctx->recursion_depth; i < n; i++) {
2390 if (DUK_UNLIKELY(js_ctx->visiting[i] == obj)) {
2391 DUK_DD(DUK_DDPRINT("fast path loop detect"));
2392 DUK_ERROR_TYPE(js_ctx->thr, DUK_STR_CYCLIC_INPUT);
2393 DUK_WO_NORETURN(return 0;);
2394 }
2395 }
2396
2397 /* Guaranteed by recursion_limit setup so we don't have to
2398 * check twice.
2399 */
2400 DUK_ASSERT(js_ctx->recursion_depth < DUK_JSON_ENC_LOOPARRAY);
2401 js_ctx->visiting[js_ctx->recursion_depth] = obj;
2402 js_ctx->recursion_depth++;
2403
2404 /* If object has a .toJSON() property, we can't be certain
2405 * that it wouldn't mutate any value arbitrarily, so bail
2406 * out of the fast path.
2407 *
2408 * If an object is a Proxy we also can't avoid side effects
2409 * so abandon.
2410 */
2411 /* XXX: non-callable .toJSON() doesn't need to cause an abort
2412 * but does at the moment, probably not worth fixing.
2413 */
2414 if (duk_hobject_hasprop_raw(js_ctx->thr, obj, DUK_HTHREAD_STRING_TO_JSON(js_ctx->thr)) ||
2415 DUK_HOBJECT_IS_PROXY(obj)) {
2416 DUK_DD(DUK_DDPRINT("object has a .toJSON property or object is a Proxy, abort fast path"));
2417 goto abort_fastpath;
2418 }
2419
2420 /* We could use a switch-case for the class number but it turns out
2421 * a small if-else ladder on class masks is better. The if-ladder
2422 * should be in order of relevancy.
2423 */
2424
2425 /* XXX: move masks to js_ctx? they don't change during one
2426 * fast path invocation.
2427 */
2428 DUK_ASSERT(DUK_HOBJECT_CLASS_MAX <= 31);
2429 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2430 if (js_ctx->flag_ext_custom_or_compatible) {
2431 c_all = DUK_HOBJECT_CMASK_ALL;
2432 c_array = DUK_HOBJECT_CMASK_ARRAY;
2433 c_unbox = DUK_HOBJECT_CMASK_NUMBER |
2434 DUK_HOBJECT_CMASK_STRING |
2435 DUK_HOBJECT_CMASK_BOOLEAN |
2436 DUK_HOBJECT_CMASK_POINTER; /* Symbols are not unboxed. */
2437 c_func = DUK_HOBJECT_CMASK_FUNCTION;
2438 c_bufobj = DUK_HOBJECT_CMASK_ALL_BUFOBJS;
2439 c_undef = 0;
2440 c_abort = 0;
2441 c_object = c_all & ~(c_array | c_unbox | c_func | c_bufobj | c_undef | c_abort);
2442 }
2443 else
2444 #endif
2445 {
2446 c_all = DUK_HOBJECT_CMASK_ALL;
2447 c_array = DUK_HOBJECT_CMASK_ARRAY;
2448 c_unbox = DUK_HOBJECT_CMASK_NUMBER |
2449 DUK_HOBJECT_CMASK_STRING |
2450 DUK_HOBJECT_CMASK_BOOLEAN; /* Symbols are not unboxed. */
2451 c_func = 0;
2452 c_bufobj = 0;
2453 c_undef = DUK_HOBJECT_CMASK_FUNCTION |
2454 DUK_HOBJECT_CMASK_POINTER;
2455 /* As the fast path doesn't currently properly support
2456 * duk_hbufobj virtual properties, abort fast path if
2457 * we encounter them in plain JSON mode.
2458 */
2459 c_abort = DUK_HOBJECT_CMASK_ALL_BUFOBJS;
2460 c_object = c_all & ~(c_array | c_unbox | c_func | c_bufobj | c_undef | c_abort);
2461 }
2462
2463 c_bit = (duk_uint32_t) DUK_HOBJECT_GET_CLASS_MASK(obj);
2464 if (c_bit & c_object) {
2465 /* All other object types. */
2466 DUK__EMIT_1(js_ctx, DUK_ASC_LCURLY);
2467
2468 /* A non-Array object should not have an array part in practice.
2469 * But since it is supported internally (and perhaps used at some
2470 * point), check and abandon if that's the case.
2471 */
2472 if (DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
2473 DUK_DD(DUK_DDPRINT("non-Array object has array part, abort fast path"));
2474 goto abort_fastpath;
2475 }
2476
2477 for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(obj); i++) {
2478 duk_hstring *k;
2479 duk_size_t prev_size;
2480
2481 k = DUK_HOBJECT_E_GET_KEY(js_ctx->thr->heap, obj, i);
2482 if (!k) {
2483 continue;
2484 }
2485 if (DUK_HSTRING_HAS_ARRIDX(k)) {
2486 /* If an object has array index keys we would need
2487 * to sort them into the ES2015 enumeration order to
2488 * be consistent with the slow path. Abort the fast
2489 * path and handle in the slow path for now.
2490 */
2491 DUK_DD(DUK_DDPRINT("property key is an array index, abort fast path"));
2492 goto abort_fastpath;
2493 }
2494 if (!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(js_ctx->thr->heap, obj, i)) {
2495 continue;
2496 }
2497 if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(js_ctx->thr->heap, obj, i)) {
2498 /* Getter might have arbitrary side effects,
2499 * so bail out.
2500 */
2501 DUK_DD(DUK_DDPRINT("property is an accessor, abort fast path"));
2502 goto abort_fastpath;
2503 }
2504 if (DUK_UNLIKELY(DUK_HSTRING_HAS_SYMBOL(k))) {
2505 continue;
2506 }
2507
2508 tv_val = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(js_ctx->thr->heap, obj, i);
2509
2510 prev_size = DUK_BW_GET_SIZE(js_ctx->thr, &js_ctx->bw);
2511 if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) {
2512 duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth);
2513 duk__json_enc_key_autoquote(js_ctx, k);
2514 DUK__EMIT_2(js_ctx, DUK_ASC_COLON, DUK_ASC_SPACE);
2515 } else {
2516 duk__json_enc_key_autoquote(js_ctx, k);
2517 DUK__EMIT_1(js_ctx, DUK_ASC_COLON);
2518 }
2519
2520 if (duk__json_stringify_fast_value(js_ctx, tv_val) == 0) {
2521 DUK_DD(DUK_DDPRINT("prop value not supported, rewind key and colon"));
2522 DUK_BW_SET_SIZE(js_ctx->thr, &js_ctx->bw, prev_size);
2523 } else {
2524 DUK__EMIT_1(js_ctx, DUK_ASC_COMMA);
2525 emitted = 1;
2526 }
2527 }
2528
2529 /* If any non-Array value had enumerable virtual own
2530 * properties, they should be serialized here (actually,
2531 * before the explicit properties). Standard types don't.
2532 */
2533
2534 if (emitted) {
2535 DUK_ASSERT(*((duk_uint8_t *) DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw) - 1) == DUK_ASC_COMMA);
2536 DUK__UNEMIT_1(js_ctx); /* eat trailing comma */
2537 if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) {
2538 DUK_ASSERT(js_ctx->recursion_depth >= 1);
2539 duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth - 1U);
2540 }
2541 }
2542 DUK__EMIT_1(js_ctx, DUK_ASC_RCURLY);
2543 } else if (c_bit & c_array) {
2544 duk_uint_fast32_t arr_len;
2545 duk_uint_fast32_t asize;
2546
2547 DUK__EMIT_1(js_ctx, DUK_ASC_LBRACKET);
2548
2549 /* Assume arrays are dense in the fast path. */
2550 if (!DUK_HOBJECT_HAS_ARRAY_PART(obj)) {
2551 DUK_DD(DUK_DDPRINT("Array object is sparse, abort fast path"));
2552 goto abort_fastpath;
2553 }
2554
2555 arr_len = (duk_uint_fast32_t) ((duk_harray *) obj)->length;
2556 asize = (duk_uint_fast32_t) DUK_HOBJECT_GET_ASIZE(obj);
2557 /* Array part may be larger than 'length'; if so, iterate
2558 * only up to array 'length'. Array part may also be smaller
2559 * than 'length' in some cases.
2560 */
2561 for (i = 0; i < arr_len; i++) {
2562 duk_tval *tv_arrval;
2563 duk_hstring *h_tmp;
2564 duk_bool_t has_inherited;
2565
2566 if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) {
2567 duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth);
2568 }
2569
2570 if (DUK_LIKELY(i < asize)) {
2571 tv_arrval = DUK_HOBJECT_A_GET_VALUE_PTR(js_ctx->thr->heap, obj, i);
2572 if (DUK_LIKELY(!DUK_TVAL_IS_UNUSED(tv_arrval))) {
2573 /* Expected case: element is present. */
2574 if (duk__json_stringify_fast_value(js_ctx, tv_arrval) == 0) {
2575 DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL);
2576 }
2577 goto elem_done;
2578 }
2579 }
2580
2581 /* Gap in array; check for inherited property,
2582 * bail out if one exists. This should be enough
2583 * to support gappy arrays for all practical code.
2584 */
2585
2586 h_tmp = duk_push_uint_to_hstring(js_ctx->thr, (duk_uint_t) i);
2587 has_inherited = duk_hobject_hasprop_raw(js_ctx->thr, obj, h_tmp);
2588 duk_pop(js_ctx->thr);
2589 if (has_inherited) {
2590 DUK_D(DUK_DPRINT("gap in array, conflicting inherited property, abort fast path"));
2591 goto abort_fastpath;
2592 }
2593
2594 /* Ordinary gap, undefined encodes to 'null' in
2595 * standard JSON, but JX/JC use their form for
2596 * undefined to better preserve the typing.
2597 */
2598 DUK_D(DUK_DPRINT("gap in array, no conflicting inherited property, remain on fast path"));
2599 #if defined(DUK_USE_JX)
2600 DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_undefined);
2601 #else
2602 DUK__EMIT_STRIDX(js_ctx, DUK_STRIDX_LC_NULL);
2603 #endif
2604 /* fall through */
2605
2606 elem_done:
2607 DUK__EMIT_1(js_ctx, DUK_ASC_COMMA);
2608 emitted = 1;
2609 }
2610
2611 if (emitted) {
2612 DUK_ASSERT(*((duk_uint8_t *) DUK_BW_GET_PTR(js_ctx->thr, &js_ctx->bw) - 1) == DUK_ASC_COMMA);
2613 DUK__UNEMIT_1(js_ctx); /* eat trailing comma */
2614 if (DUK_UNLIKELY(js_ctx->h_gap != NULL)) {
2615 DUK_ASSERT(js_ctx->recursion_depth >= 1);
2616 duk__json_enc_newline_indent(js_ctx, js_ctx->recursion_depth - 1U);
2617 }
2618 }
2619 DUK__EMIT_1(js_ctx, DUK_ASC_RBRACKET);
2620 } else if (c_bit & c_unbox) {
2621 /* Certain boxed types are required to go through
2622 * automatic unboxing. Rely on internal value being
2623 * sane (to avoid infinite recursion).
2624 */
2625 DUK_ASSERT((c_bit & DUK_HOBJECT_CMASK_SYMBOL) == 0); /* Symbols are not unboxed. */
2626
2627 #if 1
2628 /* The code below is incorrect if .toString() or .valueOf() have
2629 * have been overridden. The correct approach would be to look up
2630 * the method(s) and if they resolve to the built-in function we
2631 * can safely bypass it and look up the internal value directly.
2632 * Unimplemented for now, abort fast path for boxed values.
2633 */
2634 goto abort_fastpath;
2635 #else /* disabled */
2636 /* Disabled until fixed, see above. */
2637 duk_tval *tv_internal;
2638
2639 DUK_DD(DUK_DDPRINT("auto unboxing in fast path"));
2640
2641 tv_internal = duk_hobject_get_internal_value_tval_ptr(js_ctx->thr->heap, obj);
2642 DUK_ASSERT(tv_internal != NULL);
2643 DUK_ASSERT(DUK_TVAL_IS_STRING(tv_internal) ||
2644 DUK_TVAL_IS_NUMBER(tv_internal) ||
2645 DUK_TVAL_IS_BOOLEAN(tv_internal) ||
2646 DUK_TVAL_IS_POINTER(tv_internal));
2647
2648 tv = tv_internal;
2649 DUK_ASSERT(js_ctx->recursion_depth > 0);
2650 js_ctx->recursion_depth--; /* required to keep recursion depth correct */
2651 goto restart_match;
2652 #endif /* disabled */
2653 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2654 } else if (c_bit & c_func) {
2655 DUK__EMIT_STRIDX(js_ctx, js_ctx->stridx_custom_function);
2656 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
2657 } else if (c_bit & c_bufobj) {
2658 duk__json_enc_bufobj(js_ctx, (duk_hbufobj *) obj);
2659 #endif
2660 #endif
2661 } else if (c_bit & c_abort) {
2662 DUK_DD(DUK_DDPRINT("abort fast path for unsupported type"));
2663 goto abort_fastpath;
2664 } else {
2665 DUK_ASSERT((c_bit & c_undef) != 0);
2666
2667 /* Must decrease recursion depth before returning. */
2668 DUK_ASSERT(js_ctx->recursion_depth > 0);
2669 DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
2670 js_ctx->recursion_depth--;
2671 goto emit_undefined;
2672 }
2673
2674 DUK_ASSERT(js_ctx->recursion_depth > 0);
2675 DUK_ASSERT(js_ctx->recursion_depth <= js_ctx->recursion_limit);
2676 js_ctx->recursion_depth--;
2677 break;
2678 }
2679 case DUK_TAG_BUFFER: {
2680 /* Plain buffers are treated like Uint8Arrays: they have
2681 * enumerable indices. Other virtual properties are not
2682 * enumerable, and inherited properties are not serialized.
2683 * However, there can be a replacer (not relevant here) or
2684 * a .toJSON() method (which we need to check for explicitly).
2685 */
2686
2687 #if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
2688 if (duk_hobject_hasprop_raw(js_ctx->thr,
2689 js_ctx->thr->builtins[DUK_BIDX_UINT8ARRAY_PROTOTYPE],
2690 DUK_HTHREAD_STRING_TO_JSON(js_ctx->thr))) {
2691 DUK_DD(DUK_DDPRINT("value is a plain buffer and there's an inherited .toJSON, abort fast path"));
2692 goto abort_fastpath;
2693 }
2694 #endif
2695
2696 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2697 if (js_ctx->flag_ext_custom_or_compatible) {
2698 duk__json_enc_buffer_jx_jc(js_ctx, DUK_TVAL_GET_BUFFER(tv));
2699 break;
2700 }
2701 #endif
2702
2703 /* Plain buffers mimic Uint8Arrays, and have enumerable index
2704 * properties.
2705 */
2706 duk__json_enc_buffer_json_fastpath(js_ctx, DUK_TVAL_GET_BUFFER(tv));
2707 break;
2708 }
2709 case DUK_TAG_POINTER: {
2710 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2711 if (js_ctx->flag_ext_custom_or_compatible) {
2712 duk__json_enc_pointer(js_ctx, DUK_TVAL_GET_POINTER(tv));
2713 break;
2714 } else {
2715 goto emit_undefined;
2716 }
2717 #else
2718 goto emit_undefined;
2719 #endif
2720 }
2721 case DUK_TAG_LIGHTFUNC: {
2722 /* A lightfunc might also inherit a .toJSON() so just bail out. */
2723 /* XXX: Could just lookup .toJSON() and continue in fast path,
2724 * as it would almost never be defined.
2725 */
2726 DUK_DD(DUK_DDPRINT("value is a lightfunc, abort fast path"));
2727 goto abort_fastpath;
2728 }
2729 #if defined(DUK_USE_FASTINT)
2730 case DUK_TAG_FASTINT: {
2731 /* Number serialization has a significant impact relative to
2732 * other fast path code, so careful fast path for fastints.
2733 */
2734 duk__json_enc_fastint_tval(js_ctx, tv);
2735 break;
2736 }
2737 #endif
2738 default: {
2739 /* XXX: A fast path for usual integers would be useful when
2740 * fastint support is not enabled.
2741 */
2742 DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv));
2743 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
2744
2745 /* XXX: Stack discipline is annoying, could be changed in numconv. */
2746 duk_push_tval(js_ctx->thr, tv);
2747 duk__json_enc_double(js_ctx);
2748 duk_pop(js_ctx->thr);
2749
2750 #if 0
2751 /* Could also rely on native sprintf(), but it will handle
2752 * values like NaN, Infinity, -0, exponent notation etc in
2753 * a JSON-incompatible way.
2754 */
2755 duk_double_t d;
2756 char buf[64];
2757
2758 DUK_ASSERT(DUK_TVAL_IS_DOUBLE(tv));
2759 d = DUK_TVAL_GET_DOUBLE(tv);
2760 DUK_SPRINTF(buf, "%lg", d);
2761 DUK__EMIT_CSTR(js_ctx, buf);
2762 #endif
2763 }
2764 }
2765 return 1; /* not undefined */
2766
2767 emit_undefined:
2768 return 0; /* value was undefined/unsupported */
2769
2770 abort_fastpath:
2771 /* Error message doesn't matter: the error is ignored anyway. */
2772 DUK_DD(DUK_DDPRINT("aborting fast path"));
2773 DUK_ERROR_INTERNAL(js_ctx->thr);
2774 DUK_WO_NORETURN(return 0;);
2775 }
2776
2777 DUK_LOCAL duk_ret_t duk__json_stringify_fast(duk_hthread *thr, void *udata) {
2778 duk_json_enc_ctx *js_ctx;
2779 duk_tval *tv;
2780
2781 DUK_ASSERT(thr != NULL);
2782 DUK_ASSERT(udata != NULL);
2783
2784 js_ctx = (duk_json_enc_ctx *) udata;
2785 DUK_ASSERT(js_ctx != NULL);
2786
2787 tv = DUK_GET_TVAL_NEGIDX(thr, -1);
2788 if (duk__json_stringify_fast_value(js_ctx, tv) == 0) {
2789 DUK_DD(DUK_DDPRINT("top level value not supported, fail fast path"));
2790 DUK_DCERROR_TYPE_INVALID_ARGS(thr); /* Error message is ignored, so doesn't matter. */
2791 }
2792
2793 return 0;
2794 }
2795 #endif /* DUK_USE_JSON_STRINGIFY_FASTPATH */
2796
2797 /*
2798 * Top level wrappers
2799 */
2800
2801 DUK_INTERNAL
2802 void duk_bi_json_parse_helper(duk_hthread *thr,
2803 duk_idx_t idx_value,
2804 duk_idx_t idx_reviver,
2805 duk_small_uint_t flags) {
2806 duk_json_dec_ctx js_ctx_alloc;
2807 duk_json_dec_ctx *js_ctx = &js_ctx_alloc;
2808 duk_hstring *h_text;
2809 #if defined(DUK_USE_ASSERTIONS)
2810 duk_idx_t entry_top = duk_get_top(thr);
2811 #endif
2812
2813 /* negative top-relative indices not allowed now */
2814 DUK_ASSERT(idx_value == DUK_INVALID_INDEX || idx_value >= 0);
2815 DUK_ASSERT(idx_reviver == DUK_INVALID_INDEX || idx_reviver >= 0);
2816
2817 DUK_DDD(DUK_DDDPRINT("JSON parse start: text=%!T, reviver=%!T, flags=0x%08lx, stack_top=%ld",
2818 (duk_tval *) duk_get_tval(thr, idx_value),
2819 (duk_tval *) duk_get_tval(thr, idx_reviver),
2820 (unsigned long) flags,
2821 (long) duk_get_top(thr)));
2822
2823 duk_memzero(&js_ctx_alloc, sizeof(js_ctx_alloc));
2824 js_ctx->thr = thr;
2825 #if defined(DUK_USE_EXPLICIT_NULL_INIT)
2826 /* nothing now */
2827 #endif
2828 js_ctx->recursion_limit = DUK_USE_JSON_DEC_RECLIMIT;
2829 DUK_ASSERT(js_ctx->recursion_depth == 0);
2830
2831 /* Flag handling currently assumes that flags are consistent. This is OK
2832 * because the call sites are now strictly controlled.
2833 */
2834
2835 js_ctx->flags = flags;
2836 #if defined(DUK_USE_JX)
2837 js_ctx->flag_ext_custom = flags & DUK_JSON_FLAG_EXT_CUSTOM;
2838 #endif
2839 #if defined(DUK_USE_JC)
2840 js_ctx->flag_ext_compatible = flags & DUK_JSON_FLAG_EXT_COMPATIBLE;
2841 #endif
2842 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2843 js_ctx->flag_ext_custom_or_compatible = flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE);
2844 #endif
2845
2846 h_text = duk_to_hstring(thr, idx_value); /* coerce in-place; rejects Symbols */
2847 DUK_ASSERT(h_text != NULL);
2848
2849 /* JSON parsing code is allowed to read [p_start,p_end]: p_end is
2850 * valid and points to the string NUL terminator (which is always
2851 * guaranteed for duk_hstrings.
2852 */
2853 js_ctx->p_start = (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_text);
2854 js_ctx->p = js_ctx->p_start;
2855 js_ctx->p_end = ((const duk_uint8_t *) DUK_HSTRING_GET_DATA(h_text)) +
2856 DUK_HSTRING_GET_BYTELEN(h_text);
2857 DUK_ASSERT(*(js_ctx->p_end) == 0x00);
2858
2859 duk__json_dec_value(js_ctx); /* -> [ ... value ] */
2860 DUK_ASSERT(js_ctx->recursion_depth == 0);
2861
2862 /* Trailing whitespace has been eaten by duk__json_dec_value(), so if
2863 * we're not at end of input here, it's a SyntaxError.
2864 */
2865
2866 if (js_ctx->p != js_ctx->p_end) {
2867 duk__json_dec_syntax_error(js_ctx);
2868 }
2869
2870 if (duk_is_callable(thr, idx_reviver)) {
2871 DUK_DDD(DUK_DDDPRINT("applying reviver: %!T",
2872 (duk_tval *) duk_get_tval(thr, idx_reviver)));
2873
2874 js_ctx->idx_reviver = idx_reviver;
2875
2876 duk_push_object(thr);
2877 duk_dup_m2(thr); /* -> [ ... val root val ] */
2878 duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_EMPTY_STRING); /* default attrs ok */
2879 duk_push_hstring_stridx(thr, DUK_STRIDX_EMPTY_STRING); /* -> [ ... val root "" ] */
2880
2881 DUK_DDD(DUK_DDDPRINT("start reviver walk, root=%!T, name=%!T",
2882 (duk_tval *) duk_get_tval(thr, -2),
2883 (duk_tval *) duk_get_tval(thr, -1)));
2884
2885 DUK_ASSERT(js_ctx->recursion_depth == 0);
2886 duk__json_dec_reviver_walk(js_ctx); /* [ ... val root "" ] -> [ ... val val' ] */
2887 DUK_ASSERT(js_ctx->recursion_depth == 0);
2888 duk_remove_m2(thr); /* -> [ ... val' ] */
2889 } else {
2890 DUK_DDD(DUK_DDDPRINT("reviver does not exist or is not callable: %!T",
2891 (duk_tval *) duk_get_tval(thr, idx_reviver)));
2892 }
2893
2894 /* Final result is at stack top. */
2895
2896 DUK_DDD(DUK_DDDPRINT("JSON parse end: text=%!T, reviver=%!T, flags=0x%08lx, result=%!T, stack_top=%ld",
2897 (duk_tval *) duk_get_tval(thr, idx_value),
2898 (duk_tval *) duk_get_tval(thr, idx_reviver),
2899 (unsigned long) flags,
2900 (duk_tval *) duk_get_tval(thr, -1),
2901 (long) duk_get_top(thr)));
2902
2903 DUK_ASSERT(duk_get_top(thr) == entry_top + 1);
2904 }
2905
2906 DUK_INTERNAL
2907 void duk_bi_json_stringify_helper(duk_hthread *thr,
2908 duk_idx_t idx_value,
2909 duk_idx_t idx_replacer,
2910 duk_idx_t idx_space,
2911 duk_small_uint_t flags) {
2912 duk_json_enc_ctx js_ctx_alloc;
2913 duk_json_enc_ctx *js_ctx = &js_ctx_alloc;
2914 duk_hobject *h;
2915 duk_idx_t idx_holder;
2916 duk_idx_t entry_top;
2917
2918 /* negative top-relative indices not allowed now */
2919 DUK_ASSERT(idx_value == DUK_INVALID_INDEX || idx_value >= 0);
2920 DUK_ASSERT(idx_replacer == DUK_INVALID_INDEX || idx_replacer >= 0);
2921 DUK_ASSERT(idx_space == DUK_INVALID_INDEX || idx_space >= 0);
2922
2923 DUK_DDD(DUK_DDDPRINT("JSON stringify start: value=%!T, replacer=%!T, space=%!T, flags=0x%08lx, stack_top=%ld",
2924 (duk_tval *) duk_get_tval(thr, idx_value),
2925 (duk_tval *) duk_get_tval(thr, idx_replacer),
2926 (duk_tval *) duk_get_tval(thr, idx_space),
2927 (unsigned long) flags,
2928 (long) duk_get_top(thr)));
2929
2930 entry_top = duk_get_top(thr);
2931
2932 /*
2933 * Context init
2934 */
2935
2936 duk_memzero(&js_ctx_alloc, sizeof(js_ctx_alloc));
2937 js_ctx->thr = thr;
2938 #if defined(DUK_USE_EXPLICIT_NULL_INIT)
2939 js_ctx->h_replacer = NULL;
2940 js_ctx->h_gap = NULL;
2941 #endif
2942 js_ctx->idx_proplist = -1;
2943
2944 /* Flag handling currently assumes that flags are consistent. This is OK
2945 * because the call sites are now strictly controlled.
2946 */
2947
2948 js_ctx->flags = flags;
2949 js_ctx->flag_ascii_only = flags & DUK_JSON_FLAG_ASCII_ONLY;
2950 js_ctx->flag_avoid_key_quotes = flags & DUK_JSON_FLAG_AVOID_KEY_QUOTES;
2951 #if defined(DUK_USE_JX)
2952 js_ctx->flag_ext_custom = flags & DUK_JSON_FLAG_EXT_CUSTOM;
2953 #endif
2954 #if defined(DUK_USE_JC)
2955 js_ctx->flag_ext_compatible = flags & DUK_JSON_FLAG_EXT_COMPATIBLE;
2956 #endif
2957 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2958 js_ctx->flag_ext_custom_or_compatible = flags & (DUK_JSON_FLAG_EXT_CUSTOM | DUK_JSON_FLAG_EXT_COMPATIBLE);
2959 #endif
2960
2961 /* The #if defined() clutter here handles the JX/JC enable/disable
2962 * combinations properly.
2963 */
2964 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2965 js_ctx->stridx_custom_undefined = DUK_STRIDX_LC_NULL; /* standard JSON; array gaps */
2966 #if defined(DUK_USE_JX)
2967 if (flags & DUK_JSON_FLAG_EXT_CUSTOM) {
2968 js_ctx->stridx_custom_undefined = DUK_STRIDX_LC_UNDEFINED;
2969 js_ctx->stridx_custom_nan = DUK_STRIDX_NAN;
2970 js_ctx->stridx_custom_neginf = DUK_STRIDX_MINUS_INFINITY;
2971 js_ctx->stridx_custom_posinf = DUK_STRIDX_INFINITY;
2972 js_ctx->stridx_custom_function =
2973 (flags & DUK_JSON_FLAG_AVOID_KEY_QUOTES) ?
2974 DUK_STRIDX_JSON_EXT_FUNCTION2 :
2975 DUK_STRIDX_JSON_EXT_FUNCTION1;
2976 }
2977 #endif /* DUK_USE_JX */
2978 #if defined(DUK_USE_JX) && defined(DUK_USE_JC)
2979 else
2980 #endif /* DUK_USE_JX && DUK_USE_JC */
2981 #if defined(DUK_USE_JC)
2982 if (js_ctx->flags & DUK_JSON_FLAG_EXT_COMPATIBLE) {
2983 js_ctx->stridx_custom_undefined = DUK_STRIDX_JSON_EXT_UNDEFINED;
2984 js_ctx->stridx_custom_nan = DUK_STRIDX_JSON_EXT_NAN;
2985 js_ctx->stridx_custom_neginf = DUK_STRIDX_JSON_EXT_NEGINF;
2986 js_ctx->stridx_custom_posinf = DUK_STRIDX_JSON_EXT_POSINF;
2987 js_ctx->stridx_custom_function = DUK_STRIDX_JSON_EXT_FUNCTION1;
2988 }
2989 #endif /* DUK_USE_JC */
2990 #endif /* DUK_USE_JX || DUK_USE_JC */
2991
2992 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2993 if (js_ctx->flags & (DUK_JSON_FLAG_EXT_CUSTOM |
2994 DUK_JSON_FLAG_EXT_COMPATIBLE)) {
2995 DUK_ASSERT(js_ctx->mask_for_undefined == 0); /* already zero */
2996 }
2997 else
2998 #endif /* DUK_USE_JX || DUK_USE_JC */
2999 {
3000 /* Plain buffer is treated like ArrayBuffer and serialized.
3001 * Lightfuncs are treated like objects, but JSON explicitly
3002 * skips serializing Function objects so we can just reject
3003 * lightfuncs here.
3004 */
3005 js_ctx->mask_for_undefined = DUK_TYPE_MASK_UNDEFINED |
3006 DUK_TYPE_MASK_POINTER |
3007 DUK_TYPE_MASK_LIGHTFUNC;
3008 }
3009
3010 DUK_BW_INIT_PUSHBUF(thr, &js_ctx->bw, DUK__JSON_STRINGIFY_BUFSIZE);
3011
3012 js_ctx->idx_loop = duk_push_bare_object(thr);
3013 DUK_ASSERT(js_ctx->idx_loop >= 0);
3014
3015 /* [ ... buf loop ] */
3016
3017 /*
3018 * Process replacer/proplist (2nd argument to JSON.stringify)
3019 */
3020
3021 h = duk_get_hobject(thr, idx_replacer);
3022 if (h != NULL) {
3023 if (DUK_HOBJECT_IS_CALLABLE(h)) {
3024 js_ctx->h_replacer = h;
3025 } else if (duk_js_isarray_hobject(h)) {
3026 /* Here the specification requires correct array index enumeration
3027 * which is a bit tricky for sparse arrays (it is handled by the
3028 * enum setup code). We now enumerate ancestors too, although the
3029 * specification is not very clear on whether that is required.
3030 */
3031
3032 duk_uarridx_t plist_idx = 0;
3033 duk_small_uint_t enum_flags;
3034
3035 js_ctx->idx_proplist = duk_push_bare_array(thr);
3036
3037 enum_flags = DUK_ENUM_ARRAY_INDICES_ONLY |
3038 DUK_ENUM_SORT_ARRAY_INDICES; /* expensive flag */
3039 duk_enum(thr, idx_replacer, enum_flags);
3040 while (duk_next(thr, -1 /*enum_index*/, 1 /*get_value*/)) {
3041 /* [ ... proplist enum_obj key val ] */
3042 if (duk__json_enc_allow_into_proplist(duk_get_tval(thr, -1))) {
3043 /* XXX: duplicates should be eliminated here */
3044 DUK_DDD(DUK_DDDPRINT("proplist enum: key=%!T, val=%!T --> accept",
3045 (duk_tval *) duk_get_tval(thr, -2),
3046 (duk_tval *) duk_get_tval(thr, -1)));
3047 duk_to_string(thr, -1); /* extra coercion of strings is OK */
3048 duk_put_prop_index(thr, -4, plist_idx); /* -> [ ... proplist enum_obj key ] */
3049 plist_idx++;
3050 duk_pop(thr);
3051 } else {
3052 DUK_DDD(DUK_DDDPRINT("proplist enum: key=%!T, val=%!T --> reject",
3053 (duk_tval *) duk_get_tval(thr, -2),
3054 (duk_tval *) duk_get_tval(thr, -1)));
3055 duk_pop_2(thr);
3056 }
3057 }
3058 duk_pop(thr); /* pop enum */
3059
3060 /* [ ... proplist ] */
3061 }
3062 }
3063
3064 /* [ ... buf loop (proplist) ] */
3065
3066 /*
3067 * Process space (3rd argument to JSON.stringify)
3068 */
3069
3070 h = duk_get_hobject(thr, idx_space);
3071 if (h != NULL) {
3072 duk_small_uint_t c = DUK_HOBJECT_GET_CLASS_NUMBER(h);
3073 if (c == DUK_HOBJECT_CLASS_NUMBER) {
3074 duk_to_number(thr, idx_space);
3075 } else if (c == DUK_HOBJECT_CLASS_STRING) {
3076 duk_to_string(thr, idx_space);
3077 }
3078 }
3079
3080 if (duk_is_number(thr, idx_space)) {
3081 duk_small_int_t nspace;
3082 /* spaces[] must be static to allow initializer with old compilers like BCC */
3083 static const char spaces[10] = {
3084 DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE,
3085 DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE, DUK_ASC_SPACE,
3086 DUK_ASC_SPACE, DUK_ASC_SPACE
3087 }; /* XXX: helper */
3088
3089 /* ToInteger() coercion; NaN -> 0, infinities are clamped to 0 and 10 */
3090 nspace = (duk_small_int_t) duk_to_int_clamped(thr, idx_space, 0 /*minval*/, 10 /*maxval*/);
3091 DUK_ASSERT(nspace >= 0 && nspace <= 10);
3092
3093 duk_push_lstring(thr, spaces, (duk_size_t) nspace);
3094 js_ctx->h_gap = duk_known_hstring(thr, -1);
3095 DUK_ASSERT(js_ctx->h_gap != NULL);
3096 } else if (duk_is_string_notsymbol(thr, idx_space)) {
3097 duk_dup(thr, idx_space);
3098 duk_substring(thr, -1, 0, 10); /* clamp to 10 chars */
3099 js_ctx->h_gap = duk_known_hstring(thr, -1);
3100 } else {
3101 /* nop */
3102 }
3103
3104 if (js_ctx->h_gap != NULL) {
3105 /* If gap is empty, behave as if not given at all. Check
3106 * against byte length because character length is more
3107 * expensive.
3108 */
3109 if (DUK_HSTRING_GET_BYTELEN(js_ctx->h_gap) == 0) {
3110 js_ctx->h_gap = NULL;
3111 }
3112 }
3113
3114 /* [ ... buf loop (proplist) (gap) ] */
3115
3116 /*
3117 * Fast path: assume no mutation, iterate object property tables
3118 * directly; bail out if that assumption doesn't hold.
3119 */
3120
3121 #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH)
3122 if (js_ctx->h_replacer == NULL && /* replacer is a mutation risk */
3123 js_ctx->idx_proplist == -1) { /* proplist is very rare */
3124 duk_int_t pcall_rc;
3125 duk_small_uint_t prev_ms_base_flags;
3126
3127 DUK_DD(DUK_DDPRINT("try JSON.stringify() fast path"));
3128
3129 /* Use recursion_limit to ensure we don't overwrite js_ctx->visiting[]
3130 * array so we don't need two counter checks in the fast path. The
3131 * slow path has a much larger recursion limit which we'll use if
3132 * necessary.
3133 */
3134 DUK_ASSERT(DUK_USE_JSON_ENC_RECLIMIT >= DUK_JSON_ENC_LOOPARRAY);
3135 js_ctx->recursion_limit = DUK_JSON_ENC_LOOPARRAY;
3136 DUK_ASSERT(js_ctx->recursion_depth == 0);
3137
3138 /* Execute the fast path in a protected call. If any error is thrown,
3139 * fall back to the slow path. This includes e.g. recursion limit
3140 * because the fast path has a smaller recursion limit (and simpler,
3141 * limited loop detection).
3142 */
3143
3144 duk_dup(thr, idx_value);
3145
3146 /* Must prevent finalizers which may have arbitrary side effects. */
3147 prev_ms_base_flags = thr->heap->ms_base_flags;
3148 thr->heap->ms_base_flags |=
3149 DUK_MS_FLAG_NO_OBJECT_COMPACTION; /* Avoid attempt to compact any objects. */
3150 thr->heap->pf_prevent_count++; /* Prevent finalizers. */
3151 DUK_ASSERT(thr->heap->pf_prevent_count != 0); /* Wrap. */
3152
3153 pcall_rc = duk_safe_call(thr, duk__json_stringify_fast, (void *) js_ctx /*udata*/, 1 /*nargs*/, 0 /*nret*/);
3154
3155 DUK_ASSERT(thr->heap->pf_prevent_count > 0);
3156 thr->heap->pf_prevent_count--;
3157 thr->heap->ms_base_flags = prev_ms_base_flags;
3158
3159 if (pcall_rc == DUK_EXEC_SUCCESS) {
3160 DUK_DD(DUK_DDPRINT("fast path successful"));
3161 DUK_BW_PUSH_AS_STRING(thr, &js_ctx->bw);
3162 goto replace_finished;
3163 }
3164
3165 /* We come here for actual aborts (like encountering .toJSON())
3166 * but also for recursion/loop errors. Bufwriter size can be
3167 * kept because we'll probably need at least as much as we've
3168 * allocated so far.
3169 */
3170 DUK_D(DUK_DPRINT("fast path failed, serialize using slow path instead"));
3171 DUK_BW_RESET_SIZE(thr, &js_ctx->bw);
3172 js_ctx->recursion_depth = 0;
3173 }
3174 #endif
3175
3176 /*
3177 * Create wrapper object and serialize
3178 */
3179
3180 idx_holder = duk_push_object(thr);
3181 duk_dup(thr, idx_value);
3182 duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_EMPTY_STRING);
3183
3184 DUK_DDD(DUK_DDDPRINT("before: flags=0x%08lx, loop=%!T, replacer=%!O, "
3185 "proplist=%!T, gap=%!O, holder=%!T",
3186 (unsigned long) js_ctx->flags,
3187 (duk_tval *) duk_get_tval(thr, js_ctx->idx_loop),
3188 (duk_heaphdr *) js_ctx->h_replacer,
3189 (duk_tval *) (js_ctx->idx_proplist >= 0 ? duk_get_tval(thr, js_ctx->idx_proplist) : NULL),
3190 (duk_heaphdr *) js_ctx->h_gap,
3191 (duk_tval *) duk_get_tval(thr, -1)));
3192
3193 /* serialize the wrapper with empty string key */
3194
3195 duk_push_hstring_empty(thr);
3196
3197 /* [ ... buf loop (proplist) (gap) holder "" ] */
3198
3199 js_ctx->recursion_limit = DUK_USE_JSON_ENC_RECLIMIT;
3200 DUK_ASSERT(js_ctx->recursion_depth == 0);
3201
3202 if (DUK_UNLIKELY(duk__json_enc_value(js_ctx, idx_holder) == 0)) { /* [ ... holder key ] -> [ ... holder ] */
3203 /* Result is undefined. */
3204 duk_push_undefined(thr);
3205 } else {
3206 /* Convert buffer to result string. */
3207 DUK_BW_PUSH_AS_STRING(thr, &js_ctx->bw);
3208 }
3209
3210 DUK_DDD(DUK_DDDPRINT("after: flags=0x%08lx, loop=%!T, replacer=%!O, "
3211 "proplist=%!T, gap=%!O, holder=%!T",
3212 (unsigned long) js_ctx->flags,
3213 (duk_tval *) duk_get_tval(thr, js_ctx->idx_loop),
3214 (duk_heaphdr *) js_ctx->h_replacer,
3215 (duk_tval *) (js_ctx->idx_proplist >= 0 ? duk_get_tval(thr, js_ctx->idx_proplist) : NULL),
3216 (duk_heaphdr *) js_ctx->h_gap,
3217 (duk_tval *) duk_get_tval(thr, idx_holder)));
3218
3219 /* The stack has a variable shape here, so force it to the
3220 * desired one explicitly.
3221 */
3222
3223 #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH)
3224 replace_finished:
3225 #endif
3226 duk_replace(thr, entry_top);
3227 duk_set_top(thr, entry_top + 1);
3228
3229 DUK_DDD(DUK_DDDPRINT("JSON stringify end: value=%!T, replacer=%!T, space=%!T, "
3230 "flags=0x%08lx, result=%!T, stack_top=%ld",
3231 (duk_tval *) duk_get_tval(thr, idx_value),
3232 (duk_tval *) duk_get_tval(thr, idx_replacer),
3233 (duk_tval *) duk_get_tval(thr, idx_space),
3234 (unsigned long) flags,
3235 (duk_tval *) duk_get_tval(thr, -1),
3236 (long) duk_get_top(thr)));
3237
3238 DUK_ASSERT(duk_get_top(thr) == entry_top + 1);
3239 }
3240
3241 #if defined(DUK_USE_JSON_BUILTIN)
3242
3243 /*
3244 * Entry points
3245 */
3246
3247 DUK_INTERNAL duk_ret_t duk_bi_json_object_parse(duk_hthread *thr) {
3248 duk_bi_json_parse_helper(thr,
3249 0 /*idx_value*/,
3250 1 /*idx_replacer*/,
3251 0 /*flags*/);
3252 return 1;
3253 }
3254
3255 DUK_INTERNAL duk_ret_t duk_bi_json_object_stringify(duk_hthread *thr) {
3256 duk_bi_json_stringify_helper(thr,
3257 0 /*idx_value*/,
3258 1 /*idx_replacer*/,
3259 2 /*idx_space*/,
3260 0 /*flags*/);
3261 return 1;
3262 }
3263
3264 #endif /* DUK_USE_JSON_BUILTIN */
3265
3266 #endif /* DUK_USE_JSON_SUPPORT */
3267