Line | Count | Source |
1 | | |
2 | | /* |
3 | | * Copyright (C) Dmitry Volyntsev |
4 | | * Copyright (C) NGINX, Inc. |
5 | | */ |
6 | | |
7 | | |
8 | | #include <njs_main.h> |
9 | | |
10 | | |
11 | | typedef struct { |
12 | | njs_vm_t *vm; |
13 | | njs_mp_t *pool; |
14 | | njs_uint_t depth; |
15 | | const u_char *start; |
16 | | const u_char *end; |
17 | | } njs_json_parse_ctx_t; |
18 | | |
19 | | |
20 | | typedef struct { |
21 | | njs_value_t value; |
22 | | |
23 | | uint8_t written; /* 1 bit */ |
24 | | uint8_t array; /* 1 bit */ |
25 | | uint8_t fast_array; /* 1 bit */ |
26 | | |
27 | | int64_t index; |
28 | | int64_t length; |
29 | | njs_array_t *keys; |
30 | | njs_value_t *key; |
31 | | njs_value_t prop; |
32 | | } njs_json_state_t; |
33 | | |
34 | | |
35 | | typedef struct { |
36 | | njs_value_t retval; |
37 | | |
38 | | njs_vm_t *vm; |
39 | | |
40 | | njs_uint_t depth; |
41 | 2.75k | #define NJS_JSON_MAX_DEPTH 32 |
42 | | njs_json_state_t states[NJS_JSON_MAX_DEPTH]; |
43 | | |
44 | | njs_value_t replacer; |
45 | | njs_str_t space; |
46 | | u_char space_buf[16]; |
47 | | uint32_t keys_type; |
48 | | } njs_json_stringify_t; |
49 | | |
50 | | |
51 | | static const u_char *njs_json_parse_value(njs_json_parse_ctx_t *ctx, |
52 | | njs_value_t *value, const u_char *p); |
53 | | static const u_char *njs_json_parse_object(njs_json_parse_ctx_t *ctx, |
54 | | njs_value_t *value, const u_char *p); |
55 | | static const u_char *njs_json_parse_array(njs_json_parse_ctx_t *ctx, |
56 | | njs_value_t *value, const u_char *p); |
57 | | static const u_char *njs_json_parse_string(njs_json_parse_ctx_t *ctx, |
58 | | njs_value_t *value, const u_char *p); |
59 | | static const u_char *njs_json_parse_number(njs_json_parse_ctx_t *ctx, |
60 | | njs_value_t *value, const u_char *p); |
61 | | njs_inline uint32_t njs_json_unicode(const u_char *p); |
62 | | static const u_char *njs_json_skip_space(const u_char *start, |
63 | | const u_char *end); |
64 | | |
65 | | static njs_int_t njs_json_internalize_property(njs_vm_t *vm, |
66 | | njs_function_t *reviver, njs_value_t *holder, uint32_t atom_id, |
67 | | njs_int_t depth, njs_value_t *retval); |
68 | | static void njs_json_parse_exception(njs_json_parse_ctx_t *ctx, |
69 | | const char *msg, const u_char *pos); |
70 | | |
71 | | static njs_int_t njs_json_stringify_iterator(njs_json_stringify_t *stringify, |
72 | | njs_value_t *value, njs_value_t *retval); |
73 | | static njs_function_t *njs_object_to_json_function(njs_vm_t *vm, |
74 | | njs_value_t *value); |
75 | | static njs_int_t njs_json_stringify_to_json(njs_json_stringify_t* stringify, |
76 | | njs_json_state_t *state, njs_value_t *key, njs_value_t *value); |
77 | | static njs_int_t njs_json_stringify_replacer(njs_json_stringify_t* stringify, |
78 | | njs_json_state_t *state, njs_value_t *key, njs_value_t *value); |
79 | | static njs_int_t njs_json_stringify_array(njs_json_stringify_t *stringify); |
80 | | |
81 | | static njs_int_t njs_json_append_value(njs_vm_t *vm, njs_chb_t *chain, |
82 | | njs_value_t *value); |
83 | | static void njs_json_append_string(njs_vm_t *vm, njs_chb_t *chain, |
84 | | const njs_value_t *value, char quote); |
85 | | static void njs_json_append_number(njs_chb_t *chain, const njs_value_t *value); |
86 | | |
87 | | static njs_object_t *njs_json_wrap_value(njs_vm_t *vm, njs_value_t *wrapper, |
88 | | const njs_value_t *value); |
89 | | |
90 | | |
91 | | static const njs_object_prop_init_t njs_json_object_properties[]; |
92 | | |
93 | | |
94 | | static njs_int_t |
95 | | njs_json_parse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
96 | | njs_index_t unused, njs_value_t *retval) |
97 | 2.75k | { |
98 | 2.75k | njs_int_t ret; |
99 | 2.75k | njs_value_t *text, value, lvalue, wrapper; |
100 | 2.75k | njs_object_t *obj; |
101 | 2.75k | const u_char *p, *end; |
102 | 2.75k | const njs_value_t *reviver; |
103 | 2.75k | njs_string_prop_t string; |
104 | 2.75k | njs_json_parse_ctx_t ctx; |
105 | | |
106 | 2.75k | text = njs_lvalue_arg(&lvalue, args, nargs, 1); |
107 | | |
108 | 2.75k | if (njs_slow_path(!njs_is_string(text))) { |
109 | 2.53k | ret = njs_value_to_string(vm, text, text); |
110 | 2.53k | if (njs_slow_path(ret != NJS_OK)) { |
111 | 0 | return ret; |
112 | 0 | } |
113 | 2.53k | } |
114 | | |
115 | 2.75k | (void) njs_string_prop(vm, &string, text); |
116 | | |
117 | 2.75k | p = string.start; |
118 | 2.75k | end = p + string.size; |
119 | | |
120 | 2.75k | ctx.vm = vm; |
121 | 2.75k | ctx.pool = vm->mem_pool; |
122 | 2.75k | ctx.depth = NJS_JSON_MAX_DEPTH; |
123 | 2.75k | ctx.start = string.start; |
124 | 2.75k | ctx.end = end; |
125 | | |
126 | 2.75k | p = njs_json_skip_space(p, end); |
127 | 2.75k | if (njs_slow_path(p == end)) { |
128 | 0 | njs_json_parse_exception(&ctx, "Unexpected end of input", p); |
129 | 0 | return NJS_ERROR; |
130 | 0 | } |
131 | | |
132 | 2.75k | p = njs_json_parse_value(&ctx, &value, p); |
133 | 2.75k | if (njs_slow_path(p == NULL)) { |
134 | 1.29k | return NJS_ERROR; |
135 | 1.29k | } |
136 | | |
137 | 1.46k | p = njs_json_skip_space(p, end); |
138 | 1.46k | if (njs_slow_path(p != end)) { |
139 | 0 | njs_json_parse_exception(&ctx, "Unexpected token", p); |
140 | 0 | return NJS_ERROR; |
141 | 0 | } |
142 | | |
143 | 1.46k | reviver = njs_arg(args, nargs, 2); |
144 | | |
145 | 1.46k | if (njs_slow_path(njs_is_function(reviver))) { |
146 | 220 | obj = njs_json_wrap_value(vm, &wrapper, &value); |
147 | 220 | if (njs_slow_path(obj == NULL)) { |
148 | 0 | return NJS_ERROR; |
149 | 0 | } |
150 | | |
151 | 220 | return njs_json_internalize_property(vm, njs_function(reviver), |
152 | 220 | &wrapper, NJS_ATOM_STRING_empty, 0, |
153 | 220 | retval); |
154 | 220 | } |
155 | | |
156 | 1.24k | njs_value_assign(retval, &value); |
157 | | |
158 | 1.24k | return NJS_OK; |
159 | 1.46k | } |
160 | | |
161 | | |
162 | | njs_int_t |
163 | | njs_vm_json_parse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
164 | | njs_value_t *retval) |
165 | 0 | { |
166 | 0 | njs_function_t *parse; |
167 | |
|
168 | 0 | parse = njs_function(&njs_json_object_properties[1].desc.u.value); |
169 | |
|
170 | 0 | return njs_vm_invoke(vm, parse, args, nargs, retval); |
171 | 0 | } |
172 | | |
173 | | |
174 | | static njs_int_t |
175 | | njs_json_stringify(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
176 | | njs_index_t unused, njs_value_t *retval) |
177 | 0 | { |
178 | 0 | size_t length; |
179 | 0 | int64_t i64; |
180 | 0 | njs_int_t i; |
181 | 0 | njs_int_t ret; |
182 | 0 | njs_value_t *replacer, *space; |
183 | 0 | const u_char *p; |
184 | 0 | njs_string_prop_t prop; |
185 | 0 | njs_json_stringify_t *stringify, json_stringify; |
186 | |
|
187 | 0 | stringify = &json_stringify; |
188 | |
|
189 | 0 | stringify->vm = vm; |
190 | 0 | stringify->depth = 0; |
191 | 0 | stringify->keys_type = NJS_ENUM_STRING; |
192 | |
|
193 | 0 | replacer = njs_arg(args, nargs, 2); |
194 | |
|
195 | 0 | if (njs_is_function(replacer) || njs_is_array(replacer)) { |
196 | 0 | stringify->replacer = *replacer; |
197 | 0 | if (njs_is_array(replacer)) { |
198 | 0 | ret = njs_json_stringify_array(stringify); |
199 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
200 | 0 | goto memory_error; |
201 | 0 | } |
202 | 0 | } |
203 | |
|
204 | 0 | } else { |
205 | 0 | njs_set_undefined(&stringify->replacer); |
206 | 0 | } |
207 | | |
208 | 0 | space = njs_arg(args, nargs, 3); |
209 | |
|
210 | 0 | if (njs_is_object(space)) { |
211 | 0 | if (njs_is_object_number(space)) { |
212 | 0 | ret = njs_value_to_numeric(vm, space, space); |
213 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
214 | 0 | return ret; |
215 | 0 | } |
216 | |
|
217 | 0 | } else if (njs_is_object_string(space)) { |
218 | 0 | ret = njs_value_to_string(vm, space, space); |
219 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
220 | 0 | return ret; |
221 | 0 | } |
222 | 0 | } |
223 | 0 | } |
224 | | |
225 | 0 | switch (space->type) { |
226 | 0 | case NJS_STRING: |
227 | 0 | length = njs_string_prop(vm, &prop, space); |
228 | 0 | p = njs_string_offset(&prop, njs_min(length, 10)); |
229 | |
|
230 | 0 | stringify->space.start = prop.start; |
231 | 0 | stringify->space.length = p - prop.start; |
232 | |
|
233 | 0 | break; |
234 | | |
235 | 0 | case NJS_NUMBER: |
236 | 0 | i64 = njs_min(njs_number_to_integer(njs_number(space)), 10); |
237 | |
|
238 | 0 | if (i64 > 0) { |
239 | 0 | stringify->space.length = i64; |
240 | 0 | stringify->space.start = stringify->space_buf; |
241 | |
|
242 | 0 | for (i = 0; i < i64; i++) { |
243 | 0 | stringify->space.start[i] = ' '; |
244 | 0 | } |
245 | |
|
246 | 0 | break; |
247 | 0 | } |
248 | | |
249 | | /* Fall through. */ |
250 | | |
251 | 0 | default: |
252 | 0 | stringify->space.length = 0; |
253 | |
|
254 | 0 | break; |
255 | 0 | } |
256 | | |
257 | 0 | return njs_json_stringify_iterator(stringify, njs_arg(args, nargs, 1), |
258 | 0 | retval); |
259 | | |
260 | 0 | memory_error: |
261 | |
|
262 | 0 | njs_memory_error(vm); |
263 | |
|
264 | 0 | return NJS_ERROR; |
265 | 0 | } |
266 | | |
267 | | |
268 | | njs_int_t |
269 | | njs_vm_json_stringify(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, |
270 | | njs_value_t *retval) |
271 | 0 | { |
272 | 0 | njs_function_t *stringify; |
273 | |
|
274 | 0 | stringify = njs_function(&njs_json_object_properties[2].desc.u.value); |
275 | |
|
276 | 0 | return njs_vm_invoke(vm, stringify, args, nargs, retval); |
277 | 0 | } |
278 | | |
279 | | |
280 | | static const u_char * |
281 | | njs_json_parse_value(njs_json_parse_ctx_t *ctx, njs_value_t *value, |
282 | | const u_char *p) |
283 | 5.55k | { |
284 | 5.55k | switch (*p) { |
285 | 1.51k | case '{': |
286 | 1.51k | return njs_json_parse_object(ctx, value, p); |
287 | | |
288 | 769 | case '[': |
289 | 769 | return njs_json_parse_array(ctx, value, p); |
290 | | |
291 | 1.24k | case '"': |
292 | 1.24k | return njs_json_parse_string(ctx, value, p); |
293 | | |
294 | 0 | case 't': |
295 | 0 | if (njs_fast_path(ctx->end - p >= 4 && memcmp(p, "true", 4) == 0)) { |
296 | 0 | *value = njs_value_true; |
297 | |
|
298 | 0 | return p + 4; |
299 | 0 | } |
300 | | |
301 | 0 | goto error; |
302 | | |
303 | 0 | case 'f': |
304 | 0 | if (njs_fast_path(ctx->end - p >= 5 && memcmp(p, "false", 5) == 0)) { |
305 | 0 | *value = njs_value_false; |
306 | |
|
307 | 0 | return p + 5; |
308 | 0 | } |
309 | | |
310 | 0 | goto error; |
311 | | |
312 | 0 | case 'n': |
313 | 0 | if (njs_fast_path(ctx->end - p >= 4 && memcmp(p, "null", 4) == 0)) { |
314 | 0 | *value = njs_value_null; |
315 | |
|
316 | 0 | return p + 4; |
317 | 0 | } |
318 | | |
319 | 0 | goto error; |
320 | 5.55k | } |
321 | | |
322 | 2.03k | if (njs_fast_path(*p == '-' || (*p - '0') <= 9)) { |
323 | 2.03k | return njs_json_parse_number(ctx, value, p); |
324 | 2.03k | } |
325 | | |
326 | 0 | error: |
327 | |
|
328 | 0 | njs_json_parse_exception(ctx, "Unexpected token", p); |
329 | |
|
330 | 0 | return NULL; |
331 | 2.03k | } |
332 | | |
333 | | |
334 | | static const u_char * |
335 | | njs_json_parse_object(njs_json_parse_ctx_t *ctx, njs_value_t *value, |
336 | | const u_char *p) |
337 | 1.51k | { |
338 | 1.51k | njs_int_t ret; |
339 | 1.51k | njs_object_t *object; |
340 | 1.51k | njs_value_t prop_name, prop_value; |
341 | 1.51k | njs_object_prop_t *prop; |
342 | 1.51k | njs_flathsh_query_t fhq; |
343 | | |
344 | 1.51k | if (njs_slow_path(--ctx->depth == 0)) { |
345 | 0 | njs_json_parse_exception(ctx, "Nested too deep", p); |
346 | 0 | return NULL; |
347 | 0 | } |
348 | | |
349 | 1.51k | object = njs_object_alloc(ctx->vm); |
350 | 1.51k | if (njs_slow_path(object == NULL)) { |
351 | 0 | goto memory_error; |
352 | 0 | } |
353 | | |
354 | 1.51k | prop = NULL; |
355 | | |
356 | 2.55k | for ( ;; ) { |
357 | 2.55k | p = njs_json_skip_space(p + 1, ctx->end); |
358 | 2.55k | if (njs_slow_path(p == ctx->end)) { |
359 | 525 | goto error_end; |
360 | 525 | } |
361 | | |
362 | 2.03k | if (*p != '"') { |
363 | 0 | if (njs_fast_path(*p == '}')) { |
364 | 0 | if (njs_slow_path(prop != NULL)) { |
365 | 0 | njs_json_parse_exception(ctx, "Trailing comma", p - 1); |
366 | 0 | return NULL; |
367 | 0 | } |
368 | | |
369 | 0 | break; |
370 | 0 | } |
371 | | |
372 | 0 | goto error_token; |
373 | 0 | } |
374 | | |
375 | 2.03k | p = njs_json_parse_string(ctx, &prop_name, p); |
376 | 2.03k | if (njs_slow_path(p == NULL)) { |
377 | | /* The exception is set by the called function. */ |
378 | 0 | return NULL; |
379 | 0 | } |
380 | | |
381 | 2.03k | p = njs_json_skip_space(p, ctx->end); |
382 | 2.03k | if (njs_slow_path(p == ctx->end || *p != ':')) { |
383 | 0 | goto error_token; |
384 | 0 | } |
385 | | |
386 | 2.03k | p = njs_json_skip_space(p + 1, ctx->end); |
387 | 2.03k | if (njs_slow_path(p == ctx->end)) { |
388 | 0 | goto error_end; |
389 | 0 | } |
390 | | |
391 | 2.03k | p = njs_json_parse_value(ctx, &prop_value, p); |
392 | 2.03k | if (njs_slow_path(p == NULL)) { |
393 | | /* The exception is set by the called function. */ |
394 | 0 | return NULL; |
395 | 0 | } |
396 | | |
397 | 2.03k | fhq.key_hash = prop_name.atom_id; |
398 | 2.03k | fhq.replace = 1; |
399 | 2.03k | fhq.pool = ctx->pool; |
400 | 2.03k | fhq.proto = &njs_object_hash_proto; |
401 | | |
402 | 2.03k | ret = njs_flathsh_unique_insert(&object->hash, &fhq); |
403 | 2.03k | if (njs_slow_path(ret != NJS_OK)) { |
404 | 0 | njs_internal_error(ctx->vm, "flathsh insert/replace failed"); |
405 | 0 | return NULL; |
406 | 0 | } |
407 | | |
408 | 2.03k | prop = fhq.value; |
409 | | |
410 | 2.03k | prop->type = NJS_PROPERTY; |
411 | 2.03k | prop->enumerable = 1; |
412 | 2.03k | prop->configurable = 1; |
413 | 2.03k | prop->writable = 1; |
414 | 2.03k | prop->u.value = prop_value; |
415 | | |
416 | 2.03k | p = njs_json_skip_space(p, ctx->end); |
417 | 2.03k | if (njs_slow_path(p == ctx->end)) { |
418 | 0 | goto error_end; |
419 | 0 | } |
420 | | |
421 | 2.03k | if (*p != ',') { |
422 | 989 | if (njs_fast_path(*p == '}')) { |
423 | 220 | break; |
424 | 220 | } |
425 | | |
426 | 769 | goto error_token; |
427 | 989 | } |
428 | 2.03k | } |
429 | | |
430 | 220 | njs_set_object(value, object); |
431 | | |
432 | 220 | ctx->depth++; |
433 | | |
434 | 220 | return p + 1; |
435 | | |
436 | 769 | error_token: |
437 | | |
438 | 769 | njs_json_parse_exception(ctx, "Unexpected token", p); |
439 | | |
440 | 769 | return NULL; |
441 | | |
442 | 525 | error_end: |
443 | | |
444 | 525 | njs_json_parse_exception(ctx, "Unexpected end of input", p); |
445 | | |
446 | 525 | return NULL; |
447 | | |
448 | 0 | memory_error: |
449 | |
|
450 | 0 | njs_memory_error(ctx->vm); |
451 | |
|
452 | 0 | return NULL; |
453 | 1.51k | } |
454 | | |
455 | | |
456 | | static const u_char * |
457 | | njs_json_parse_array(njs_json_parse_ctx_t *ctx, njs_value_t *value, |
458 | | const u_char *p) |
459 | 769 | { |
460 | 769 | njs_int_t ret; |
461 | 769 | njs_bool_t empty; |
462 | 769 | njs_array_t *array; |
463 | 769 | njs_value_t element; |
464 | | |
465 | 769 | if (njs_slow_path(--ctx->depth == 0)) { |
466 | 0 | njs_json_parse_exception(ctx, "Nested too deep", p); |
467 | 0 | return NULL; |
468 | 0 | } |
469 | | |
470 | 769 | array = njs_array_alloc(ctx->vm, 0, 0, NJS_ARRAY_SPARE); |
471 | 769 | if (njs_slow_path(array == NULL)) { |
472 | 0 | return NULL; |
473 | 0 | } |
474 | | |
475 | 769 | empty = 1; |
476 | | |
477 | 769 | for ( ;; ) { |
478 | 769 | p = njs_json_skip_space(p + 1, ctx->end); |
479 | 769 | if (njs_slow_path(p == ctx->end)) { |
480 | 0 | goto error_end; |
481 | 0 | } |
482 | | |
483 | 769 | if (*p == ']') { |
484 | 0 | if (njs_slow_path(!empty)) { |
485 | 0 | njs_json_parse_exception(ctx, "Trailing comma", p - 1); |
486 | 0 | return NULL; |
487 | 0 | } |
488 | | |
489 | 0 | break; |
490 | 0 | } |
491 | | |
492 | 769 | p = njs_json_parse_value(ctx, &element, p); |
493 | 769 | if (njs_slow_path(p == NULL)) { |
494 | 0 | return NULL; |
495 | 0 | } |
496 | | |
497 | 769 | ret = njs_array_add(ctx->vm, array, &element); |
498 | 769 | if (njs_slow_path(ret != NJS_OK)) { |
499 | 0 | return NULL; |
500 | 0 | } |
501 | | |
502 | 769 | empty = 0; |
503 | | |
504 | 769 | p = njs_json_skip_space(p, ctx->end); |
505 | 769 | if (njs_slow_path(p == ctx->end)) { |
506 | 0 | goto error_end; |
507 | 0 | } |
508 | | |
509 | 769 | if (*p != ',') { |
510 | 769 | if (njs_fast_path(*p == ']')) { |
511 | 769 | break; |
512 | 769 | } |
513 | | |
514 | 0 | goto error_token; |
515 | 769 | } |
516 | 769 | } |
517 | | |
518 | 769 | njs_set_array(value, array); |
519 | | |
520 | 769 | ctx->depth++; |
521 | | |
522 | 769 | return p + 1; |
523 | | |
524 | 0 | error_token: |
525 | |
|
526 | 0 | njs_json_parse_exception(ctx, "Unexpected token", p); |
527 | |
|
528 | 0 | return NULL; |
529 | | |
530 | 0 | error_end: |
531 | |
|
532 | 0 | njs_json_parse_exception(ctx, "Unexpected end of input", p); |
533 | |
|
534 | 0 | return NULL; |
535 | 769 | } |
536 | | |
537 | | |
538 | | static const u_char * |
539 | | njs_json_parse_string(njs_json_parse_ctx_t *ctx, njs_value_t *value, |
540 | | const u_char *p) |
541 | 3.27k | { |
542 | 3.27k | u_char ch, *s, *dst; |
543 | 3.27k | size_t size, surplus; |
544 | 3.27k | uint32_t utf, utf_low; |
545 | 3.27k | njs_int_t ret; |
546 | 3.27k | const u_char *start, *last; |
547 | | |
548 | 3.27k | enum { |
549 | 3.27k | sw_usual = 0, |
550 | 3.27k | sw_escape, |
551 | 3.27k | sw_encoded1, |
552 | 3.27k | sw_encoded2, |
553 | 3.27k | sw_encoded3, |
554 | 3.27k | sw_encoded4, |
555 | 3.27k | } state; |
556 | | |
557 | 3.27k | start = p + 1; |
558 | | |
559 | 3.27k | dst = NULL; |
560 | 3.27k | state = 0; |
561 | 3.27k | surplus = 0; |
562 | | |
563 | 151k | for (p = start; p < ctx->end; p++) { |
564 | 151k | ch = *p; |
565 | | |
566 | 151k | switch (state) { |
567 | | |
568 | 118k | case sw_usual: |
569 | | |
570 | 118k | if (ch == '"') { |
571 | 3.27k | break; |
572 | 3.27k | } |
573 | | |
574 | 115k | if (ch == '\\') { |
575 | 12.4k | state = sw_escape; |
576 | 12.4k | continue; |
577 | 12.4k | } |
578 | | |
579 | 103k | if (njs_fast_path(ch >= ' ')) { |
580 | 103k | continue; |
581 | 103k | } |
582 | | |
583 | 0 | njs_json_parse_exception(ctx, "Forbidden source char", p); |
584 | |
|
585 | 0 | return NULL; |
586 | | |
587 | 12.4k | case sw_escape: |
588 | | |
589 | 12.4k | switch (ch) { |
590 | 1.24k | case '"': |
591 | 6.20k | case '\\': |
592 | 6.84k | case '/': |
593 | 6.84k | case 'n': |
594 | 6.84k | case 'r': |
595 | 6.84k | case 't': |
596 | 7.44k | case 'b': |
597 | 7.44k | case 'f': |
598 | 7.44k | surplus++; |
599 | 7.44k | state = sw_usual; |
600 | 7.44k | continue; |
601 | | |
602 | 4.96k | case 'u': |
603 | | /* |
604 | | * Basic unicode 6 bytes "\uXXXX" in JSON |
605 | | * and up to 3 bytes in UTF-8. |
606 | | * |
607 | | * Surrogate pair: 12 bytes "\uXXXX\uXXXX" in JSON |
608 | | * and 3 or 4 bytes in UTF-8. |
609 | | */ |
610 | 4.96k | surplus += 3; |
611 | 4.96k | state = sw_encoded1; |
612 | 4.96k | continue; |
613 | 12.4k | } |
614 | | |
615 | 0 | njs_json_parse_exception(ctx, "Unknown escape char", p); |
616 | |
|
617 | 0 | return NULL; |
618 | | |
619 | 4.96k | case sw_encoded1: |
620 | 9.92k | case sw_encoded2: |
621 | 14.8k | case sw_encoded3: |
622 | 19.8k | case sw_encoded4: |
623 | | |
624 | 19.8k | if (njs_fast_path((ch >= '0' && ch <= '9') |
625 | 19.8k | || (ch >= 'A' && ch <= 'F') |
626 | 19.8k | || (ch >= 'a' && ch <= 'f'))) |
627 | 19.8k | { |
628 | 19.8k | state = (state == sw_encoded4) ? sw_usual : state + 1; |
629 | 19.8k | continue; |
630 | 19.8k | } |
631 | | |
632 | 0 | njs_json_parse_exception(ctx, "Invalid Unicode escape sequence", p); |
633 | |
|
634 | 0 | return NULL; |
635 | 151k | } |
636 | | |
637 | 3.27k | break; |
638 | 151k | } |
639 | | |
640 | 3.27k | if (njs_slow_path(p == ctx->end)) { |
641 | 0 | njs_json_parse_exception(ctx, "Unexpected end of input", p); |
642 | 0 | return NULL; |
643 | 0 | } |
644 | | |
645 | | /* Points to the ending quote mark. */ |
646 | 3.27k | last = p; |
647 | | |
648 | 3.27k | size = last - start - surplus; |
649 | | |
650 | 3.27k | if (surplus != 0) { |
651 | 1.24k | p = start; |
652 | | |
653 | 1.24k | dst = njs_mp_alloc(ctx->pool, size); |
654 | 1.24k | if (njs_slow_path(dst == NULL)) { |
655 | 0 | njs_memory_error(ctx->vm);; |
656 | 0 | return NULL; |
657 | 0 | } |
658 | | |
659 | 1.24k | s = dst; |
660 | | |
661 | 69.6k | do { |
662 | 69.6k | ch = *p++; |
663 | | |
664 | 69.6k | if (ch != '\\') { |
665 | 58.4k | *s++ = ch; |
666 | 58.4k | continue; |
667 | 58.4k | } |
668 | | |
669 | 11.1k | ch = *p++; |
670 | | |
671 | 11.1k | switch (ch) { |
672 | 1.24k | case '"': |
673 | 6.20k | case '\\': |
674 | 6.84k | case '/': |
675 | 6.84k | *s++ = ch; |
676 | 6.84k | continue; |
677 | | |
678 | 0 | case 'n': |
679 | 0 | *s++ = '\n'; |
680 | 0 | continue; |
681 | | |
682 | 0 | case 'r': |
683 | 0 | *s++ = '\r'; |
684 | 0 | continue; |
685 | | |
686 | 0 | case 't': |
687 | 0 | *s++ = '\t'; |
688 | 0 | continue; |
689 | | |
690 | 598 | case 'b': |
691 | 598 | *s++ = '\b'; |
692 | 598 | continue; |
693 | | |
694 | 0 | case 'f': |
695 | 0 | *s++ = '\f'; |
696 | 0 | continue; |
697 | 11.1k | } |
698 | | |
699 | | /* "\uXXXX": Unicode escape sequence. */ |
700 | | |
701 | 3.72k | utf = njs_json_unicode(p); |
702 | 3.72k | p += 4; |
703 | | |
704 | 3.72k | if (njs_surrogate_any(utf)) { |
705 | | |
706 | 3.72k | if (utf > 0xdbff || p[0] != '\\' || p[1] != 'u') { |
707 | 2.48k | s = njs_utf8_encode(s, NJS_UNICODE_REPLACEMENT); |
708 | 2.48k | continue; |
709 | 2.48k | } |
710 | | |
711 | 1.24k | p += 2; |
712 | | |
713 | 1.24k | utf_low = njs_json_unicode(p); |
714 | 1.24k | p += 4; |
715 | | |
716 | 1.24k | if (njs_fast_path(njs_surrogate_trailing(utf_low))) { |
717 | 0 | utf = njs_surrogate_pair(utf, utf_low); |
718 | |
|
719 | 1.24k | } else if (njs_surrogate_leading(utf_low)) { |
720 | 598 | utf = NJS_UNICODE_REPLACEMENT; |
721 | 598 | s = njs_utf8_encode(s, NJS_UNICODE_REPLACEMENT); |
722 | | |
723 | 643 | } else { |
724 | 643 | utf = utf_low; |
725 | 643 | s = njs_utf8_encode(s, NJS_UNICODE_REPLACEMENT); |
726 | 643 | } |
727 | 1.24k | } |
728 | | |
729 | 1.24k | s = njs_utf8_encode(s, utf); |
730 | | |
731 | 69.6k | } while (p != last); |
732 | | |
733 | 1.24k | size = s - dst; |
734 | 1.24k | start = dst; |
735 | 1.24k | } |
736 | | |
737 | 3.27k | ret = njs_atom_string_create(ctx->vm, value, (u_char *) start, size); |
738 | 3.27k | if (njs_slow_path(ret != NJS_OK)) { |
739 | 0 | return NULL; |
740 | 0 | } |
741 | | |
742 | 3.27k | if (dst != NULL) { |
743 | 1.24k | njs_mp_free(ctx->pool, dst); |
744 | 1.24k | } |
745 | | |
746 | 3.27k | return last + 1; |
747 | 3.27k | } |
748 | | |
749 | | |
750 | | static const u_char * |
751 | | njs_json_parse_number(njs_json_parse_ctx_t *ctx, njs_value_t *value, |
752 | | const u_char *p) |
753 | 2.03k | { |
754 | 2.03k | double num; |
755 | 2.03k | njs_int_t sign; |
756 | 2.03k | const u_char *start; |
757 | | |
758 | 2.03k | sign = 1; |
759 | | |
760 | 2.03k | if (*p == '-') { |
761 | 220 | if (p + 1 == ctx->end) { |
762 | 0 | goto error; |
763 | 0 | } |
764 | | |
765 | 220 | p++; |
766 | 220 | sign = -1; |
767 | 220 | } |
768 | | |
769 | 2.03k | start = p; |
770 | 2.03k | num = njs_number_dec_parse(&p, ctx->end, 0); |
771 | 2.03k | if (p != start) { |
772 | 2.03k | njs_set_number(value, sign * num); |
773 | 2.03k | return p; |
774 | 2.03k | } |
775 | | |
776 | 0 | error: |
777 | |
|
778 | 0 | njs_json_parse_exception(ctx, "Unexpected number", p); |
779 | |
|
780 | 0 | return NULL; |
781 | 2.03k | } |
782 | | |
783 | | |
784 | | njs_inline uint32_t |
785 | | njs_json_unicode(const u_char *p) |
786 | 4.96k | { |
787 | 4.96k | u_char c; |
788 | 4.96k | uint32_t utf; |
789 | 4.96k | njs_uint_t i; |
790 | | |
791 | 4.96k | utf = 0; |
792 | | |
793 | 24.8k | for (i = 0; i < 4; i++) { |
794 | 19.8k | utf <<= 4; |
795 | 19.8k | c = p[i] | 0x20; |
796 | 19.8k | c -= '0'; |
797 | 19.8k | if (c > 9) { |
798 | 18.0k | c += '0' - 'a' + 10; |
799 | 18.0k | } |
800 | | |
801 | 19.8k | utf |= c; |
802 | 19.8k | } |
803 | | |
804 | 4.96k | return utf; |
805 | 4.96k | } |
806 | | |
807 | | |
808 | | static const u_char * |
809 | | njs_json_skip_space(const u_char *start, const u_char *end) |
810 | 14.4k | { |
811 | 14.4k | const u_char *p; |
812 | | |
813 | 18.0k | for (p = start; njs_fast_path(p != end); p++) { |
814 | | |
815 | 18.0k | switch (*p) { |
816 | 1.53k | case ' ': |
817 | 1.53k | case '\t': |
818 | 2.83k | case '\r': |
819 | 5.61k | case '\n': |
820 | 5.61k | continue; |
821 | 18.0k | } |
822 | | |
823 | 12.4k | break; |
824 | 18.0k | } |
825 | | |
826 | 14.4k | return p; |
827 | 14.4k | } |
828 | | |
829 | | |
830 | | static njs_int_t |
831 | | njs_json_internalize_property(njs_vm_t *vm, njs_function_t *reviver, |
832 | | njs_value_t *holder, uint32_t atom_id, njs_int_t depth, |
833 | | njs_value_t *retval) |
834 | 864 | { |
835 | 864 | int64_t k, length; |
836 | 864 | njs_int_t ret; |
837 | 864 | njs_value_t val, new_elem; |
838 | 864 | njs_value_t arguments[3]; |
839 | 864 | njs_array_t *keys; |
840 | | |
841 | 864 | if (njs_slow_path(depth++ >= NJS_JSON_MAX_DEPTH)) { |
842 | 0 | njs_type_error(vm, "Nested too deep or a cyclic structure"); |
843 | 0 | return NJS_ERROR; |
844 | 0 | } |
845 | | |
846 | 864 | ret = njs_value_property(vm, holder, atom_id, &val); |
847 | 864 | if (njs_slow_path(ret == NJS_ERROR)) { |
848 | 0 | return NJS_ERROR; |
849 | 0 | } |
850 | | |
851 | 864 | keys = NULL; |
852 | | |
853 | 864 | if (njs_is_object(&val)) { |
854 | 220 | if (!njs_is_array(&val)) { |
855 | 220 | keys = njs_array_keys(vm, &val, 0); |
856 | 220 | if (njs_slow_path(keys == NULL)) { |
857 | 0 | return NJS_ERROR; |
858 | 0 | } |
859 | | |
860 | 864 | for (k = 0; k < keys->length; k++) { |
861 | 644 | ret = njs_json_internalize_property(vm, reviver, &val, |
862 | 644 | keys->start[k].atom_id, |
863 | 644 | depth, &new_elem); |
864 | | |
865 | 644 | if (njs_slow_path(ret != NJS_OK)) { |
866 | 0 | goto done; |
867 | 0 | } |
868 | | |
869 | 644 | if (njs_is_undefined(&new_elem)) { |
870 | 644 | ret = njs_value_property_delete(vm, &val, |
871 | 644 | keys->start[k].atom_id, |
872 | 644 | NULL, 0); |
873 | | |
874 | 644 | } else { |
875 | 0 | ret = njs_value_property_set(vm, &val, |
876 | 0 | keys->start[k].atom_id, |
877 | 0 | &new_elem); |
878 | 0 | } |
879 | | |
880 | 644 | if (njs_slow_path(ret == NJS_ERROR)) { |
881 | 0 | goto done; |
882 | 0 | } |
883 | 644 | } |
884 | | |
885 | 220 | } else { |
886 | |
|
887 | 0 | ret = njs_object_length(vm, &val, &length); |
888 | 0 | if (njs_slow_path(ret == NJS_ERROR)) { |
889 | 0 | return NJS_ERROR; |
890 | 0 | } |
891 | | |
892 | 0 | for (k = 0; k < length; k++) { |
893 | 0 | ret = njs_json_internalize_property(vm, reviver, &val, |
894 | 0 | njs_number_atom(k), |
895 | 0 | depth, &new_elem); |
896 | |
|
897 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
898 | 0 | return NJS_ERROR; |
899 | 0 | } |
900 | | |
901 | 0 | if (njs_is_undefined(&new_elem)) { |
902 | 0 | ret = njs_value_property_delete(vm, &val, |
903 | 0 | njs_number_atom(k), NULL, |
904 | 0 | 0); |
905 | |
|
906 | 0 | } else { |
907 | 0 | ret = njs_value_property_set(vm, &val, njs_number_atom(k), |
908 | 0 | &new_elem); |
909 | 0 | } |
910 | |
|
911 | 0 | if (njs_slow_path(ret == NJS_ERROR)) { |
912 | 0 | return NJS_ERROR; |
913 | 0 | } |
914 | 0 | } |
915 | 0 | } |
916 | 220 | } |
917 | | |
918 | 864 | njs_value_assign(&arguments[0], holder); |
919 | 864 | njs_atom_to_value(vm, &arguments[1], atom_id); |
920 | 864 | njs_value_assign(&arguments[2], &val); |
921 | | |
922 | 864 | ret = njs_function_apply(vm, reviver, arguments, 3, retval); |
923 | | |
924 | 864 | done: |
925 | | |
926 | 864 | if (keys != NULL) { |
927 | 220 | njs_array_destroy(vm, keys); |
928 | 220 | } |
929 | | |
930 | 864 | return ret; |
931 | 864 | } |
932 | | |
933 | | |
934 | | static void |
935 | | njs_json_parse_exception(njs_json_parse_ctx_t *ctx, const char *msg, |
936 | | const u_char *pos) |
937 | 1.29k | { |
938 | 1.29k | ssize_t length; |
939 | | |
940 | 1.29k | length = njs_utf8_length(ctx->start, pos - ctx->start); |
941 | 1.29k | if (njs_slow_path(length < 0)) { |
942 | 0 | length = 0; |
943 | 0 | } |
944 | | |
945 | 1.29k | njs_syntax_error(ctx->vm, "%s at position %z", msg, length); |
946 | 1.29k | } |
947 | | |
948 | | |
949 | | static njs_json_state_t * |
950 | | njs_json_push_stringify_state(njs_json_stringify_t *stringify, |
951 | | njs_value_t *value) |
952 | 7.46k | { |
953 | 7.46k | njs_int_t ret; |
954 | 7.46k | njs_json_state_t *state; |
955 | | |
956 | 7.46k | if (njs_slow_path(stringify->depth >= NJS_JSON_MAX_DEPTH)) { |
957 | 0 | njs_type_error(stringify->vm, "Nested too deep or a cyclic structure"); |
958 | 0 | return NULL; |
959 | 0 | } |
960 | | |
961 | 7.46k | state = &stringify->states[stringify->depth++]; |
962 | 7.46k | state->value = *value; |
963 | 7.46k | state->array = njs_is_array(value); |
964 | 7.46k | state->fast_array = njs_is_fast_array(value); |
965 | 7.46k | state->index = 0; |
966 | 7.46k | state->written = 0; |
967 | 7.46k | state->keys = NULL; |
968 | 7.46k | state->key = NULL; |
969 | | |
970 | 7.46k | if (state->fast_array) { |
971 | 7.26k | state->length = njs_array_len(value); |
972 | 7.26k | } |
973 | | |
974 | 7.46k | if (njs_is_array(&stringify->replacer)) { |
975 | 0 | state->keys = njs_array(&stringify->replacer); |
976 | |
|
977 | 7.46k | } else if (state->array) { |
978 | 7.26k | state->keys = njs_array_keys(stringify->vm, value, 1); |
979 | 7.26k | if (njs_slow_path(state->keys == NULL)) { |
980 | 0 | return NULL; |
981 | 0 | } |
982 | | |
983 | 7.26k | ret = njs_object_length(stringify->vm, &state->value, &state->length); |
984 | 7.26k | if (njs_slow_path(ret == NJS_ERROR)) { |
985 | 0 | return NULL; |
986 | 0 | } |
987 | | |
988 | 7.26k | } else { |
989 | 202 | state->keys = njs_value_own_enumerate(stringify->vm, value, |
990 | 202 | NJS_ENUM_KEYS |
991 | 202 | | stringify->keys_type |
992 | 202 | | NJS_ENUM_ENUMERABLE_ONLY); |
993 | | |
994 | 202 | if (njs_slow_path(state->keys == NULL)) { |
995 | 0 | return NULL; |
996 | 0 | } |
997 | 202 | } |
998 | | |
999 | 7.46k | return state; |
1000 | 7.46k | } |
1001 | | |
1002 | | |
1003 | | njs_inline njs_json_state_t * |
1004 | | njs_json_pop_stringify_state(njs_json_stringify_t *stringify) |
1005 | 7.46k | { |
1006 | 7.46k | njs_json_state_t *state; |
1007 | | |
1008 | 7.46k | state = &stringify->states[stringify->depth - 1]; |
1009 | 7.46k | if (!njs_is_array(&stringify->replacer) && state->keys != NULL) { |
1010 | 7.46k | njs_array_destroy(stringify->vm, state->keys); |
1011 | 7.46k | state->keys = NULL; |
1012 | 7.46k | } |
1013 | | |
1014 | 7.46k | if (stringify->depth > 1) { |
1015 | 0 | stringify->depth--; |
1016 | 0 | state = &stringify->states[stringify->depth - 1]; |
1017 | 0 | state->written = 1; |
1018 | 0 | return state; |
1019 | 0 | } |
1020 | | |
1021 | 7.46k | return NULL; |
1022 | 7.46k | } |
1023 | | |
1024 | | |
1025 | | njs_inline njs_bool_t |
1026 | | njs_json_is_object(const njs_value_t *value) |
1027 | 0 | { |
1028 | 0 | if (!njs_is_object(value)) { |
1029 | 0 | return 0; |
1030 | 0 | } |
1031 | | |
1032 | 0 | if (njs_is_function(value)) { |
1033 | 0 | return 0; |
1034 | 0 | } |
1035 | | |
1036 | 0 | if (njs_is_object_value(value)) { |
1037 | 0 | switch (njs_object_value(value)->type) { |
1038 | 0 | case NJS_BOOLEAN: |
1039 | 0 | case NJS_NUMBER: |
1040 | 0 | case NJS_STRING: |
1041 | 0 | return 0; |
1042 | | |
1043 | 0 | default: |
1044 | 0 | break; |
1045 | 0 | } |
1046 | 0 | } |
1047 | | |
1048 | 0 | return 1; |
1049 | 0 | } |
1050 | | |
1051 | | |
1052 | | njs_inline void |
1053 | | njs_json_stringify_indent(njs_json_stringify_t *stringify, njs_chb_t *chain, |
1054 | | njs_int_t times) |
1055 | 27.0k | { |
1056 | 27.0k | njs_int_t i; |
1057 | | |
1058 | 27.0k | if (stringify->space.length != 0) { |
1059 | 0 | times += stringify->depth; |
1060 | 0 | njs_chb_append(chain,"\n", 1); |
1061 | 0 | for (i = 0; i < (times - 1); i++) { |
1062 | 0 | njs_chb_append_str(chain, &stringify->space); |
1063 | 0 | } |
1064 | 0 | } |
1065 | 27.0k | } |
1066 | | |
1067 | | |
1068 | | njs_inline njs_bool_t |
1069 | | njs_json_stringify_done(njs_json_state_t *state, njs_bool_t array) |
1070 | 31.5k | { |
1071 | 31.5k | return array ? state->index >= state->length |
1072 | 31.5k | : state->index >= state->keys->length; |
1073 | 31.5k | } |
1074 | | |
1075 | | |
1076 | | static njs_int_t |
1077 | | njs_json_stringify_iterator(njs_json_stringify_t *stringify, |
1078 | | njs_value_t *object, njs_value_t *retval) |
1079 | 0 | { |
1080 | 0 | int64_t size; |
1081 | 0 | njs_int_t ret; |
1082 | 0 | njs_chb_t chain; |
1083 | 0 | njs_value_t *key, *value, index, wrapper; |
1084 | 0 | njs_object_t *obj; |
1085 | 0 | njs_json_state_t *state; |
1086 | |
|
1087 | 0 | obj = njs_json_wrap_value(stringify->vm, &wrapper, object); |
1088 | 0 | if (njs_slow_path(obj == NULL)) { |
1089 | 0 | goto memory_error; |
1090 | 0 | } |
1091 | | |
1092 | 0 | state = njs_json_push_stringify_state(stringify, &wrapper); |
1093 | 0 | if (njs_slow_path(state == NULL)) { |
1094 | 0 | goto memory_error; |
1095 | 0 | } |
1096 | | |
1097 | 0 | NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(stringify->vm)); |
1098 | |
|
1099 | 0 | for ( ;; ) { |
1100 | 0 | if (state->index == 0) { |
1101 | 0 | njs_chb_append(&chain, state->array ? "[" : "{", 1); |
1102 | 0 | njs_json_stringify_indent(stringify, &chain, 0); |
1103 | 0 | } |
1104 | |
|
1105 | 0 | if (njs_json_stringify_done(state, state->array)) { |
1106 | 0 | njs_json_stringify_indent(stringify, &chain, -1); |
1107 | 0 | njs_chb_append(&chain, state->array ? "]" : "}", 1); |
1108 | |
|
1109 | 0 | state = njs_json_pop_stringify_state(stringify); |
1110 | 0 | if (state == NULL) { |
1111 | 0 | goto done; |
1112 | 0 | } |
1113 | | |
1114 | 0 | continue; |
1115 | 0 | } |
1116 | | |
1117 | 0 | value = &stringify->retval; |
1118 | |
|
1119 | 0 | if (state->array) { |
1120 | 0 | njs_set_number(&index, state->index); |
1121 | 0 | key = &index; |
1122 | |
|
1123 | 0 | } else { |
1124 | 0 | key = &state->keys->start[state->index]; |
1125 | 0 | } |
1126 | |
|
1127 | 0 | ret = njs_value_property_val(stringify->vm, &state->value, key, value); |
1128 | 0 | if (njs_slow_path(ret == NJS_ERROR)) { |
1129 | 0 | return ret; |
1130 | 0 | } |
1131 | | |
1132 | 0 | if (state->array && ret == NJS_DECLINED) { |
1133 | 0 | njs_set_null(value); |
1134 | 0 | } |
1135 | |
|
1136 | 0 | ret = njs_json_stringify_to_json(stringify, state, key, value); |
1137 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1138 | 0 | return ret; |
1139 | 0 | } |
1140 | | |
1141 | 0 | ret = njs_json_stringify_replacer(stringify, state, key, value); |
1142 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1143 | 0 | return ret; |
1144 | 0 | } |
1145 | | |
1146 | 0 | state->index++; |
1147 | |
|
1148 | 0 | if (!state->array |
1149 | 0 | && (njs_is_undefined(value) |
1150 | 0 | || njs_is_symbol(value) |
1151 | 0 | || njs_is_function(value) |
1152 | 0 | || !njs_is_valid(value))) |
1153 | 0 | { |
1154 | 0 | continue; |
1155 | 0 | } |
1156 | | |
1157 | 0 | if (state->written) { |
1158 | 0 | njs_chb_append_literal(&chain,","); |
1159 | 0 | njs_json_stringify_indent(stringify, &chain, 0); |
1160 | 0 | } |
1161 | |
|
1162 | 0 | state->written = 1; |
1163 | |
|
1164 | 0 | if (!state->array) { |
1165 | 0 | njs_json_append_string(stringify->vm, &chain, key, '\"'); |
1166 | 0 | njs_chb_append_literal(&chain,":"); |
1167 | 0 | if (stringify->space.length != 0) { |
1168 | 0 | njs_chb_append_literal(&chain," "); |
1169 | 0 | } |
1170 | 0 | } |
1171 | |
|
1172 | 0 | if (njs_json_is_object(value)) { |
1173 | 0 | state = njs_json_push_stringify_state(stringify, value); |
1174 | 0 | if (njs_slow_path(state == NULL)) { |
1175 | 0 | return NJS_ERROR; |
1176 | 0 | } |
1177 | | |
1178 | 0 | continue; |
1179 | 0 | } |
1180 | | |
1181 | 0 | ret = njs_json_append_value(stringify->vm, &chain, value); |
1182 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1183 | 0 | return ret; |
1184 | 0 | } |
1185 | 0 | } |
1186 | | |
1187 | 0 | done: |
1188 | | |
1189 | | /* |
1190 | | * The value to stringify is wrapped as '{"": value}'. |
1191 | | * Stripping the wrapper's data. |
1192 | | */ |
1193 | |
|
1194 | 0 | njs_chb_drain(&chain, njs_length("{\"\":")); |
1195 | 0 | njs_chb_drop(&chain, njs_length("}")); |
1196 | |
|
1197 | 0 | if (stringify->space.length != 0) { |
1198 | 0 | njs_chb_drain(&chain, njs_length("\n ")); |
1199 | 0 | njs_chb_drop(&chain, njs_length("\n")); |
1200 | 0 | } |
1201 | |
|
1202 | 0 | size = njs_chb_size(&chain); |
1203 | 0 | if (njs_slow_path(size < 0)) { |
1204 | 0 | njs_chb_destroy(&chain); |
1205 | 0 | goto memory_error; |
1206 | 0 | } |
1207 | | |
1208 | 0 | if (size == 0) { |
1209 | 0 | njs_set_undefined(retval); |
1210 | 0 | goto release; |
1211 | 0 | } |
1212 | | |
1213 | 0 | ret = njs_string_create_chb(stringify->vm, retval, &chain); |
1214 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1215 | 0 | njs_chb_destroy(&chain); |
1216 | 0 | goto memory_error; |
1217 | 0 | } |
1218 | | |
1219 | 0 | release: |
1220 | |
|
1221 | 0 | njs_chb_destroy(&chain); |
1222 | |
|
1223 | 0 | return NJS_OK; |
1224 | | |
1225 | 0 | memory_error: |
1226 | |
|
1227 | 0 | njs_memory_error(stringify->vm); |
1228 | |
|
1229 | 0 | return NJS_ERROR; |
1230 | 0 | } |
1231 | | |
1232 | | |
1233 | | static njs_function_t * |
1234 | | njs_object_to_json_function(njs_vm_t *vm, njs_value_t *value) |
1235 | 0 | { |
1236 | 0 | njs_int_t ret; |
1237 | 0 | njs_value_t retval; |
1238 | 0 | njs_flathsh_query_t fhq; |
1239 | |
|
1240 | 0 | if (njs_is_object(value)) { |
1241 | 0 | fhq.proto = &njs_object_hash_proto; |
1242 | 0 | fhq.key_hash = NJS_ATOM_STRING_toJSON; |
1243 | |
|
1244 | 0 | ret = njs_object_property(vm, njs_object(value), &fhq, &retval); |
1245 | |
|
1246 | 0 | if (njs_slow_path(ret == NJS_ERROR)) { |
1247 | 0 | return NULL; |
1248 | 0 | } |
1249 | | |
1250 | 0 | return njs_is_function(&retval) ? njs_function(&retval) : NULL; |
1251 | 0 | } |
1252 | | |
1253 | 0 | return NULL; |
1254 | 0 | } |
1255 | | |
1256 | | |
1257 | | static njs_int_t |
1258 | | njs_json_stringify_to_json(njs_json_stringify_t* stringify, |
1259 | | njs_json_state_t *state, njs_value_t *key, njs_value_t *value) |
1260 | 0 | { |
1261 | 0 | njs_int_t ret; |
1262 | 0 | njs_value_t arguments[2]; |
1263 | 0 | njs_function_t *to_json; |
1264 | |
|
1265 | 0 | if (!njs_is_object(value)) { |
1266 | 0 | return NJS_OK; |
1267 | 0 | } |
1268 | | |
1269 | 0 | to_json = njs_object_to_json_function(stringify->vm, value); |
1270 | 0 | if (to_json == NULL) { |
1271 | 0 | return NJS_OK; |
1272 | 0 | } |
1273 | | |
1274 | 0 | arguments[0] = *value; |
1275 | |
|
1276 | 0 | if (!state->array) { |
1277 | 0 | arguments[1] = *key; |
1278 | |
|
1279 | 0 | } else { |
1280 | 0 | ret = njs_uint32_to_string(stringify->vm, &arguments[1], state->index); |
1281 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1282 | 0 | return NJS_ERROR; |
1283 | 0 | } |
1284 | 0 | } |
1285 | | |
1286 | 0 | return njs_function_apply(stringify->vm, to_json, arguments, 2, |
1287 | 0 | &stringify->retval); |
1288 | 0 | } |
1289 | | |
1290 | | |
1291 | | static njs_int_t |
1292 | | njs_json_stringify_replacer(njs_json_stringify_t* stringify, |
1293 | | njs_json_state_t *state, njs_value_t *key, njs_value_t *value) |
1294 | 0 | { |
1295 | 0 | njs_int_t ret; |
1296 | 0 | njs_value_t arguments[3]; |
1297 | |
|
1298 | 0 | if (!njs_is_function(&stringify->replacer)) { |
1299 | 0 | return NJS_OK; |
1300 | 0 | } |
1301 | | |
1302 | 0 | arguments[0] = state->value; |
1303 | 0 | arguments[2] = *value; |
1304 | |
|
1305 | 0 | if (!state->array) { |
1306 | 0 | arguments[1] = *key; |
1307 | |
|
1308 | 0 | } else { |
1309 | 0 | ret = njs_uint32_to_string(stringify->vm, &arguments[1], state->index); |
1310 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1311 | 0 | return NJS_ERROR; |
1312 | 0 | } |
1313 | 0 | } |
1314 | | |
1315 | 0 | return njs_function_apply(stringify->vm, njs_function(&stringify->replacer), |
1316 | 0 | arguments, 3, &stringify->retval); |
1317 | 0 | } |
1318 | | |
1319 | | |
1320 | | static njs_int_t |
1321 | | njs_json_stringify_array(njs_json_stringify_t *stringify) |
1322 | 0 | { |
1323 | 0 | njs_int_t ret; |
1324 | 0 | int64_t i, k, length; |
1325 | 0 | njs_value_t *value, *item; |
1326 | 0 | njs_array_t *properties; |
1327 | |
|
1328 | 0 | ret = njs_object_length(stringify->vm, &stringify->replacer, &length); |
1329 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1330 | 0 | return ret; |
1331 | 0 | } |
1332 | | |
1333 | 0 | properties = njs_array_alloc(stringify->vm, 1, 0, NJS_ARRAY_SPARE); |
1334 | 0 | if (njs_slow_path(properties == NULL)) { |
1335 | 0 | return NJS_ERROR; |
1336 | 0 | } |
1337 | | |
1338 | 0 | item = njs_array_push(stringify->vm, properties); |
1339 | 0 | njs_set_empty_string(stringify->vm, item); |
1340 | |
|
1341 | 0 | for (i = 0; i < length; i++) { |
1342 | 0 | ret = njs_value_property_i64(stringify->vm, &stringify->replacer, i, |
1343 | 0 | &stringify->retval); |
1344 | 0 | if (njs_slow_path(ret == NJS_ERROR)) { |
1345 | 0 | return ret; |
1346 | 0 | } |
1347 | | |
1348 | 0 | value = &stringify->retval; |
1349 | |
|
1350 | 0 | switch (value->type) { |
1351 | 0 | case NJS_STRING: |
1352 | 0 | break; |
1353 | | |
1354 | 0 | case NJS_NUMBER: |
1355 | 0 | ret = njs_number_to_string(stringify->vm, value, value); |
1356 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1357 | 0 | return NJS_ERROR; |
1358 | 0 | } |
1359 | | |
1360 | 0 | break; |
1361 | | |
1362 | 0 | case NJS_OBJECT_VALUE: |
1363 | 0 | switch (njs_object_value(value)->type) { |
1364 | 0 | case NJS_NUMBER: |
1365 | 0 | case NJS_STRING: |
1366 | 0 | ret = njs_value_to_string(stringify->vm, value, value); |
1367 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1368 | 0 | return NJS_ERROR; |
1369 | 0 | } |
1370 | | |
1371 | 0 | break; |
1372 | | |
1373 | 0 | default: |
1374 | 0 | continue; |
1375 | 0 | } |
1376 | | |
1377 | 0 | break; |
1378 | | |
1379 | 0 | default: |
1380 | 0 | continue; |
1381 | 0 | } |
1382 | | |
1383 | 0 | for (k = 0; k < properties->length; k++) { |
1384 | 0 | if (njs_values_strict_equal(stringify->vm, value, |
1385 | 0 | &properties->start[k]) == 1) |
1386 | 0 | { |
1387 | 0 | break; |
1388 | 0 | } |
1389 | 0 | } |
1390 | |
|
1391 | 0 | if (k == properties->length) { |
1392 | 0 | item = njs_array_push(stringify->vm, properties); |
1393 | 0 | if (njs_slow_path(item == NULL)) { |
1394 | 0 | return NJS_ERROR; |
1395 | 0 | } |
1396 | | |
1397 | 0 | njs_value_assign(item, value); |
1398 | 0 | } |
1399 | 0 | } |
1400 | | |
1401 | 0 | njs_set_array(&stringify->replacer, properties); |
1402 | |
|
1403 | 0 | return NJS_OK; |
1404 | 0 | } |
1405 | | |
1406 | | |
1407 | | static njs_int_t |
1408 | | njs_json_append_value(njs_vm_t *vm, njs_chb_t *chain, njs_value_t *value) |
1409 | 0 | { |
1410 | 0 | njs_int_t ret; |
1411 | |
|
1412 | 0 | if (njs_is_object_value(value)) { |
1413 | 0 | switch (njs_object_value(value)->type) { |
1414 | 0 | case NJS_NUMBER: |
1415 | 0 | ret = njs_value_to_numeric(vm, value, value); |
1416 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1417 | 0 | return ret; |
1418 | 0 | } |
1419 | | |
1420 | 0 | break; |
1421 | | |
1422 | 0 | case NJS_BOOLEAN: |
1423 | 0 | njs_value_assign(value, njs_object_value(value)); |
1424 | 0 | break; |
1425 | | |
1426 | 0 | case NJS_STRING: |
1427 | 0 | ret = njs_value_to_string(vm, value, value); |
1428 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1429 | 0 | return ret; |
1430 | 0 | } |
1431 | | |
1432 | 0 | break; |
1433 | | |
1434 | 0 | default: |
1435 | 0 | break; |
1436 | 0 | } |
1437 | 0 | } |
1438 | | |
1439 | 0 | switch (value->type) { |
1440 | 0 | case NJS_STRING: |
1441 | 0 | njs_json_append_string(vm, chain, value, '\"'); |
1442 | 0 | break; |
1443 | | |
1444 | 0 | case NJS_NUMBER: |
1445 | 0 | njs_json_append_number(chain, value); |
1446 | 0 | break; |
1447 | | |
1448 | 0 | case NJS_BOOLEAN: |
1449 | 0 | if (njs_is_true(value)) { |
1450 | 0 | njs_chb_append_literal(chain, "true"); |
1451 | |
|
1452 | 0 | } else { |
1453 | 0 | njs_chb_append_literal(chain, "false"); |
1454 | 0 | } |
1455 | |
|
1456 | 0 | break; |
1457 | | |
1458 | 0 | case NJS_UNDEFINED: |
1459 | 0 | case NJS_NULL: |
1460 | 0 | case NJS_SYMBOL: |
1461 | 0 | case NJS_INVALID: |
1462 | 0 | case NJS_FUNCTION: |
1463 | 0 | default: |
1464 | 0 | njs_chb_append_literal(chain, "null"); |
1465 | 0 | } |
1466 | | |
1467 | 0 | return NJS_OK; |
1468 | 0 | } |
1469 | | |
1470 | | |
1471 | | static void |
1472 | | njs_json_append_string(njs_vm_t *vm, njs_chb_t *chain, const njs_value_t *value, |
1473 | | char quote) |
1474 | 13.4k | { |
1475 | 13.4k | size_t size; |
1476 | 13.4k | u_char c, *dst, *dst_end; |
1477 | 13.4k | njs_bool_t utf8; |
1478 | 13.4k | const u_char *p, *end; |
1479 | 13.4k | njs_string_prop_t string; |
1480 | | |
1481 | 13.4k | static char hex2char[16] = { '0', '1', '2', '3', '4', '5', '6', '7', |
1482 | 13.4k | '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; |
1483 | | |
1484 | 13.4k | (void) njs_string_prop(vm, &string, value); |
1485 | | |
1486 | 13.4k | p = string.start; |
1487 | 13.4k | end = p + string.size; |
1488 | 13.4k | utf8 = (string.length != 0 && string.length != string.size); |
1489 | | |
1490 | 13.4k | size = njs_max(string.size + 2, 7); |
1491 | 13.4k | dst = njs_chb_reserve(chain, size); |
1492 | 13.4k | if (njs_slow_path(dst == NULL)) { |
1493 | 0 | return; |
1494 | 0 | } |
1495 | | |
1496 | 13.4k | dst_end = dst + size; |
1497 | | |
1498 | 13.4k | *dst++ = quote; |
1499 | 13.4k | njs_chb_written(chain, 1); |
1500 | | |
1501 | 273k | while (p < end) { |
1502 | 259k | if (njs_slow_path(dst_end <= dst + njs_length("\\uXXXX"))) { |
1503 | 40.8k | size = njs_max(end - p + 1, 6); |
1504 | 40.8k | dst = njs_chb_reserve(chain, size); |
1505 | 40.8k | if (njs_slow_path(dst == NULL)) { |
1506 | 0 | return; |
1507 | 0 | } |
1508 | | |
1509 | 40.8k | dst_end = dst + size; |
1510 | 40.8k | } |
1511 | | |
1512 | 259k | if (njs_slow_path(*p < ' ' |
1513 | 259k | || *p == '\\' |
1514 | 259k | || (*p == '\"' && quote == '\"'))) |
1515 | 68.1k | { |
1516 | 68.1k | c = (u_char) *p++; |
1517 | 68.1k | *dst++ = '\\'; |
1518 | 68.1k | njs_chb_written(chain, 2); |
1519 | | |
1520 | 68.1k | switch (c) { |
1521 | 1.67k | case '\\': |
1522 | 1.67k | *dst++ = '\\'; |
1523 | 1.67k | break; |
1524 | 0 | case '"': |
1525 | 0 | *dst++ = '\"'; |
1526 | 0 | break; |
1527 | 403 | case '\r': |
1528 | 403 | *dst++ = 'r'; |
1529 | 403 | break; |
1530 | 6.91k | case '\n': |
1531 | 6.91k | *dst++ = 'n'; |
1532 | 6.91k | break; |
1533 | 1.44k | case '\t': |
1534 | 1.44k | *dst++ = 't'; |
1535 | 1.44k | break; |
1536 | 4 | case '\b': |
1537 | 4 | *dst++ = 'b'; |
1538 | 4 | break; |
1539 | 494 | case '\f': |
1540 | 494 | *dst++ = 'f'; |
1541 | 494 | break; |
1542 | 57.2k | default: |
1543 | 57.2k | *dst++ = 'u'; |
1544 | 57.2k | *dst++ = '0'; |
1545 | 57.2k | *dst++ = '0'; |
1546 | 57.2k | *dst++ = hex2char[(c & 0xf0) >> 4]; |
1547 | 57.2k | *dst++ = hex2char[c & 0x0f]; |
1548 | 57.2k | njs_chb_written(chain, 4); |
1549 | 68.1k | } |
1550 | | |
1551 | 68.1k | continue; |
1552 | 68.1k | } |
1553 | | |
1554 | 191k | if (utf8) { |
1555 | | /* UTF-8 string. */ |
1556 | 178k | dst = njs_utf8_copy(dst, &p, end); |
1557 | | |
1558 | 178k | } else { |
1559 | | /* ASCII string. */ |
1560 | 12.9k | *dst++ = *p++; |
1561 | 12.9k | } |
1562 | | |
1563 | 191k | njs_chb_written(chain, dst - chain->last->pos); |
1564 | 191k | } |
1565 | | |
1566 | 13.4k | njs_chb_append(chain, "e, 1); |
1567 | 13.4k | } |
1568 | | |
1569 | | |
1570 | | static void |
1571 | | njs_json_append_number(njs_chb_t *chain, const njs_value_t *value) |
1572 | 0 | { |
1573 | 0 | u_char *p; |
1574 | 0 | size_t size; |
1575 | 0 | double num; |
1576 | |
|
1577 | 0 | num = njs_number(value); |
1578 | |
|
1579 | 0 | if (isnan(num) || isinf(num)) { |
1580 | 0 | njs_chb_append_literal(chain, "null"); |
1581 | |
|
1582 | 0 | } else { |
1583 | 0 | p = njs_chb_reserve(chain, 64); |
1584 | 0 | if (njs_slow_path(p == NULL)) { |
1585 | 0 | return; |
1586 | 0 | } |
1587 | | |
1588 | 0 | size = njs_dtoa(num, (char *) p); |
1589 | |
|
1590 | 0 | njs_chb_written(chain, size); |
1591 | 0 | } |
1592 | 0 | } |
1593 | | |
1594 | | |
1595 | | /* |
1596 | | * Wraps a value as '{"": <value>}'. |
1597 | | */ |
1598 | | static njs_object_t * |
1599 | | njs_json_wrap_value(njs_vm_t *vm, njs_value_t *wrapper, |
1600 | | const njs_value_t *value) |
1601 | 220 | { |
1602 | 220 | njs_int_t ret; |
1603 | 220 | njs_object_prop_t *prop; |
1604 | 220 | njs_flathsh_query_t fhq; |
1605 | | |
1606 | 220 | wrapper->data.u.object = njs_object_alloc(vm); |
1607 | 220 | if (njs_slow_path(njs_object(wrapper) == NULL)) { |
1608 | 0 | return NULL; |
1609 | 0 | } |
1610 | | |
1611 | 220 | wrapper->type = NJS_OBJECT; |
1612 | 220 | wrapper->data.truth = 1; |
1613 | | |
1614 | 220 | fhq.key_hash = NJS_ATOM_STRING_empty; |
1615 | 220 | fhq.replace = 0; |
1616 | 220 | fhq.pool = vm->mem_pool; |
1617 | 220 | fhq.proto = &njs_object_hash_proto; |
1618 | | |
1619 | 220 | ret = njs_flathsh_unique_insert(njs_object_hash(wrapper), &fhq); |
1620 | 220 | if (njs_slow_path(ret != NJS_OK)) { |
1621 | 0 | return NULL; |
1622 | 0 | } |
1623 | | |
1624 | 220 | prop = fhq.value; |
1625 | | |
1626 | 220 | prop->type = NJS_PROPERTY; |
1627 | 220 | prop->enumerable = 1; |
1628 | 220 | prop->configurable = 1; |
1629 | 220 | prop->writable = 1; |
1630 | 220 | prop->u.value = *value; |
1631 | | |
1632 | 220 | return wrapper->data.u.object; |
1633 | 220 | } |
1634 | | |
1635 | | |
1636 | | static const njs_object_prop_init_t njs_json_object_properties[] = |
1637 | | { |
1638 | | NJS_DECLARE_PROP_VALUE(SYMBOL_toStringTag, njs_ascii_strval("JSON"), |
1639 | | NJS_OBJECT_PROP_VALUE_C), |
1640 | | |
1641 | | NJS_DECLARE_PROP_NATIVE(STRING_parse, njs_json_parse, 2, 0), |
1642 | | |
1643 | | NJS_DECLARE_PROP_NATIVE(STRING_stringify, njs_json_stringify, 3, 0), |
1644 | | }; |
1645 | | |
1646 | | |
1647 | | const njs_object_init_t njs_json_object_init = { |
1648 | | njs_json_object_properties, |
1649 | | njs_nitems(njs_json_object_properties), |
1650 | | }; |
1651 | | |
1652 | | |
1653 | | static njs_int_t |
1654 | | njs_dump_terminal(njs_json_stringify_t *stringify, njs_chb_t *chain, |
1655 | | njs_value_t *value, njs_uint_t console) |
1656 | 17.0k | { |
1657 | 17.0k | njs_str_t str; |
1658 | 17.0k | njs_int_t ret; |
1659 | 17.0k | njs_value_t str_val, tag; |
1660 | 17.0k | njs_typed_array_t *array; |
1661 | 17.0k | njs_string_prop_t string; |
1662 | | |
1663 | 17.0k | njs_int_t (*to_string)(njs_vm_t *, njs_value_t *, const njs_value_t *); |
1664 | | |
1665 | 17.0k | switch (value->type) { |
1666 | 0 | case NJS_NULL: |
1667 | 0 | njs_chb_append_literal(chain, "null"); |
1668 | 0 | break; |
1669 | | |
1670 | 0 | case NJS_UNDEFINED: |
1671 | 0 | njs_chb_append_literal(chain, "undefined"); |
1672 | 0 | break; |
1673 | | |
1674 | 0 | case NJS_BOOLEAN: |
1675 | 0 | if (njs_is_true(value)) { |
1676 | 0 | njs_chb_append_literal(chain, "true"); |
1677 | |
|
1678 | 0 | } else { |
1679 | 0 | njs_chb_append_literal(chain, "false"); |
1680 | 0 | } |
1681 | |
|
1682 | 0 | break; |
1683 | | |
1684 | 13.4k | case NJS_STRING: |
1685 | 13.4k | njs_string_get(stringify->vm, value, &str); |
1686 | | |
1687 | 13.4k | if (!console || stringify->depth != 0) { |
1688 | 13.4k | njs_json_append_string(stringify->vm, chain, value, '\''); |
1689 | 13.4k | return NJS_OK; |
1690 | 13.4k | } |
1691 | | |
1692 | 0 | njs_chb_append_str(chain, &str); |
1693 | |
|
1694 | 0 | break; |
1695 | | |
1696 | 0 | case NJS_SYMBOL: |
1697 | 0 | ret = njs_symbol_descriptive_string(stringify->vm, &str_val, value); |
1698 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1699 | 0 | return NJS_ERROR; |
1700 | 0 | } |
1701 | | |
1702 | 0 | njs_string_get(stringify->vm, &str_val, &str); |
1703 | 0 | njs_chb_append_str(chain, &str); |
1704 | |
|
1705 | 0 | break; |
1706 | | |
1707 | 0 | case NJS_INVALID: |
1708 | | /* Fall through. */ |
1709 | 0 | break; |
1710 | | |
1711 | 0 | case NJS_OBJECT_VALUE: |
1712 | 0 | value = njs_object_value(value); |
1713 | |
|
1714 | 0 | switch (value->type) { |
1715 | 0 | case NJS_BOOLEAN: |
1716 | 0 | if (njs_is_true(value)) { |
1717 | 0 | njs_chb_append_literal(chain, "[Boolean: true]"); |
1718 | |
|
1719 | 0 | } else { |
1720 | 0 | njs_chb_append_literal(chain, "[Boolean: false]"); |
1721 | 0 | } |
1722 | |
|
1723 | 0 | break; |
1724 | | |
1725 | 0 | case NJS_NUMBER: |
1726 | 0 | if (njs_slow_path(njs_number(value) == 0.0 |
1727 | 0 | && signbit(njs_number(value)))) |
1728 | 0 | { |
1729 | |
|
1730 | 0 | njs_chb_append_literal(chain, "[Number: -0]"); |
1731 | 0 | break; |
1732 | 0 | } |
1733 | | |
1734 | 0 | ret = njs_number_to_string(stringify->vm, &str_val, value); |
1735 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1736 | 0 | return NJS_ERROR; |
1737 | 0 | } |
1738 | | |
1739 | 0 | njs_string_get(stringify->vm, &str_val, &str); |
1740 | 0 | njs_chb_sprintf(chain, 16 + str.length, "[Number: %V]", &str); |
1741 | 0 | break; |
1742 | | |
1743 | 0 | case NJS_SYMBOL: |
1744 | 0 | ret = njs_symbol_descriptive_string(stringify->vm, &str_val, value); |
1745 | 0 | if (njs_slow_path(ret != NJS_OK)) { |
1746 | 0 | return NJS_ERROR; |
1747 | 0 | } |
1748 | | |
1749 | 0 | njs_string_get(stringify->vm, &str_val, &str); |
1750 | 0 | njs_chb_sprintf(chain, 16 + str.length, "[Symbol: %V]", &str); |
1751 | |
|
1752 | 0 | break; |
1753 | | |
1754 | 0 | case NJS_STRING: |
1755 | 0 | default: |
1756 | 0 | njs_chb_append_literal(chain, "[String: "); |
1757 | 0 | njs_json_append_string(stringify->vm, chain, value, '\''); |
1758 | 0 | njs_chb_append_literal(chain, "]"); |
1759 | 0 | break; |
1760 | 0 | } |
1761 | | |
1762 | 0 | break; |
1763 | | |
1764 | 752 | case NJS_FUNCTION: |
1765 | 752 | if (njs_function(value)->native) { |
1766 | 0 | str = njs_str_value("native"); |
1767 | |
|
1768 | 752 | } else { |
1769 | 752 | str = njs_str_value(""); |
1770 | 752 | } |
1771 | | |
1772 | 752 | ret = njs_value_property(stringify->vm, value, NJS_ATOM_STRING_name, |
1773 | 752 | &tag); |
1774 | 752 | if (njs_slow_path(ret == NJS_ERROR)) { |
1775 | 0 | return ret; |
1776 | 0 | } |
1777 | | |
1778 | 752 | if (njs_is_string(&tag)) { |
1779 | 275 | njs_string_get(stringify->vm, &tag, &str); |
1780 | 275 | } |
1781 | | |
1782 | 752 | if (str.length != 0) { |
1783 | 275 | njs_chb_sprintf(chain, 32 + str.length, "[Function: %V]", &str); |
1784 | | |
1785 | 477 | } else { |
1786 | 477 | njs_chb_append_literal(chain, "[Function]"); |
1787 | 477 | } |
1788 | | |
1789 | 752 | break; |
1790 | | |
1791 | 0 | case NJS_TYPED_ARRAY: |
1792 | 0 | array = njs_typed_array(value); |
1793 | 0 | ret = njs_object_string_tag(stringify->vm, value, &tag); |
1794 | 0 | if (njs_slow_path(ret == NJS_ERROR)) { |
1795 | 0 | return ret; |
1796 | 0 | } |
1797 | | |
1798 | 0 | if (ret == NJS_OK) { |
1799 | 0 | (void) njs_string_prop(stringify->vm, &string, &tag); |
1800 | 0 | njs_chb_append(chain, string.start, string.size); |
1801 | 0 | njs_chb_append_literal(chain, " "); |
1802 | 0 | } |
1803 | |
|
1804 | 0 | njs_chb_append_literal(chain, "["); |
1805 | |
|
1806 | 0 | njs_typed_array_to_chain(stringify->vm, chain, array, NULL); |
1807 | |
|
1808 | 0 | njs_chb_append_literal(chain, "]"); |
1809 | |
|
1810 | 0 | break; |
1811 | | |
1812 | 20 | case NJS_NUMBER: |
1813 | 20 | if (njs_slow_path(njs_number(value) == 0.0 |
1814 | 20 | && signbit(njs_number(value)))) |
1815 | 0 | { |
1816 | |
|
1817 | 0 | njs_chb_append_literal(chain, "-0"); |
1818 | 0 | break; |
1819 | 0 | } |
1820 | | |
1821 | | /* Fall through. */ |
1822 | | |
1823 | 20 | case NJS_OBJECT: |
1824 | 20 | case NJS_REGEXP: |
1825 | 2.85k | case NJS_DATE: |
1826 | | |
1827 | 2.85k | switch (value->type) { |
1828 | 20 | case NJS_NUMBER: |
1829 | 20 | to_string = njs_number_to_string; |
1830 | 20 | break; |
1831 | | |
1832 | 0 | case NJS_REGEXP: |
1833 | 0 | to_string = njs_regexp_to_string; |
1834 | 0 | break; |
1835 | | |
1836 | 2.83k | case NJS_DATE: |
1837 | 2.83k | to_string = njs_date_to_string; |
1838 | 2.83k | break; |
1839 | | |
1840 | 0 | default: |
1841 | 0 | to_string = njs_error_to_string; |
1842 | 2.85k | } |
1843 | | |
1844 | 2.85k | ret = to_string(stringify->vm, &str_val, value); |
1845 | 2.85k | if (njs_slow_path(ret != NJS_OK)) { |
1846 | 0 | return NJS_ERROR; |
1847 | 0 | } |
1848 | | |
1849 | 2.85k | njs_string_get(stringify->vm, &str_val, &str); |
1850 | 2.85k | njs_chb_append_str(chain, &str); |
1851 | | |
1852 | 2.85k | break; |
1853 | | |
1854 | 0 | default: |
1855 | |
|
1856 | 0 | njs_chb_sprintf(chain, 64, "[Unknown value type:%uD]", value->type); |
1857 | 17.0k | } |
1858 | | |
1859 | 3.60k | return NJS_OK; |
1860 | 17.0k | } |
1861 | | |
1862 | | |
1863 | | njs_inline njs_bool_t |
1864 | | njs_dump_is_recursive(const njs_value_t *value) |
1865 | 24.4k | { |
1866 | 24.4k | return (value->type == NJS_OBJECT && !njs_object(value)->error_data) |
1867 | 24.2k | || (value->type == NJS_ARRAY) |
1868 | 17.0k | || (value->type >= NJS_OBJECT_SPECIAL_MAX |
1869 | 0 | && !njs_is_object_primitive(value)); |
1870 | 24.4k | } |
1871 | | |
1872 | | |
1873 | | njs_inline njs_int_t |
1874 | | njs_dump_visited(njs_vm_t *vm, njs_json_stringify_t *stringify, |
1875 | | const njs_value_t *value) |
1876 | 0 | { |
1877 | 0 | njs_int_t depth; |
1878 | |
|
1879 | 0 | depth = stringify->depth - 1; |
1880 | |
|
1881 | 0 | for (; depth >= 0; depth--) { |
1882 | 0 | if (njs_values_same(vm, &stringify->states[depth].value, value)) { |
1883 | 0 | return 1; |
1884 | 0 | } |
1885 | 0 | } |
1886 | | |
1887 | 0 | return 0; |
1888 | 0 | } |
1889 | | |
1890 | | |
1891 | | njs_inline void |
1892 | | njs_dump_empty(njs_json_stringify_t *stringify, njs_json_state_t *state, |
1893 | | njs_chb_t *chain, njs_bool_t sep_position) |
1894 | 24.2k | { |
1895 | 24.2k | double key, prev; |
1896 | 24.2k | int64_t diff; |
1897 | | |
1898 | 24.2k | if (!state->array) { |
1899 | 742 | return; |
1900 | 742 | } |
1901 | | |
1902 | 23.5k | if (sep_position) { |
1903 | 16.2k | key = njs_key_to_index(state->key); |
1904 | 16.2k | prev = (state->index > 1) ? njs_key_to_index(&state->key[-1]) : -1; |
1905 | | |
1906 | 16.2k | } else { |
1907 | 7.26k | key = state->length; |
1908 | 7.26k | if (state->key == NULL) { |
1909 | 0 | prev = -1; |
1910 | |
|
1911 | 7.26k | } else { |
1912 | 7.26k | prev = (state->index > 0) ? njs_key_to_index(state->key) : -1; |
1913 | 7.26k | } |
1914 | 7.26k | } |
1915 | | |
1916 | 23.5k | if (isnan(prev)) { |
1917 | 0 | return; |
1918 | 0 | } |
1919 | | |
1920 | 23.5k | if (isnan(key)) { |
1921 | 0 | key = state->length; |
1922 | 0 | } |
1923 | | |
1924 | 23.5k | diff = key - prev; |
1925 | | |
1926 | 23.5k | if (diff > 1) { |
1927 | 2.83k | if (sep_position == 0 && state->keys->length) { |
1928 | 0 | if (prev != -1) { |
1929 | 0 | njs_chb_append_literal(chain, ","); |
1930 | 0 | njs_json_stringify_indent(stringify, chain, 1); |
1931 | 0 | } |
1932 | 0 | } |
1933 | | |
1934 | 2.83k | if (diff - 1 == 1) { |
1935 | 0 | njs_chb_sprintf(chain, 64, "<empty>"); |
1936 | |
|
1937 | 2.83k | } else { |
1938 | 2.83k | njs_chb_sprintf(chain, 64, "<%L empty items>", diff - 1); |
1939 | 2.83k | } |
1940 | | |
1941 | 2.83k | state->written = 1; |
1942 | | |
1943 | 2.83k | if (sep_position == 1 && state->keys->length) { |
1944 | 2.83k | njs_chb_append_literal(chain, ","); |
1945 | 2.83k | njs_json_stringify_indent(stringify, chain, 1); |
1946 | 2.83k | } |
1947 | 2.83k | } |
1948 | 23.5k | } |
1949 | | |
1950 | | |
1951 | | njs_int_t |
1952 | | njs_vm_value_dump(njs_vm_t *vm, njs_str_t *retval, njs_value_t *value, |
1953 | | njs_uint_t console, njs_uint_t indent) |
1954 | 7.72k | { |
1955 | 7.72k | njs_int_t ret; |
1956 | 7.72k | njs_chb_t chain; |
1957 | 7.72k | njs_str_t str; |
1958 | 7.72k | njs_value_t *key, *val, s, tag, exception; |
1959 | 7.72k | njs_json_state_t *state; |
1960 | 7.72k | njs_string_prop_t string; |
1961 | 7.72k | njs_object_prop_t *prop; |
1962 | 7.72k | njs_property_query_t pq; |
1963 | 7.72k | njs_json_stringify_t *stringify, dump_stringify; |
1964 | | |
1965 | 7.72k | stringify = &dump_stringify; |
1966 | | |
1967 | 7.72k | stringify->vm = vm; |
1968 | 7.72k | stringify->depth = 0; |
1969 | | |
1970 | 7.72k | if (njs_slow_path(vm->top_frame == NULL)) { |
1971 | | /* An exception was thrown during compilation. */ |
1972 | 0 | njs_vm_runtime_init(vm); |
1973 | 0 | } |
1974 | | |
1975 | 7.72k | if (njs_is_valid(&vm->exception)) { |
1976 | 0 | exception = njs_vm_exception(vm); |
1977 | 0 | value = &exception; |
1978 | 0 | } |
1979 | | |
1980 | 7.72k | NJS_CHB_MP_INIT(&chain, njs_vm_memory_pool(vm)); |
1981 | | |
1982 | 7.72k | if (!njs_dump_is_recursive(value)) { |
1983 | 255 | ret = njs_dump_terminal(stringify, &chain, value, console); |
1984 | 255 | if (njs_slow_path(ret != NJS_OK)) { |
1985 | 0 | goto memory_error; |
1986 | 0 | } |
1987 | | |
1988 | 255 | goto done; |
1989 | 255 | } |
1990 | | |
1991 | 7.46k | njs_set_undefined(&stringify->replacer); |
1992 | 7.46k | stringify->keys_type = NJS_ENUM_STRING | NJS_ENUM_SYMBOL; |
1993 | 7.46k | indent = njs_min(indent, 10); |
1994 | 7.46k | stringify->space.length = indent; |
1995 | 7.46k | stringify->space.start = stringify->space_buf; |
1996 | | |
1997 | 7.46k | njs_memset(stringify->space.start, ' ', indent); |
1998 | | |
1999 | 7.46k | state = njs_json_push_stringify_state(stringify, value); |
2000 | 7.46k | if (njs_slow_path(state == NULL)) { |
2001 | 0 | goto memory_error; |
2002 | 0 | } |
2003 | | |
2004 | 31.5k | for ( ;; ) { |
2005 | 31.5k | if (state->index == 0) { |
2006 | 7.46k | ret = njs_object_string_tag(vm, &state->value, &tag); |
2007 | 7.46k | if (njs_slow_path(ret == NJS_ERROR)) { |
2008 | 0 | return ret; |
2009 | 0 | } |
2010 | | |
2011 | 7.46k | if (ret == NJS_OK) { |
2012 | 0 | (void) njs_string_prop(vm, &string, &tag); |
2013 | 0 | njs_chb_append(&chain, string.start, string.size); |
2014 | 0 | njs_chb_append_literal(&chain, " "); |
2015 | 0 | } |
2016 | | |
2017 | 7.46k | njs_chb_append(&chain, state->array ? "[" : "{", 1); |
2018 | 7.46k | njs_json_stringify_indent(stringify, &chain, 1); |
2019 | 7.46k | } |
2020 | | |
2021 | 31.5k | if (njs_json_stringify_done(state, 0)) { |
2022 | 7.46k | njs_dump_empty(stringify, state, &chain, 0); |
2023 | | |
2024 | 7.46k | njs_json_stringify_indent(stringify, &chain, 0); |
2025 | 7.46k | njs_chb_append(&chain, state->array ? "]" : "}", 1); |
2026 | | |
2027 | 7.46k | state = njs_json_pop_stringify_state(stringify); |
2028 | 7.46k | if (state == NULL) { |
2029 | 7.46k | goto done; |
2030 | 7.46k | } |
2031 | | |
2032 | 0 | continue; |
2033 | 7.46k | } |
2034 | | |
2035 | 24.0k | njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0); |
2036 | | |
2037 | 24.0k | key = &state->keys->start[state->index++]; |
2038 | | |
2039 | 24.0k | if (state->array && key->atom_id == NJS_ATOM_STRING_length) { |
2040 | 7.26k | continue; |
2041 | 7.26k | } |
2042 | | |
2043 | 16.7k | state->key = key; |
2044 | | |
2045 | 16.7k | ret = njs_property_query_val(vm, &pq, &state->value, key); |
2046 | 16.7k | if (njs_slow_path(ret != NJS_OK)) { |
2047 | 0 | if (ret == NJS_DECLINED) { |
2048 | 0 | continue; |
2049 | 0 | } |
2050 | | |
2051 | 0 | goto exception; |
2052 | 0 | } |
2053 | | |
2054 | 16.7k | prop = pq.fhq.value; |
2055 | | |
2056 | 16.7k | if (prop->type == NJS_WHITEOUT || !prop->enumerable) { |
2057 | 0 | if (!state->array) { |
2058 | 0 | continue; |
2059 | 0 | } |
2060 | 0 | } |
2061 | | |
2062 | 16.7k | if (state->written) { |
2063 | 9.30k | njs_chb_append_literal(&chain, ","); |
2064 | 9.30k | njs_json_stringify_indent(stringify, &chain, 1); |
2065 | 9.30k | } |
2066 | | |
2067 | 16.7k | state->written = 1; |
2068 | | |
2069 | 16.7k | njs_dump_empty(stringify, state, &chain, 1); |
2070 | | |
2071 | 16.7k | if (!state->array || isnan(njs_key_to_index(key))) { |
2072 | 540 | njs_atom_string_get(vm, key->atom_id, &pq.fhq.key); |
2073 | 540 | njs_chb_append(&chain, pq.fhq.key.start, pq.fhq.key.length); |
2074 | 540 | njs_chb_append_literal(&chain, ":"); |
2075 | 540 | if (stringify->space.length != 0) { |
2076 | 0 | njs_chb_append_literal(&chain, " "); |
2077 | 0 | } |
2078 | 540 | } |
2079 | | |
2080 | 16.7k | val = njs_prop_value(prop); |
2081 | | |
2082 | 16.7k | if (prop->type == NJS_PROPERTY_HANDLER) { |
2083 | 0 | pq.scratch = *prop; |
2084 | 0 | prop = &pq.scratch; |
2085 | 0 | ret = njs_prop_handler(prop)(vm, prop, pq.fhq.key_hash, |
2086 | 0 | &state->value, NULL, |
2087 | 0 | njs_prop_value(prop)); |
2088 | |
|
2089 | 0 | if (njs_slow_path(ret == NJS_ERROR)) { |
2090 | 0 | return ret; |
2091 | 0 | } |
2092 | | |
2093 | 0 | val = njs_prop_value(prop); |
2094 | 0 | } |
2095 | | |
2096 | 16.7k | if (njs_is_accessor_descriptor(prop)) { |
2097 | 23 | if (njs_prop_getter(prop) != NULL) { |
2098 | 20 | if (njs_prop_setter(prop) != NULL) { |
2099 | 0 | njs_atom_to_value(vm, &s, NJS_ATOM_STRING__Getter_Setter_); |
2100 | |
|
2101 | 20 | } else { |
2102 | 20 | njs_atom_to_value(vm, &s, NJS_ATOM_STRING__Getter_); |
2103 | 20 | } |
2104 | | |
2105 | 20 | } else { |
2106 | 3 | njs_atom_to_value(vm, &s, NJS_ATOM_STRING__Setter_); |
2107 | 3 | } |
2108 | | |
2109 | 23 | val = &s; |
2110 | 23 | } |
2111 | | |
2112 | 16.7k | if (njs_dump_is_recursive(val)) { |
2113 | 0 | if (njs_slow_path(njs_dump_visited(vm, stringify, val))) { |
2114 | 0 | njs_chb_append_literal(&chain, "[Circular]"); |
2115 | 0 | continue; |
2116 | 0 | } |
2117 | | |
2118 | 0 | state = njs_json_push_stringify_state(stringify, val); |
2119 | 0 | if (njs_slow_path(state == NULL)) { |
2120 | 0 | goto exception; |
2121 | 0 | } |
2122 | | |
2123 | 0 | continue; |
2124 | 0 | } |
2125 | | |
2126 | 16.7k | ret = njs_dump_terminal(stringify, &chain, val, console); |
2127 | 16.7k | if (njs_slow_path(ret != NJS_OK)) { |
2128 | 0 | if (ret == NJS_DECLINED) { |
2129 | 0 | goto exception; |
2130 | 0 | } |
2131 | | |
2132 | 0 | goto memory_error; |
2133 | 0 | } |
2134 | 16.7k | } |
2135 | | |
2136 | 7.72k | done: |
2137 | | |
2138 | 7.72k | ret = njs_chb_join(&chain, &str); |
2139 | 7.72k | if (njs_slow_path(ret != NJS_OK)) { |
2140 | 0 | goto memory_error; |
2141 | 0 | } |
2142 | | |
2143 | 7.72k | njs_chb_destroy(&chain); |
2144 | | |
2145 | 7.72k | *retval = str; |
2146 | | |
2147 | 7.72k | return NJS_OK; |
2148 | | |
2149 | 0 | memory_error: |
2150 | |
|
2151 | 0 | njs_memory_error(vm); |
2152 | |
|
2153 | 0 | exception: |
2154 | |
|
2155 | 0 | njs_vm_value_string(vm, retval, &vm->exception); |
2156 | |
|
2157 | 0 | return NJS_OK; |
2158 | 0 | } |