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

Dmitry Volyntsev xeioex at nginx.com
Wed May 4 23:58:42 UTC 2022


details:   https://hg.nginx.org/njs/rev/80ed74a0e205
branches:  
changeset: 1849:80ed74a0e205
user:      Dmitry Volyntsev <xeioex at 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()"),



More information about the nginx-devel mailing list