Welcome! Log In Create A New Profile

Advanced

[njs] Fixed JSON.parse() when reviver function is provided.

Dmitry Volyntsev
May 04, 2022 08:04PM
details: https://hg.nginx.org/njs/rev/80ed74a0e205
branches:
changeset: 1849:80ed74a0e205
user: Dmitry Volyntsev <xeioex@nginx.com>
date: Wed May 04 16:44:48 2022 -0700
description:
Fixed JSON.parse() when reviver function is provided.

This closes #480 issue on Github.

diffstat:

src/njs_json.c | 289 +++++++++++++++-------------------------------
src/test/njs_benchmark.c | 10 +
src/test/njs_unit_test.c | 25 ++++
3 files changed, 131 insertions(+), 193 deletions(-)

diffs (417 lines):

diff -r 723873b4e315 -r 80ed74a0e205 src/njs_json.c
--- a/src/njs_json.c Wed May 04 16:23:46 2022 -0700
+++ b/src/njs_json.c Wed May 04 16:44:48 2022 -0700
@@ -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(con
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
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

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;
-
- return njs_json_parse_iterator(vm, parse, &value);
+ 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_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,
}


-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)
+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)
{
- 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];
+ int64_t k, length;
+ njs_int_t ret;
+ njs_value_t val, new_elem, index;
+ njs_value_t arguments[3];
+ njs_array_t *keys;
+
+ if (njs_slow_path(depth++ >= NJS_JSON_MAX_DEPTH)) {
+ njs_type_error(vm, "Nested too deep or a cyclic structure");
+ return NJS_ERROR;
}

- return NULL;
-}
-
-
-static njs_int_t
-njs_json_parse_iterator(njs_vm_t *vm, njs_json_parse_t *parse,
- njs_value_t *object)
-{
- 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;
-
- obj = njs_json_wrap_value(vm, &wrapper, object);
- if (njs_slow_path(obj == NULL)) {
+ ret = njs_value_property(vm, holder, name, &val);
+ if (njs_slow_path(ret == NJS_ERROR)) {
return NJS_ERROR;
}

- state = njs_json_push_parse_state(vm, parse, &wrapper);
- if (njs_slow_path(state == NULL)) {
- 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;
-
- 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;
+ for (k = 0; k < keys->length; k++) {
+ ret = njs_json_internalize_property(vm, reviver, &val,
+ &keys->start[k], depth,
+ &new_elem);
+
+ if (njs_slow_path(ret != NJS_OK)) {
+ goto done;
}

- continue;
- }
-
- 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;
+ if (njs_is_undefined(&new_elem)) {
+ ret = njs_value_property_delete(vm, &val, &keys->start[k],
+ NULL, 0);
+
+ } 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_object_length(vm, &val, &length);
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ return NJS_ERROR;
}
- }
-
- ret = njs_json_parse_iterator_call(vm, parse, state);
- 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;
+ }
+
+ ret = njs_json_internalize_property(vm, reviver, &val, &index,
+ depth, &new_elem);
+
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ if (njs_is_undefined(&new_elem)) {
+ ret = njs_value_property_delete(vm, &val, &index, NULL, 0);
+
+ } else {
+ ret = njs_value_property_set(vm, &val, &index, &new_elem);
+ }
+
+ if (njs_slow_path(ret == NJS_ERROR)) {
+ return NJS_ERROR;
+ }
+ }
}
}
-}
-
-
-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_function_apply(vm, parse->function, arguments, 3,
- &parse->retval);
- if (njs_slow_path(ret != NJS_OK)) {
- return ret;
- }
-
- if (njs_is_undefined(&parse->retval)) {
- prop->type = NJS_WHITEOUT;
-
- } else {
- prop->value = parse->retval;
- }
-
- break;
-
- case NJS_PROPERTY_REF:
- value = prop->value.data.u.value;
- arguments[2] = *value;
-
- ret = njs_function_apply(vm, parse->function, arguments, 3,
- &parse->retval);
- if (njs_slow_path(ret != NJS_OK)) {
- return ret;
- }
-
- 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);
- }
-
- if (njs_slow_path(ret == NJS_ERROR)) {
- return NJS_ERROR;
- }
-
- break;
-
- default:
- njs_internal_error(vm, "njs_json_parse_iterator_call() unexpected "
- "property type:%s", njs_prop_type_string(prop->type));
+
+ njs_value_assign(&arguments[0], holder);
+ njs_value_assign(&arguments[1], name);
+ njs_value_assign(&arguments[2], &val);
+
+ ret = njs_function_apply(vm, reviver, arguments, 3, retval);
+
+done:
+
+ if (keys != NULL) {
+ njs_array_destroy(vm, keys);
}

- return NJS_OK;
+ return ret;
}


diff -r 723873b4e315 -r 80ed74a0e205 src/test/njs_benchmark.c
--- a/src/test/njs_benchmark.c Wed May 04 16:23:46 2022 -0700
+++ b/src/test/njs_benchmark.c Wed May 04 16:44:48 2022 -0700
@@ -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 -r 723873b4e315 -r 80ed74a0e205 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c Wed May 04 16:23:46 2022 -0700
+++ b/src/test/njs_unit_test.c Wed May 04 16:44:48 2022 -0700
@@ -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()"),
_______________________________________________
nginx-devel mailing list -- nginx-devel@nginx.org
To unsubscribe send an email to nginx-devel-leave@nginx.org
Subject Author Views Posted

[njs] Fixed JSON.parse() when reviver function is provided.

Dmitry Volyntsev 531 May 04, 2022 08:04PM



Sorry, you do not have permission to post/reply in this forum.

Online Users

Guests: 323
Record Number of Users: 8 on April 13, 2023
Record Number of Guests: 421 on December 02, 2018
Powered by nginx      Powered by FreeBSD      PHP Powered      Powered by MariaDB      ipv6 ready