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.
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
17 #include "duk_internal.h"
20 * Local defines and forward declarations.
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' */
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
);
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
);
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
);
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
);
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
);
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
);
76 DUK_LOCAL_DECL
void duk__enc_newline_indent(duk_json_enc_ctx
*js_ctx
, duk_int_t depth
);
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
87 * 0xa0 ... 0xff: backslash + one char
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
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
114 #endif /* DUK_USE_JSON_QUOTESTRING_FASTPATH */
116 #if defined(DUK_USE_JSON_DECSTRING_FASTPATH)
117 DUK_LOCAL
const duk_uint8_t duk__json_decstr_lookup
[256] = {
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
138 #endif /* DUK_USE_JSON_DECSTRING_FASTPATH */
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)
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
162 #endif /* DUK_USE_JSON_EATWHITE_FASTPATH */
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)
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
186 #endif /* DUK_USE_JSON_DECNUMBER_FASTPATH */
189 * Parsing implementation.
191 * JSON lexer is now separate from duk_lexer.c because there are numerous
192 * small differences making it difficult to share the lexer.
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
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.
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
));
210 DUK_LOCAL
void duk__dec_eat_white(duk_json_dec_ctx
*js_ctx
) {
211 const duk_uint8_t
*p
;
216 DUK_ASSERT(p
<= js_ctx
->p_end
);
219 #if defined(DUK_USE_JSON_EATWHITE_FASTPATH)
220 /* This fast path is pretty marginal in practice.
221 * XXX: candidate for removal.
223 DUK_ASSERT(duk__json_eatwhite_lookup
[0x00] == 0x00); /* end-of-input breaks */
224 if (duk__json_eatwhite_lookup
[t
] == 0) {
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.
234 #endif /* DUK_USE_JSON_EATWHITE_FASTPATH */
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
);
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
);
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
);
255 /* For JX, expressing the whole unsigned 32-bit range matters. */
256 DUK_LOCAL duk_uint_fast32_t
duk__dec_decode_hex_escape(duk_json_dec_ctx
*js_ctx
, duk_small_uint_t n
) {
258 duk_uint_fast32_t res
= 0;
262 for (i
= 0; i
< n
; i
++) {
263 /* XXX: share helper from lexer; duk_lexer.c / hexval(). */
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
));
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
;
275 /* catches EOF and invalid digits */
280 DUK_DDD(DUK_DDDPRINT("final hex decoded value: %ld", (long) res
));
284 duk__dec_syntax_error(js_ctx
);
289 DUK_LOCAL
void duk__dec_req_stridx(duk_json_dec_ctx
*js_ctx
, duk_small_uint_t stridx
) {
291 const duk_uint8_t
*p
;
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.
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
);
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 */
312 y
= duk__dec_get(js_ctx
);
314 /* Catches EOF of JSON input. */
323 duk__dec_syntax_error(js_ctx
);
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
;
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).
334 cp
= (duk_uint_fast32_t
) duk__dec_get(js_ctx
);
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;
345 cp
= duk__dec_decode_hex_escape(js_ctx
, 4);
350 if (js_ctx
->flag_ext_custom
) {
351 cp
= duk__dec_decode_hex_escape(js_ctx
, 8);
353 return 1; /* syntax error */
358 if (js_ctx
->flag_ext_custom
) {
359 cp
= duk__dec_decode_hex_escape(js_ctx
, 2);
361 return 1; /* syntax error */
365 #endif /* DUK_USE_JX */
367 /* catches EOF (0x00) */
368 return 1; /* syntax error */
371 DUK_RAW_WRITE_XUTF8(*ext_p
, cp
);
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
;
383 /* '"' was eaten by caller */
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).
391 DUK_BW_INIT_PUSHBUF(js_ctx
->thr
, bw
, DUK__JSON_DECSTR_BUFSIZE
);
392 q
= DUK_BW_GET_PTR(js_ctx
->thr
, bw
);
394 #if defined(DUK_USE_JSON_DECSTRING_FASTPATH)
396 duk_small_uint_t safe
;
398 const duk_uint8_t
*p
;
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.
406 safe
= DUK__JSON_DECSTR_CHUNKSIZE
;
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
);
411 p
= js_ctx
->p
; /* temp copy, write back for next loop */
419 /* End of input (NUL) goes through slow path and causes SyntaxError. */
420 DUK_ASSERT(duk__json_decstr_lookup
[0] == 0x00);
423 x
= (duk_small_int_t
) duk__json_decstr_lookup
[b
];
424 if (DUK_LIKELY(x
!= 0)) {
425 /* Fast path, decode as is. */
427 } else if (b
== DUK_ASC_DOUBLEQUOTE
) {
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).
436 if (duk__dec_string_escape(js_ctx
, &q
) != 0) {
447 #else /* DUK_USE_JSON_DECSTRING_FASTPATH */
451 q
= DUK_BW_ENSURE_RAW(js_ctx
->thr
, bw
, DUK_UNICODE_MAX_XUTF8_LENGTH
, q
);
453 x
= duk__dec_get(js_ctx
);
455 if (x
== DUK_ASC_DOUBLEQUOTE
) {
457 } else if (x
== DUK_ASC_BACKSLASH
) {
458 if (duk__dec_string_escape(js_ctx
, &q
) != 0) {
461 } else if (x
< 0x20) {
462 /* catches EOF (NUL) */
465 *q
++ = (duk_uint8_t
) x
;
468 #endif /* DUK_USE_JSON_DECSTRING_FASTPATH */
470 DUK_BW_SETPTR_AND_COMPACT(js_ctx
->thr
, bw
, q
);
471 duk_to_string(ctx
, -1);
478 duk__dec_syntax_error(js_ctx
);
483 /* Decode a plain string consisting entirely of identifier characters.
484 * Used to parse plain keys (e.g. "foo: 123").
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
;
492 /* Caller has already eaten the first char so backtrack one byte. */
494 js_ctx
->p
--; /* safe */
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.
502 * Because no unescaping takes place, we can just scan to the end of the
503 * plain string and intern from the input buffer.
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.
515 if (!duk_unicode_is_identifier_part((duk_codepoint_t
) x
)) {
521 duk_push_lstring(ctx
, (const char *) js_ctx
->p
, (duk_size_t
) (p
- js_ctx
->p
));
526 #endif /* DUK_USE_JX */
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
;
536 /* Caller has already eaten the first character ('(') which we don't need. */
543 /* Assume that the native representation never contains a closing
547 if (x
== DUK_ASC_RPAREN
) {
550 /* NUL term or -1 (EOF), NUL check would suffice */
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.
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
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 ')' */
577 duk__dec_syntax_error(js_ctx
);
580 #endif /* DUK_USE_JX */
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
;
591 /* Caller has already eaten the first character ('|') which we don't need. */
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:
599 * - Pipe char detection
601 * - Unknown length of input and output
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).
610 /* This loop intentionally does not ensure characters are valid
611 * ([0-9a-fA-F]) because the hex decode call below will do that.
613 if (x
== DUK_ASC_PIPE
) {
616 /* NUL term or -1 (EOF), NUL check would suffice */
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);
628 js_ctx
->p
= p
+ 1; /* skip '|' */
635 duk__dec_syntax_error(js_ctx
);
638 #endif /* DUK_USE_JX */
640 /* Parse a number, other than NaN or +/- Infinity */
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
;
646 duk_small_uint_t s2n_flags
;
648 DUK_DDD(DUK_DDDPRINT("parse_number"));
652 /* First pass parse is very lenient (e.g. allows '1.2.3') and extracts a
653 * string for strict number parsing.
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
));
664 #if defined(DUK_USE_JSON_DECNUMBER_FASTPATH)
665 /* This fast path is pretty marginal in practice.
666 * XXX: candidate for removal.
668 DUK_ASSERT(duk__json_decnumber_lookup
[0x00] == 0x00); /* end-of-input breaks */
669 if (duk__json_decnumber_lookup
[x
] == 0) {
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.
681 #endif /* DUK_USE_JSON_DECNUMBER_FASTPATH */
682 p
++; /* safe, because matched (NUL causes a break) */
686 DUK_ASSERT(js_ctx
->p
> p_start
);
687 duk_push_lstring(ctx
, (const char *) p_start
, (duk_size_t
) (p
- p_start
));
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
;
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
);
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)));
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
);
710 /* c recursion check */
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
);
717 js_ctx
->recursion_depth
++;
720 DUK_LOCAL
void duk__dec_objarr_exit(duk_json_dec_ctx
*js_ctx
) {
721 /* c recursion check */
723 DUK_ASSERT(js_ctx
->recursion_depth
> 0);
724 DUK_ASSERT(js_ctx
->recursion_depth
<= js_ctx
->recursion_limit
);
725 js_ctx
->recursion_depth
--;
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 */
733 DUK_DDD(DUK_DDDPRINT("parse_object"));
735 duk__dec_objarr_entry(js_ctx
);
737 duk_push_object(ctx
);
739 /* Initial '{' has been checked and eaten by caller. */
743 x
= duk__dec_get_nonwhite(js_ctx
);
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
));
749 /* handle comma and closing brace */
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 */
757 } else if (key_count
== 0) {
758 /* accept anything, expect first value (EOF will be
759 * caught by key parsing below.
763 /* catches EOF (NUL) and initial comma */
767 /* parse key and value */
769 if (x
== DUK_ASC_DOUBLEQUOTE
) {
770 duk__dec_string(js_ctx
);
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
);
780 /* [ ... obj key ] */
782 x
= duk__dec_get_nonwhite(js_ctx
);
783 if (x
!= DUK_ASC_COLON
) {
787 duk__dec_value(js_ctx
);
789 /* [ ... obj key val ] */
791 duk_xdef_prop_wec(ctx
, -3);
800 DUK_DDD(DUK_DDDPRINT("parse_object: final object is %!T",
801 (duk_tval
*) duk_get_tval(ctx
, -1)));
803 duk__dec_objarr_exit(js_ctx
);
807 duk__dec_syntax_error(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
;
816 DUK_DDD(DUK_DDDPRINT("parse_array"));
818 duk__dec_objarr_entry(js_ctx
);
822 /* Initial '[' has been checked and eaten by caller. */
826 x
= duk__dec_get_nonwhite(js_ctx
);
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
));
832 /* handle comma and closing bracket */
834 if ((x
== DUK_ASC_COMMA
) && (arr_idx
!= 0)) {
835 /* accept comma, expect new value */
837 } else if (x
== DUK_ASC_RBRACKET
) {
838 /* eat closing bracket */
840 } else if (arr_idx
== 0) {
841 /* accept anything, expect first value (EOF will be
842 * caught by duk__dec_value() below.
844 js_ctx
->p
--; /* backtrack (safe) */
846 /* catches EOF (NUL) and initial comma */
852 duk__dec_value(js_ctx
);
854 /* [ ... arr val ] */
856 duk_xdef_prop_index_wec(ctx
, -2, arr_idx
);
860 /* Must set 'length' explicitly when using duk_xdef_prop_xxx() to
864 duk_set_length(ctx
, -1, arr_idx
);
868 DUK_DDD(DUK_DDDPRINT("parse_array: final array is %!T",
869 (duk_tval
*) duk_get_tval(ctx
, -1)));
871 duk__dec_objarr_exit(js_ctx
);
875 duk__dec_syntax_error(js_ctx
);
879 DUK_LOCAL
void duk__dec_value(duk_json_dec_ctx
*js_ctx
) {
880 duk_context
*ctx
= (duk_context
*) js_ctx
->thr
;
883 x
= duk__dec_get_nonwhite(js_ctx
);
885 DUK_DDD(DUK_DDDPRINT("parse_value: initial x=%ld", (long) x
));
887 /* Note: duk__dec_req_stridx() backtracks one char */
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
)) {
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
);
898 { /* unconditional block */
900 /* We already ate 'x', so backup one byte. */
901 js_ctx
->p
--; /* safe */
902 duk__dec_number(js_ctx
);
904 } else if (x
== DUK_ASC_LC_T
) {
905 duk__dec_req_stridx(js_ctx
, DUK_STRIDX_TRUE
);
907 } else if (x
== DUK_ASC_LC_F
) {
908 duk__dec_req_stridx(js_ctx
, DUK_STRIDX_FALSE
);
910 } else if (x
== DUK_ASC_LC_N
) {
911 duk__dec_req_stridx(js_ctx
, DUK_STRIDX_LC_NULL
);
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
);
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
);
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
);
933 /* catches EOF (NUL) */
937 duk__dec_eat_white(js_ctx
);
943 duk__dec_syntax_error(js_ctx
);
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.
951 DUK_LOCAL
void duk__dec_reviver_walk(duk_json_dec_ctx
*js_ctx
) {
952 duk_context
*ctx
= (duk_context
*) js_ctx
->thr
;
954 duk_uarridx_t i
, arr_len
;
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)));
962 duk_get_prop(ctx
, -3); /* -> [ ... holder name val ] */
964 h
= duk_get_hobject(ctx
, -1);
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 ] */
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)));
976 /* XXX: push_uint_string / push_u32_string */
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 ] */
982 if (duk_is_undefined(ctx
, -1)) {
984 duk_del_prop_index(ctx
, -1, i
);
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).
990 duk_put_prop_index(ctx
, -2, i
);
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)));
1002 /* [ ... holder name val enum obj_key ] */
1006 /* [ ... holder name val enum obj_key val obj_key ] */
1007 duk__dec_reviver_walk(js_ctx
);
1009 /* [ ... holder name val enum obj_key new_elem ] */
1010 if (duk_is_undefined(ctx
, -1)) {
1012 duk_del_prop(ctx
, -3);
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).
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.
1023 duk_put_prop(ctx
, -4);
1026 duk_pop(ctx
); /* pop enum */
1030 /* [ ... holder name val ] */
1032 duk_dup(ctx
, js_ctx
->idx_reviver
);
1033 duk_insert(ctx
, -4); /* -> [ ... reviver holder name val ] */
1034 duk_call_method(ctx
, 2); /* -> [ ... res ] */
1036 DUK_DDD(DUK_DDDPRINT("walk: top=%ld, result=%!T",
1037 (long) duk_get_top(ctx
), (duk_tval
*) duk_get_tval(ctx
, -1)));
1041 * Stringify implementation.
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))
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))
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
);
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
);
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
);
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
);
1071 DUK_LOCAL
void duk__emit_stridx(duk_json_enc_ctx
*js_ctx
, duk_small_uint_t stridx
) {
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
);
1079 DUK_BW_WRITE_ENSURE_HSTRING(js_ctx
->thr
, &js_ctx
->bw
, h
);
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);
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))
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
;
1098 /* Caller ensures space for at least DUK__JSON_MAX_ESC_LEN. */
1100 /* Select appropriate escape format automatically, and set 'tmp' to a
1101 * value encoding both the escape format character and the nybble count:
1103 * (nybble_count << 16) | (escape_char1) | (escape_char2)
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
);
1111 tmp
= DUK__MKESC(4, DUK_ASC_BACKSLASH
, DUK_ASC_LC_U
);
1115 if (DUK_LIKELY(cp
< 0x10000UL
)) {
1116 tmp
= DUK__MKESC(4, DUK_ASC_BACKSLASH
, DUK_ASC_LC_U
);
1119 if (DUK_LIKELY(js_ctx
->flag_ext_custom
)) {
1120 tmp
= DUK__MKESC(8, DUK_ASC_BACKSLASH
, DUK_ASC_UC_U
);
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.
1129 tmp
= DUK__MKESC(8, DUK_ASC_UC_U
, DUK_ASC_PLUS
);
1133 *q
++ = (duk_uint8_t
) ((tmp
>> 8) & 0xff);
1134 *q
++ = (duk_uint8_t
) (tmp
& 0xff);
1139 dig
= (duk_small_uint_t
) ((cp
>> (4 * tmp
)) & 0x0f);
1140 *q
++ = duk_lc_digits
[dig
];
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. */
1151 DUK_ASSERT(k
!= NULL
);
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.
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
;
1167 /* Zero length string is not accepted without quotes */
1168 goto quote_normally
;
1170 cp
= (duk_codepoint_t
) (*p
++);
1171 if (DUK_UNLIKELY(!duk_unicode_is_identifier_start(cp
))) {
1172 goto quote_normally
;
1175 cp
= (duk_codepoint_t
) (*p
++);
1176 if (DUK_UNLIKELY(!duk_unicode_is_identifier_part(cp
))) {
1177 goto quote_normally
;
1181 /* This seems faster than emitting bytes one at a time and
1182 * then potentially rewinding.
1184 DUK__EMIT_HSTR(js_ctx
, k
);
1189 duk__enc_quote_string(js_ctx
, k
);
1192 /* The Quote(value) operation: quote a string.
1194 * Stack policy: [ ] -> [ ].
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
;
1201 duk_ucodepoint_t cp
; /* typed for duk_unicode_decode_xutf8() */
1203 DUK_DDD(DUK_DDDPRINT("duk__enc_quote_string: h_str=%!O", (duk_heaphdr
*) h_str
));
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
);
1210 DUK__EMIT_1(js_ctx
, DUK_ASC_DOUBLEQUOTE
);
1212 /* Encode string in small chunks, estimating the maximum expansion so that
1213 * there's no need to ensure space while processing the chunk.
1217 duk_size_t left
, now
, space
;
1219 left
= (duk_size_t
) (p_end
- p
);
1220 now
= (left
> DUK__JSON_ENCSTR_CHUNKSIZE
?
1221 DUK__JSON_ENCSTR_CHUNKSIZE
: left
);
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).
1229 q
= DUK_BW_ENSURE_GETPTR(thr
, &js_ctx
->bw
, space
);
1234 #if defined(DUK_USE_JSON_QUOTESTRING_FASTPATH)
1237 b
= duk__json_quotestr_lookup
[*p
++];
1238 if (DUK_LIKELY(b
< 0x80)) {
1239 /* Most input bytes go through here. */
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
);
1253 DUK_ASSERT(b
== 0x81);
1256 /* slow path is shared */
1257 #else /* DUK_USE_JSON_QUOTESTRING_FASTPATH */
1260 if (DUK_LIKELY(cp
<= 0x7f)) {
1261 /* ascii fast path: avoid decoding utf-8 */
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
;
1270 /* This approach is a bit shorter than a straight
1271 * if-else-ladder and also a bit faster.
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
;
1278 q
= duk__emit_esc_auto_fast(js_ctx
, cp
, q
);
1280 } else if (cp
== 0x7f && js_ctx
->flag_ascii_only
) {
1281 q
= duk__emit_esc_auto_fast(js_ctx
, cp
, q
);
1283 /* any other printable -> as is */
1284 *q
++ = (duk_uint8_t
) cp
;
1287 /* slow path is shared */
1288 #endif /* DUK_USE_JSON_QUOTESTRING_FASTPATH */
1290 /* slow path decode */
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.
1301 * Another alternative would be to scan forwards to start of next codepoint
1302 * (or end of input) and emit just one replacement codepoint.
1306 if (!duk_unicode_decode_xutf8(thr
, &p
, p_start
, p_end
, &cp
)) {
1307 /* Decode failed. */
1312 #ifdef DUK_USE_NONSTD_JSON_ESC_U2028_U2029
1313 if (js_ctx
->flag_ascii_only
|| cp
== 0x2028 || cp
== 0x2029) {
1315 if (js_ctx
->flag_ascii_only
) {
1317 q
= duk__emit_esc_auto_fast(js_ctx
, cp
, q
);
1320 DUK_RAW_WRITE_XUTF8(q
, cp
);
1325 DUK_BW_SET_PTR(thr
, &js_ctx
->bw
, q
);
1328 DUK__EMIT_1(js_ctx
, DUK_ASC_DOUBLEQUOTE
);
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).
1334 DUK_LOCAL
void duk__enc_double(duk_json_enc_ctx
*js_ctx
) {
1341 duk_small_uint_t stridx
;
1342 duk_small_uint_t n2s_flags
;
1345 DUK_ASSERT(js_ctx
!= NULL
);
1347 DUK_ASSERT(thr
!= NULL
);
1348 ctx
= (duk_context
*) thr
;
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
);
1355 c
= (duk_small_int_t
) DUK_FPCLASSIFY(d
);
1356 s
= (duk_small_int_t
) DUK_SIGNBIT(d
);
1359 if (DUK_LIKELY(!(c
== DUK_FP_INFINITE
|| c
== DUK_FP_NAN
))) {
1360 DUK_ASSERT(DUK_ISFINITE(d
));
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'.
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' */
1370 #endif /* DUK_USE_JX || DUK_USE_JC */
1373 /* [ ... number ] -> [ ... string ] */
1374 duk_numconv_stringify(ctx
, 10 /*radix*/, 0 /*digits*/, n2s_flags
);
1376 h_str
= duk_to_hstring(ctx
, -1);
1377 DUK_ASSERT(h_str
!= NULL
);
1378 DUK__EMIT_HSTR(js_ctx
, h_str
);
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
;
1391 stridx
= js_ctx
->stridx_custom_neginf
;
1394 stridx
= DUK_STRIDX_LC_NULL
;
1396 DUK__EMIT_STRIDX(js_ctx
, stridx
);
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
) {
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.
1408 duk_uint8_t buf
[20 + 1];
1410 /* Caller must ensure 'tv' is indeed a fastint! */
1411 DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv
));
1412 v
= DUK_TVAL_GET_FASTINT(tv
);
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.
1419 DUK_SPRINTF((char *) buf
, "%lld", (long long) v
);
1420 DUK__EMIT_CSTR(js_ctx
, (const char *) buf
);
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
) {
1430 duk_size_t i
, len_safe
;
1431 #if !defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE)
1432 duk_bool_t shift_dst
;
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.
1443 #if defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE)
1444 q16
= (duk_uint16_t
*) (void *) dst
;
1446 shift_dst
= (duk_bool_t
) (((duk_size_t
) dst
) & 0x01U
);
1448 DUK_DD(DUK_DDPRINT("unaligned accesses not possible, dst not aligned -> step to dst + 1"));
1449 q16
= (duk_uint16_t
*) (void *) (dst
+ 1);
1451 DUK_DD(DUK_DDPRINT("unaligned accesses not possible, dst is aligned"));
1452 q16
= (duk_uint16_t
*) (void *) dst
;
1454 DUK_ASSERT((((duk_size_t
) q16
) & 0x01U
) == 0);
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]];
1465 q
= (duk_uint8_t
*) q16
;
1467 #if !defined(DUK_USE_UNALIGNED_ACCESSES_POSSIBLE)
1470 DUK_MEMMOVE((void *) dst
, (const void *) (dst
+ 1), 2 * len_safe
);
1471 DUK_ASSERT(dst
+ 2 * len_safe
== q
);
1475 for (; i
< src_len
; i
++) {
1477 *q
++ = duk_lc_digits
[x
>> 4];
1478 *q
++ = duk_lc_digits
[x
& 0x0f];
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
;
1491 p_end
= src
+ src_len
;
1493 while (p
!= p_end
) {
1495 *q
++ = duk_lc_digits
[x
>> 4];
1496 *q
++ = duk_lc_digits
[x
& 0x0f];
1501 #endif /* DUK_USE_HEX_FASTPATH */
1503 DUK_LOCAL
void duk__enc_buffer_data(duk_json_enc_ctx
*js_ctx
, duk_uint8_t
*buf_data
, duk_size_t buf_len
) {
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
);
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.
1519 /* The #ifdef clutter here needs to handle the three cases:
1520 * (1) JX+JC, (2) JX only, (3) JC only.
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
);
1529 #if defined(DUK_USE_JX) && defined(DUK_USE_JC)
1530 if (js_ctx
->flag_ext_custom
)
1532 #if defined(DUK_USE_JX)
1534 *q
++ = DUK_ASC_PIPE
;
1535 q
= duk__enc_buffer_data_hex(buf_data
, buf_len
, q
);
1536 *q
++ = DUK_ASC_PIPE
;
1540 #if defined(DUK_USE_JX) && defined(DUK_USE_JC)
1543 #if defined(DUK_USE_JC)
1545 DUK_ASSERT(js_ctx
->flag_ext_compatible
);
1546 DUK_MEMCPY((void *) q
, (const void *) "{\"_buf\":\"", 9); /* len: 9 */
1548 q
= duk__enc_buffer_data_hex(buf_data
, buf_len
, q
);
1549 *q
++ = DUK_ASC_DOUBLEQUOTE
;
1550 *q
++ = DUK_ASC_RCURLY
;
1554 DUK_BW_SET_PTR(thr
, &js_ctx
->bw
, q
);
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
));
1562 #endif /* DUK_USE_JX || DUK_USE_JC */
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? */
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
);
1572 DUK_MEMZERO(buf
, sizeof(buf
));
1574 /* The #ifdef clutter here needs to handle the three cases:
1575 * (1) JX+JC, (2) JX only, (3) JC only.
1577 #if defined(DUK_USE_JX) && defined(DUK_USE_JC)
1578 if (js_ctx
->flag_ext_custom
)
1580 #if defined(DUK_USE_JX)
1582 fmt
= ptr
? "(%p)" : "(null)";
1585 #if defined(DUK_USE_JX) && defined(DUK_USE_JC)
1588 #if defined(DUK_USE_JC)
1590 DUK_ASSERT(js_ctx
->flag_ext_compatible
);
1591 fmt
= ptr
? "{\"_ptr\":\"%p\"}" : "{\"_ptr\":\"null\"}";
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
);
1599 #endif /* DUK_USE_JX || DUK_USE_JC */
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
);
1605 if (h_bufobj
->buf
== NULL
|| !DUK_HBUFFEROBJECT_VALID_SLICE(h_bufobj
)) {
1606 DUK__EMIT_STRIDX(js_ctx
, DUK_STRIDX_LC_NULL
);
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
);
1614 #endif /* DUK_USE_JX || DUK_USE_JC */
1616 /* Indent helper. Calling code relies on js_ctx->recursion_depth also being
1617 * directly related to indent depth.
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 */
1624 DUK__EMIT_1(js_ctx
, 0x0a);
1625 while (depth
-- > 0) {
1626 DUK__EMIT_HSTR(js_ctx
, js_ctx
->h_gap
);
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
;
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
;
1638 DUK_ASSERT(js_ctx
->h_gap
!= NULL
);
1639 DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(js_ctx
->h_gap
) > 0); /* caller guarantees */
1641 DUK__EMIT_1(js_ctx
, 0x0a);
1642 if (DUK_UNLIKELY(depth
== 0)) {
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.
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);
1656 need_bytes
= gap_len
* depth
;
1657 p
= DUK_BW_ENSURE_GETPTR(js_ctx
->thr
, &js_ctx
->bw
, need_bytes
);
1660 DUK_MEMCPY((void *) p
, (const void *) gap_data
, (size_t) gap_len
);
1662 avail_bytes
= gap_len
;
1663 DUK_ASSERT(need_bytes
>= gap_len
);
1664 need_bytes
-= gap_len
;
1666 while (need_bytes
>= avail_bytes
) {
1667 DUK_MEMCPY((void *) p
, (const void *) p_start
, (size_t) avail_bytes
);
1669 need_bytes
-= avail_bytes
;
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
);
1676 /*avail_bytes += need_bytes*/
1678 DUK_BW_SET_PTR(js_ctx
->thr
, &js_ctx
->bw
, p
);
1680 #endif /* DUK_USE_PREFER_SIZE */
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
;
1688 *entry_top
= duk_get_top(ctx
);
1690 duk_require_stack(ctx
, DUK_JSON_ENC_REQSTACK
);
1692 /* Loop check using a hybrid approach: a fixed-size visited[] array
1693 * with overflow in a loop check object.
1696 h_target
= duk_get_hobject(ctx
, -1); /* object or array */
1697 DUK_ASSERT(h_target
!= NULL
);
1699 n
= js_ctx
->recursion_depth
;
1700 if (DUK_UNLIKELY(n
> DUK_JSON_ENC_LOOPARRAY
)) {
1701 n
= DUK_JSON_ENC_LOOPARRAY
;
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
);
1709 if (js_ctx
->recursion_depth
< DUK_JSON_ENC_LOOPARRAY
) {
1710 js_ctx
->visiting
[js_ctx
->recursion_depth
] = h_target
;
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
);
1717 duk_push_true(ctx
); /* -> [ ... voidp true ] */
1718 duk_put_prop(ctx
, js_ctx
->idx_loop
); /* -> [ ... ] */
1721 /* C recursion check. */
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
);
1728 js_ctx
->recursion_depth
++;
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
)));
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
;
1739 /* C recursion check. */
1741 DUK_ASSERT(js_ctx
->recursion_depth
> 0);
1742 DUK_ASSERT(js_ctx
->recursion_depth
<= js_ctx
->recursion_limit
);
1743 js_ctx
->recursion_depth
--;
1747 h_target
= duk_get_hobject(ctx
, *entry_top
- 1); /* original target at entry_top - 1 */
1748 DUK_ASSERT(h_target
!= NULL
);
1750 if (js_ctx
->recursion_depth
< DUK_JSON_ENC_LOOPARRAY
) {
1751 /* Previous entry was inside visited[], nothing to do. */
1753 duk_push_sprintf(ctx
, DUK_STR_FMT_PTR
, (void *) h_target
);
1754 duk_del_prop(ctx
, js_ctx
->idx_loop
); /* -> [ ... ] */
1757 /* Restore stack top after unbalanced code paths. */
1758 duk_set_top(ctx
, *entry_top
);
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
)));
1764 /* The JO(value) operation: encode object.
1766 * Stack policy: [ object ] -> [ object ].
1768 DUK_LOCAL
void duk__enc_object(duk_json_enc_ctx
*js_ctx
) {
1769 duk_context
*ctx
= (duk_context
*) js_ctx
->thr
;
1771 duk_idx_t entry_top
;
1775 duk_uarridx_t arr_len
, i
;
1776 duk_size_t prev_size
;
1778 DUK_DDD(DUK_DDDPRINT("duk__enc_object: obj=%!T", (duk_tval
*) duk_get_tval(ctx
, -1)));
1780 duk__enc_objarr_entry(js_ctx
, &entry_top
);
1782 idx_obj
= entry_top
- 1;
1784 if (js_ctx
->idx_proplist
>= 0) {
1785 idx_keys
= js_ctx
->idx_proplist
;
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 */
1794 DUK_DDD(DUK_DDDPRINT("idx_keys=%ld, h_keys=%!T",
1795 (long) idx_keys
, (duk_tval
*) duk_get_tval(ctx
, idx_keys
)));
1797 /* Steps 8-10 have been merged to avoid a "partial" variable. */
1799 DUK__EMIT_1(js_ctx
, DUK_ASC_LCURLY
);
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).
1807 arr_len
= (duk_uarridx_t
) duk_get_length(ctx
, idx_keys
);
1809 for (i
= 0; i
< arr_len
; i
++) {
1810 duk_get_prop_index(ctx
, idx_keys
, i
); /* -> [ ... key ] */
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)));
1816 h_key
= duk_get_hstring(ctx
, -1);
1817 DUK_ASSERT(h_key
!= NULL
);
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
);
1825 duk__enc_key_autoquote(js_ctx
, h_key
);
1826 DUK__EMIT_1(js_ctx
, DUK_ASC_COLON
);
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.
1835 DUK_BW_SET_SIZE(js_ctx
->thr
, &js_ctx
->bw
, prev_size
);
1837 DUK__EMIT_1(js_ctx
, DUK_ASC_COMMA
);
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);
1852 DUK__EMIT_1(js_ctx
, DUK_ASC_RCURLY
);
1854 duk__enc_objarr_exit(js_ctx
, &entry_top
);
1856 DUK_ASSERT_TOP(ctx
, entry_top
);
1859 /* The JA(value) operation: encode array.
1861 * Stack policy: [ array ] -> [ array ].
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
;
1868 duk_uarridx_t i
, arr_len
;
1870 DUK_DDD(DUK_DDDPRINT("duk__enc_array: array=%!T",
1871 (duk_tval
*) duk_get_tval(ctx
, -1)));
1873 duk__enc_objarr_entry(js_ctx
, &entry_top
);
1875 idx_arr
= entry_top
- 1;
1877 /* Steps 8-10 have been merged to avoid a "partial" variable. */
1879 DUK__EMIT_1(js_ctx
, DUK_ASC_LBRACKET
);
1881 arr_len
= (duk_uarridx_t
) duk_get_length(ctx
, idx_arr
);
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
));
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
);
1893 /* XXX: duk_push_uint_string() */
1894 duk_push_uint(ctx
, (duk_uint_t
) i
);
1895 duk_to_string(ctx
, -1); /* -> [ ... key ] */
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
);
1908 DUK__EMIT_1(js_ctx
, DUK_ASC_COMMA
);
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);
1920 DUK__EMIT_1(js_ctx
, DUK_ASC_RBRACKET
);
1922 duk__enc_objarr_exit(js_ctx
, &entry_top
);
1924 DUK_ASSERT_TOP(ctx
, entry_top
);
1927 /* The Str(key, holder) operation.
1929 * Stack policy: [ ... key ] -> [ ... ]
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
;
1936 duk_tval
*tv_holder
;
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)));
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
);
1952 /* -> [ ... key val ] */
1954 DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval
*) duk_get_tval(ctx
, -1)));
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 */
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' ] */
1969 duk_pop(ctx
); /* -> [ ... key val ] */
1973 /* [ ... key val ] */
1975 DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval
*) duk_get_tval(ctx
, -1)));
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' ] */
1988 /* [ ... key val ] */
1990 DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval
*) duk_get_tval(ctx
, -1)));
1992 tv
= DUK_GET_TVAL_NEGIDX(ctx
, -1);
1993 if (DUK_TVAL_IS_OBJECT(tv
)) {
1996 h
= DUK_TVAL_GET_OBJECT(tv
);
1997 DUK_ASSERT(h
!= NULL
);
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
);
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.
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)"));
2015 DUK_DDD(DUK_DDDPRINT("-> bufferobject won't result in undefined, encode directly"));
2016 duk__enc_bufferobject(js_ctx
, h_bufobj
);
2019 DUK_DDD(DUK_DDDPRINT("no JX/JC support, bufferobject/buffer will always result in undefined"));
2023 c
= (duk_small_int_t
) DUK_HOBJECT_GET_CLASS_NUMBER(h
);
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.
2032 DUK_ASSERT(!duk_is_callable(ctx
, -1));
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));
2042 #if defined(DUK_USE_JX) || defined(DUK_USE_JC)
2043 case DUK_HOBJECT_CLASS_POINTER
:
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);
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.
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
);
2068 DUK_DDD(DUK_DDDPRINT("-> will result in undefined (function)"));
2071 #else /* DUK_USE_JX || DUK_USE_JC */
2072 DUK_DDD(DUK_DDDPRINT("-> will result in undefined (function)"));
2074 #endif /* DUK_USE_JX || DUK_USE_JC */
2081 /* [ ... key val ] */
2083 DUK_DDD(DUK_DDDPRINT("value=%!T", (duk_tval
*) duk_get_tval(ctx
, -1)));
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)"));
2090 tv
= DUK_GET_TVAL_NEGIDX(ctx
, -1);
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
);
2100 case DUK_TAG_NULL
: {
2101 DUK__EMIT_STRIDX(js_ctx
, DUK_STRIDX_LC_NULL
);
2104 case DUK_TAG_BOOLEAN
: {
2105 DUK__EMIT_STRIDX(js_ctx
, DUK_TVAL_GET_BOOLEAN(tv
) ?
2106 DUK_STRIDX_TRUE
: DUK_STRIDX_FALSE
);
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
));
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
);
2120 duk__enc_quote_string(js_ctx
, h
);
2123 case DUK_TAG_OBJECT
: {
2124 duk_hobject
*h
= DUK_TVAL_GET_OBJECT(tv
);
2125 DUK_ASSERT(h
!= NULL
);
2127 /* Function values are handled completely above (including
2128 * coercion results):
2130 DUK_ASSERT(!DUK_HOBJECT_IS_CALLABLE(h
));
2132 if (DUK_HOBJECT_GET_CLASS_NUMBER(h
) == DUK_HOBJECT_CLASS_ARRAY
) {
2133 duk__enc_array(js_ctx
);
2135 duk__enc_object(js_ctx
);
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
));
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
);
2152 /* Standard JSON omits functions */
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.
2162 duk__enc_fastint_tval(js_ctx
, tv
);
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.
2172 duk__enc_double(js_ctx
);
2178 duk_pop_2(ctx
); /* [ ... key val ] -> [ ... ] */
2179 return 1; /* emitted */
2182 duk_pop_2(ctx
); /* [ ... key val ] -> [ ... ] */
2183 return 0; /* not emitted */
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
) {
2191 DUK_ASSERT(tv
!= NULL
);
2192 if (DUK_TVAL_IS_STRING(tv
) || DUK_TVAL_IS_NUMBER(tv
)) {
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
) {
2207 * JSON.stringify() fast path
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.
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
;
2219 DUK_DDD(DUK_DDDPRINT("stringify fast: %!T", tv
));
2221 DUK_ASSERT(js_ctx
!= NULL
);
2222 DUK_ASSERT(js_ctx
->thr
!= NULL
);
2224 #if 0 /* disabled for now */
2228 DUK_ASSERT(tv
!= NULL
);
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
);
2237 goto emit_undefined
;
2240 goto emit_undefined
;
2243 case DUK_TAG_NULL
: {
2244 DUK__EMIT_STRIDX(js_ctx
, DUK_STRIDX_LC_NULL
);
2247 case DUK_TAG_BOOLEAN
: {
2248 DUK__EMIT_STRIDX(js_ctx
, DUK_TVAL_GET_BOOLEAN(tv
) ?
2249 DUK_STRIDX_TRUE
: DUK_STRIDX_FALSE
);
2252 case DUK_TAG_STRING
: {
2255 h
= DUK_TVAL_GET_STRING(tv
);
2256 DUK_ASSERT(h
!= NULL
);
2257 duk__enc_quote_string(js_ctx
, h
);
2260 case DUK_TAG_OBJECT
: {
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
;
2267 /* For objects JSON.stringify() only looks for own, enumerable
2268 * properties which is nice for the fast path here.
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).
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!).
2282 /* XXX: for real world code, could just ignore array inheritance
2283 * and only look at array own properties.
2286 /* We rely on a few object flag / class number relationships here,
2290 obj
= DUK_TVAL_GET_OBJECT(tv
);
2291 DUK_ASSERT(obj
!= NULL
);
2292 DUK_ASSERT_HOBJECT_VALID(obj
);
2294 /* Once recursion depth is increased, exit path must decrease
2295 * it (though it's OK to abort the fast path).
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
);
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
);
2312 /* Guaranteed by recursion_limit setup so we don't have to
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
++;
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.
2323 * If an object is a Proxy we also can't avoid side effects
2326 /* XXX: non-callable .toJSON() doesn't need to cause an abort
2327 * but does at the moment, probably not worth fixing.
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
;
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.
2340 /* XXX: move masks to js_ctx? they don't change during one
2341 * fast path invocation.
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
;
2355 c_object
= c_all
& ~(c_array
| c_unbox
| c_func
| c_bufobj
| c_undef
);
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
;
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
);
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
);
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.
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
;
2387 for (i
= 0; i
< (duk_uint_fast32_t
) DUK_HOBJECT_GET_ENEXT(obj
); i
++) {
2389 duk_size_t prev_size
;
2391 k
= DUK_HOBJECT_E_GET_KEY(js_ctx
->thr
->heap
, obj
, i
);
2395 if (!DUK_HOBJECT_E_SLOT_IS_ENUMERABLE(js_ctx
->thr
->heap
, obj
, i
)) {
2398 if (DUK_HOBJECT_E_SLOT_IS_ACCESSOR(js_ctx
->thr
->heap
, obj
, i
)) {
2399 /* Getter might have arbitrary side effects,
2402 DUK_DD(DUK_DDPRINT("property is an accessor, abort fast path"));
2403 goto abort_fastpath
;
2405 if (DUK_HSTRING_HAS_INTERNAL(k
)) {
2409 tv_val
= DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(js_ctx
->thr
->heap
, obj
, i
);
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
);
2417 duk__enc_key_autoquote(js_ctx
, k
);
2418 DUK__EMIT_1(js_ctx
, DUK_ASC_COLON
);
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
);
2425 DUK__EMIT_1(js_ctx
, DUK_ASC_COMMA
);
2430 /* If any non-Array value had enumerable virtual own
2431 * properties, they should be serialized here. Standard
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);
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
;
2448 DUK__EMIT_1(js_ctx
, DUK_ASC_LBRACKET
);
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
;
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.
2462 DUK_DD(DUK_DDPRINT("arr_len > asize, abort fast path"));
2463 goto abort_fastpath
;
2465 /* Array part may be larger than 'length'; if so, iterate
2466 * only up to array 'length'.
2468 for (i
= 0; i
< arr_len
; i
++) {
2469 DUK_ASSERT(i
< (duk_uint_fast32_t
) DUK_HOBJECT_GET_ASIZE(obj
));
2471 tv_val
= DUK_HOBJECT_A_GET_VALUE_PTR(js_ctx
->thr
->heap
, obj
, i
);
2473 if (DUK_UNLIKELY(js_ctx
->h_gap
!= NULL
)) {
2474 duk__enc_newline_indent(js_ctx
, js_ctx
->recursion_depth
);
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.
2483 duk_bool_t has_inherited
;
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
);
2492 if (has_inherited
) {
2493 DUK_D(DUK_DPRINT("gap in array, conflicting inherited property, abort fast path"));
2494 goto abort_fastpath
;
2497 /* Ordinary gap, undefined encodes to 'null' in
2498 * standard JSON (and no JX/JC support here now).
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
);
2504 DUK__EMIT_STRIDX(js_ctx
, DUK_STRIDX_LC_NULL
);
2507 if (duk__json_stringify_fast_value(js_ctx
, tv_val
) == 0) {
2508 DUK__EMIT_STRIDX(js_ctx
, DUK_STRIDX_LC_NULL
);
2512 DUK__EMIT_1(js_ctx
, DUK_ASC_COMMA
);
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);
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).
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.
2537 goto abort_fastpath
;
2538 #else /* disabled */
2539 /* Disabled until fixed, see above. */
2540 duk_tval
*tv_internal
;
2542 DUK_DD(DUK_DDPRINT("auto unboxing in fast path"));
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
));
2552 DUK_ASSERT(js_ctx
->recursion_depth
> 0);
2553 js_ctx
->recursion_depth
--; /* required to keep recursion depth correct */
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
);
2563 DUK_ASSERT((c_bit
& c_undef
) != 0);
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
;
2572 DUK_ASSERT(js_ctx
->recursion_depth
> 0);
2573 DUK_ASSERT(js_ctx
->recursion_depth
<= js_ctx
->recursion_limit
);
2574 js_ctx
->recursion_depth
--;
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
));
2583 goto emit_undefined
;
2586 goto emit_undefined
;
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
));
2595 goto emit_undefined
;
2598 goto emit_undefined
;
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.
2606 DUK_DD(DUK_DDPRINT("value is a lightfunc, abort fast path"));
2607 goto abort_fastpath
;
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.
2614 duk__enc_fastint_tval(js_ctx
, tv
);
2619 /* XXX: A fast path for usual integers would be useful when
2620 * fastint support is not enabled.
2622 DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv
));
2623 DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv
));
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
);
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.
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
);
2645 return 1; /* not undefined */
2648 return 0; /* value was undefined/unsupported */
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 */
2657 DUK_LOCAL duk_ret_t
duk__json_stringify_fast(duk_context
*ctx
) {
2658 duk_json_enc_ctx
*js_ctx
;
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
);
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 */
2675 #endif /* DUK_USE_JSON_STRINGIFY_FASTPATH */
2678 * Top level wrappers
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
);
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);
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
)));
2704 DUK_MEMZERO(&js_ctx_alloc
, sizeof(js_ctx_alloc
));
2706 #ifdef DUK_USE_EXPLICIT_NULL_INIT
2709 js_ctx
->recursion_limit
= DUK_USE_JSON_DEC_RECLIMIT
;
2710 DUK_ASSERT(js_ctx
->recursion_depth
== 0);
2712 /* Flag handling currently assumes that flags are consistent. This is OK
2713 * because the call sites are now strictly controlled.
2716 js_ctx
->flags
= flags
;
2717 #if defined(DUK_USE_JX)
2718 js_ctx
->flag_ext_custom
= flags
& DUK_JSON_FLAG_EXT_CUSTOM
;
2720 #if defined(DUK_USE_JC)
2721 js_ctx
->flag_ext_compatible
= flags
& DUK_JSON_FLAG_EXT_COMPATIBLE
;
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
);
2727 h_text
= duk_to_hstring(ctx
, idx_value
); /* coerce in-place */
2728 DUK_ASSERT(h_text
!= NULL
);
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.
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);
2740 duk__dec_value(js_ctx
); /* -> [ ... value ] */
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.
2746 if (js_ctx
->p
!= js_ctx
->p_end
) {
2747 duk__dec_syntax_error(js_ctx
);
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
)));
2754 js_ctx
->idx_reviver
= idx_reviver
;
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 "" ] */
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)));
2765 duk__dec_reviver_walk(js_ctx
); /* [ ... val root "" ] -> [ ... val val' ] */
2766 duk_remove(ctx
, -2); /* -> [ ... val' ] */
2768 DUK_DDD(DUK_DDDPRINT("reviver does not exist or is not callable: %!T",
2769 (duk_tval
*) duk_get_tval(ctx
, idx_reviver
)));
2772 /* Final result is at stack top. */
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
)));
2781 DUK_ASSERT(duk_get_top(ctx
) == entry_top
+ 1);
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
;
2794 duk_idx_t idx_holder
;
2795 duk_idx_t entry_top
;
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);
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
)));
2809 entry_top
= duk_get_top(ctx
);
2815 DUK_MEMZERO(&js_ctx_alloc
, sizeof(js_ctx_alloc
));
2817 #ifdef DUK_USE_EXPLICIT_NULL_INIT
2818 js_ctx
->h_replacer
= NULL
;
2819 js_ctx
->h_gap
= NULL
;
2821 js_ctx
->idx_proplist
= -1;
2823 /* Flag handling currently assumes that flags are consistent. This is OK
2824 * because the call sites are now strictly controlled.
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
;
2831 js_ctx
->flag_ext_custom
= flags
& DUK_JSON_FLAG_EXT_CUSTOM
;
2834 js_ctx
->flag_ext_compatible
= flags
& DUK_JSON_FLAG_EXT_COMPATIBLE
;
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
);
2840 /* The #ifdef clutter here handles the JX/JC enable/disable
2841 * combinations properly.
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
;
2856 #endif /* DUK_USE_JX */
2857 #if defined(DUK_USE_JX) && defined(DUK_USE_JC)
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
;
2868 #endif /* DUK_USE_JC */
2869 #endif /* DUK_USE_JX || DUK_USE_JC */
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 */
2877 #endif /* DUK_USE_JX || DUK_USE_JC */
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
;
2885 DUK_BW_INIT_PUSHBUF(thr
, &js_ctx
->bw
, DUK__JSON_STRINGIFY_BUFSIZE
);
2887 js_ctx
->idx_loop
= duk_push_object_internal(ctx
);
2888 DUK_ASSERT(js_ctx
->idx_loop
>= 0);
2890 /* [ ... buf loop ] */
2893 * Process replacer/proplist (2nd argument to JSON.stringify)
2896 h
= duk_get_hobject(ctx
, idx_replacer
);
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.
2907 duk_uarridx_t plist_idx
= 0;
2908 duk_small_uint_t enum_flags
;
2910 js_ctx
->idx_proplist
= duk_push_array(ctx
); /* XXX: array internal? */
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 ] */
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)));
2933 duk_pop(ctx
); /* pop enum */
2935 /* [ ... proplist ] */
2939 /* [ ... buf loop (proplist) ] */
2942 * Process space (3rd argument to JSON.stringify)
2945 h
= duk_get_hobject(ctx
, idx_space
);
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
);
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 */
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);
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
);
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
;
2988 /* [ ... buf loop (proplist) (gap) ] */
2991 * Fast path: assume no mutation, iterate object property tables
2992 * directly; bail out if that assumption doesn't hold.
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 */
2999 #ifdef DUK_USE_MARK_AND_SWEEP
3000 duk_small_uint_t prev_mark_and_sweep_base_flags
;
3003 DUK_DD(DUK_DDPRINT("try JSON.stringify() fast path"));
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
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);
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).
3020 duk_push_pointer(ctx
, (void *) js_ctx
);
3021 duk_dup(ctx
, idx_value
);
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 */
3031 pcall_rc
= duk_safe_call(ctx
, duk__json_stringify_fast
, 2 /*nargs*/, 0 /*nret*/);
3033 #if defined(DUK_USE_MARK_AND_SWEEP)
3034 thr
->heap
->mark_and_sweep_base_flags
= prev_mark_and_sweep_base_flags
;
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
;
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
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;
3054 * Create wrapper object and serialize
3057 idx_holder
= duk_push_object(ctx
);
3058 duk_dup(ctx
, idx_value
);
3059 duk_put_prop_stridx(ctx
, -2, DUK_STRIDX_EMPTY_STRING
);
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)));
3070 /* serialize the wrapper with empty string key */
3072 duk_push_hstring_stridx(ctx
, DUK_STRIDX_EMPTY_STRING
);
3074 /* [ ... buf loop (proplist) (gap) holder "" ] */
3076 js_ctx
->recursion_limit
= DUK_USE_JSON_ENC_RECLIMIT
;
3077 DUK_ASSERT(js_ctx
->recursion_depth
== 0);
3079 if (DUK_UNLIKELY(duk__enc_value(js_ctx
, idx_holder
) == 0)) { /* [ ... holder key ] -> [ ... holder ] */
3080 /* Result is undefined. */
3081 duk_push_undefined(ctx
);
3083 /* Convert buffer to result string. */
3084 DUK_BW_PUSH_AS_STRING(thr
, &js_ctx
->bw
);
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
)));
3096 /* The stack has a variable shape here, so force it to the
3097 * desired one explicitly.
3100 #if defined(DUK_USE_JSON_STRINGIFY_FASTPATH)
3103 duk_replace(ctx
, entry_top
);
3104 duk_set_top(ctx
, entry_top
+ 1);
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
)));
3115 DUK_ASSERT(duk_get_top(ctx
) == entry_top
+ 1);
3122 DUK_INTERNAL duk_ret_t
duk_bi_json_object_parse(duk_context
*ctx
) {
3123 duk_bi_json_parse_helper(ctx
,
3130 DUK_INTERNAL duk_ret_t
duk_bi_json_object_stringify(duk_context
*ctx
) {
3131 duk_bi_json_stringify_helper(ctx
,
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