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