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