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