From 2ad0ea24a58d570634e09c2e58c3b314505eaa6a Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Wed, 4 May 2022 16:44:48 -0700 Subject: [PATCH] Fixed JSON.parse() when reviver function is provided. This closes #480 issue on Github. --- src/njs_json.c | 257 ++++++++++++--------------------------- src/test/njs_benchmark.c | 10 ++ src/test/njs_unit_test.c | 25 ++++ 3 files changed, 115 insertions(+), 177 deletions(-) diff --git a/src/njs_json.c b/src/njs_json.c index 344326d1..9b74365e 100644 --- a/src/njs_json.c +++ b/src/njs_json.c @@ -28,27 +28,17 @@ typedef struct { int64_t length; njs_array_t *keys; njs_value_t *key; - njs_object_prop_t *prop; + njs_value_t prop; } njs_json_state_t; -typedef struct { - njs_value_t retval; - - njs_uint_t depth; -#define NJS_JSON_MAX_DEPTH 32 - njs_json_state_t states[NJS_JSON_MAX_DEPTH]; - - njs_function_t *function; -} njs_json_parse_t; - - typedef struct { njs_value_t retval; njs_vm_t *vm; njs_uint_t depth; +#define NJS_JSON_MAX_DEPTH 32 njs_json_state_t states[NJS_JSON_MAX_DEPTH]; njs_value_t replacer; @@ -72,10 +62,9 @@ njs_inline uint32_t njs_json_unicode(const u_char *p); static const u_char *njs_json_skip_space(const u_char *start, const u_char *end); -static njs_int_t njs_json_parse_iterator(njs_vm_t *vm, njs_json_parse_t *parse, - njs_value_t *value); -static njs_int_t njs_json_parse_iterator_call(njs_vm_t *vm, - njs_json_parse_t *parse, njs_json_state_t *state); +static njs_int_t njs_json_internalize_property(njs_vm_t *vm, + njs_function_t *reviver, njs_value_t *holder, njs_value_t *name, + njs_int_t depth, njs_value_t *retval); static void njs_json_parse_exception(njs_json_parse_ctx_t *ctx, const char *msg, const u_char *pos); @@ -108,15 +97,13 @@ njs_json_parse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused) { njs_int_t ret; - njs_value_t *text, value, lvalue; + njs_value_t *text, value, lvalue, wrapper; + njs_object_t *obj; const u_char *p, *end; - njs_json_parse_t *parse, json_parse; const njs_value_t *reviver; njs_string_prop_t string; njs_json_parse_ctx_t ctx; - parse = &json_parse; - text = njs_lvalue_arg(&lvalue, args, nargs, 1); if (njs_slow_path(!njs_is_string(text))) { @@ -156,11 +143,16 @@ njs_json_parse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, reviver = njs_arg(args, nargs, 2); - if (njs_slow_path(njs_is_function(reviver) && njs_is_object(&value))) { - parse->function = njs_function(reviver); - parse->depth = 0; + if (njs_slow_path(njs_is_function(reviver))) { + obj = njs_json_wrap_value(vm, &wrapper, &value); + if (njs_slow_path(obj == NULL)) { + return NJS_ERROR; + } - return njs_json_parse_iterator(vm, parse, &value); + return njs_json_internalize_property(vm, njs_function(reviver), + &wrapper, + njs_value_arg(&njs_string_empty), + 0, &vm->retval); } vm->retval = value; @@ -851,195 +843,106 @@ njs_json_skip_space(const u_char *start, const u_char *end) } -static njs_json_state_t * -njs_json_push_parse_state(njs_vm_t *vm, njs_json_parse_t *parse, - njs_value_t *value) -{ - njs_json_state_t *state; - - if (njs_slow_path(parse->depth >= NJS_JSON_MAX_DEPTH)) { - njs_type_error(vm, "Nested too deep or a cyclic structure"); - return NULL; - } - - state = &parse->states[parse->depth++]; - state->value = *value; - state->index = 0; - state->prop = NULL; - state->keys = njs_value_own_enumerate(vm, value, NJS_ENUM_KEYS, - NJS_ENUM_STRING, 0); - if (state->keys == NULL) { - return NULL; - } - - return state; -} - - -njs_inline njs_json_state_t * -njs_json_pop_parse_state(njs_vm_t *vm, njs_json_parse_t *parse) -{ - njs_json_state_t *state; - - state = &parse->states[parse->depth - 1]; - njs_array_destroy(vm, state->keys); - state->keys = NULL; - - if (parse->depth > 1) { - parse->depth--; - return &parse->states[parse->depth - 1]; - } - - return NULL; -} - - static njs_int_t -njs_json_parse_iterator(njs_vm_t *vm, njs_json_parse_t *parse, - njs_value_t *object) +njs_json_internalize_property(njs_vm_t *vm, njs_function_t *reviver, + njs_value_t *holder, njs_value_t *name, njs_int_t depth, + njs_value_t *retval) { - njs_int_t ret; - njs_value_t *key, wrapper; - njs_object_t *obj; - njs_json_state_t *state; - njs_object_prop_t *prop; - njs_property_query_t pq; + int64_t k, length; + njs_int_t ret; + njs_value_t val, new_elem, index; + njs_value_t arguments[3]; + njs_array_t *keys; - obj = njs_json_wrap_value(vm, &wrapper, object); - if (njs_slow_path(obj == NULL)) { + if (njs_slow_path(depth++ >= NJS_JSON_MAX_DEPTH)) { + njs_type_error(vm, "Nested too deep or a cyclic structure"); return NJS_ERROR; } - state = njs_json_push_parse_state(vm, parse, &wrapper); - if (njs_slow_path(state == NULL)) { + ret = njs_value_property(vm, holder, name, &val); + if (njs_slow_path(ret == NJS_ERROR)) { return NJS_ERROR; } - for ( ;; ) { - if (state->index < state->keys->length) { - njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 0); - - key = &state->keys->start[state->index]; - - ret = njs_property_query(vm, &pq, &state->value, key); - if (njs_slow_path(ret != NJS_OK)) { - if (ret == NJS_DECLINED) { - state->index++; - continue; - } + keys = NULL; + if (njs_is_object(&val)) { + if (!njs_is_array(&val)) { + keys = njs_array_keys(vm, &val, 0); + if (njs_slow_path(keys == NULL)) { return NJS_ERROR; } - prop = pq.lhq.value; - - if (prop->type == NJS_WHITEOUT) { - state->index++; - continue; - } - - state->prop = prop; + for (k = 0; k < keys->length; k++) { + ret = njs_json_internalize_property(vm, reviver, &val, + &keys->start[k], depth, + &new_elem); - if (prop->type == NJS_PROPERTY && njs_is_object(&prop->value)) { - state = njs_json_push_parse_state(vm, parse, &prop->value); - if (state == NULL) { - return NJS_ERROR; + if (njs_slow_path(ret != NJS_OK)) { + goto done; } - continue; - } + if (njs_is_undefined(&new_elem)) { + ret = njs_value_property_delete(vm, &val, &keys->start[k], + NULL, 0); - if (prop->type == NJS_PROPERTY_REF - && njs_is_object(prop->value.data.u.value)) - { - state = njs_json_push_parse_state(vm, parse, - prop->value.data.u.value); - if (state == NULL) { - return NJS_ERROR; + } else { + ret = njs_value_property_set(vm, &val, &keys->start[k], + &new_elem); } - continue; + if (njs_slow_path(ret == NJS_ERROR)) { + goto done; + } } } else { - state = njs_json_pop_parse_state(vm, parse); - if (state == NULL) { - vm->retval = parse->retval; - return NJS_OK; - } - } - - ret = njs_json_parse_iterator_call(vm, parse, state); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - } -} - -static njs_int_t -njs_json_parse_iterator_call(njs_vm_t *vm, njs_json_parse_t *parse, - njs_json_state_t *state) -{ - njs_int_t ret; - njs_value_t arguments[3], *value; - njs_object_prop_t *prop; - - prop = state->prop; - - arguments[0] = state->value; - arguments[1] = state->keys->start[state->index++]; - - switch (prop->type) { - case NJS_PROPERTY: - arguments[2] = prop->value; + ret = njs_object_length(vm, &val, &length); + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } - ret = njs_function_apply(vm, parse->function, arguments, 3, - &parse->retval); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } + for (k = 0; k < length; k++) { + ret = njs_int64_to_string(vm, &index, k); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } - if (njs_is_undefined(&parse->retval)) { - prop->type = NJS_WHITEOUT; + ret = njs_json_internalize_property(vm, reviver, &val, &index, + depth, &new_elem); - } else { - prop->value = parse->retval; - } + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } - break; + if (njs_is_undefined(&new_elem)) { + ret = njs_value_property_delete(vm, &val, &index, NULL, 0); - case NJS_PROPERTY_REF: - value = prop->value.data.u.value; - arguments[2] = *value; + } else { + ret = njs_value_property_set(vm, &val, &index, &new_elem); + } - ret = njs_function_apply(vm, parse->function, arguments, 3, - &parse->retval); - if (njs_slow_path(ret != NJS_OK)) { - return ret; + if (njs_slow_path(ret == NJS_ERROR)) { + return NJS_ERROR; + } + } } + } - if (njs_is_undefined(&parse->retval)) { - ret = njs_value_property_i64_delete(vm, &state->value, - state->index - 1, NULL); - - } else { - ret = njs_value_property_i64_set(vm, &state->value, - state->index - 1, &parse->retval); - } + njs_value_assign(&arguments[0], holder); + njs_value_assign(&arguments[1], name); + njs_value_assign(&arguments[2], &val); - if (njs_slow_path(ret == NJS_ERROR)) { - return NJS_ERROR; - } + ret = njs_function_apply(vm, reviver, arguments, 3, retval); - break; +done: - default: - njs_internal_error(vm, "njs_json_parse_iterator_call() unexpected " - "property type:%s", njs_prop_type_string(prop->type)); + if (keys != NULL) { + njs_array_destroy(vm, keys); } - return NJS_OK; + return ret; } diff --git a/src/test/njs_benchmark.c b/src/test/njs_benchmark.c index 018b87bc..0030e992 100644 --- a/src/test/njs_benchmark.c +++ b/src/test/njs_benchmark.c @@ -208,6 +208,16 @@ static njs_benchmark_test_t njs_test[] = njs_str("123"), 1000000 }, + { "JSON.parse large", + njs_str("JSON.parse(JSON.stringify([Array(2**16)]))[0].length"), + njs_str("65536"), + 10 }, + + { "JSON.parse reviver large", + njs_str("JSON.parse(JSON.stringify([Array(2**16)]), v=>v)"), + njs_str(""), + 10 }, + { "for loop 100M", njs_str("var i; for (i = 0; i < 100000000; i++); i"), njs_str("100000000"), diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 17a81674..215f2dd2 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -17203,6 +17203,31 @@ static njs_unit_test_t njs_test[] = "JSON.parse('[1]', func);"), njs_str("") }, + { njs_str("JSON.parse(JSON.stringify([Array(2**16)]), v => v)"), + njs_str("") }, + + { njs_str("var order = []; function reviver(k, v) { order.push(k); };" + "JSON.parse('{\"p1\":0,\"p2\":0,\"p1\":0,\"2\":0,\"1\":0}', reviver);" + "order"), + njs_str("1,2,p1,p2,") }, + + { njs_str("function reviver(k, v) {" + " if (k == '0') Object.defineProperty(this, '1', {configurable: false});" + " if (k == '1') return;" + " return v;" + " };" + "JSON.parse('[1, 2]', reviver)"), + njs_str("1,2") }, + + { njs_str("JSON.parse('0', (k, v) => {throw 'Oops'})"), + njs_str("Oops") }, + + { njs_str("JSON.parse('{\"a\":1}', (k, v) => {if (k == 'a') {throw 'Oops'}; return v;})"), + njs_str("Oops") }, + + { njs_str("JSON.parse('[2,3,43]', (k, v) => {if (v == 43) {throw 'Oops'}; return v;})"), + njs_str("Oops") }, + /* JSON.stringify() */ { njs_str("JSON.stringify()"), -- 2.47.3