CVE-2022-43286: Use After Free (CWE-416)
value: Nginx NJS v0.7.2 was discovered to contain a heap-use-after-free bug caused by illegal memory copy in the function njs_json_parse_iterator_call at njs_json.c.
CWE description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
CVSS v3 base score: 9.8
Published: 2022-10-28T21:15Z
Repository: https://github.com/nginx/njs
Language: C
Files changed: src/njs_json.c, src/test/njs_benchmark.c, src/test/njs_unit_test.c
Commit date: 2022-05-04 16:44:48 -0700
Commit message: Fixed JSON.parse() when reviver function is provided.

This closes #480 issue on Github.

Diff: diff --git a/src/njs_json.c b/src/njs_json.c
index 344326d15..9b74365ea 100644
--- a/src/njs_json.c
+++ b/src/njs_json.c
@@ -26,31 +26,21 @@ typedef struct {
 
     int64_t                    index;
     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;
     njs_str_t                  space;
     u_char                     space_buf[16];
@@ -70,14 +60,13 @@ static const u_char *njs_json_parse_number(njs_json_parse_ctx_t *ctx,
     njs_value_t *value, const u_char *p);
 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);
 
 static njs_int_t njs_json_stringify_iterator(njs_vm_t *vm,
     njs_json_stringify_t *stringify, njs_value_t *value);
@@ -106,19 +95,17 @@ static const njs_object_prop_t  njs_json_object_properties[];
 static njs_int_t
 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))) {
         ret = njs_value_to_string(vm, text, text);
         if (njs_slow_path(ret != NJS_OK)) {
@@ -154,15 +141,20 @@ njs_json_parse(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
         return NJS_ERROR;
     }
 
     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;
 
     return NJS_OK;
@@ -849,199 +841,110 @@ njs_json_skip_space(const u_char *start, const u_char *end)
 
     return p;
 }
 
 
-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;
 }
 
 
 static void
 njs_json_parse_exception(njs_json_parse_ctx_t *ctx, const char *msg,
diff --git a/src/test/njs_benchmark.c b/src/test/njs_benchmark.c
index 018b87bc5..0030e992c 100644
--- a/src/test/njs_benchmark.c
+++ b/src/test/njs_benchmark.c
@@ -206,10 +206,20 @@ static njs_benchmark_test_t  njs_test[] =
     { "JSON.parse",
       njs_str("JSON.parse('{\"a\":123, \"XXX\":[3,4,null]}').a"),
       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"),
       1 },
 
diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c
index 17a816743..215f2dd24 100644
--- a/src/test/njs_unit_test.c
+++ b/src/test/njs_unit_test.c
@@ -17201,10 +17201,35 @@ static njs_unit_test_t  njs_test[] =
 
     { njs_str("function func() {this[8] = 1; return new Int8Array(func)}"
               "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()"),
       njs_str("undefined") },
 

Vulnerable code:     njs_object_prop_t          *prop;
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;


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);
    njs_value_t           *text, value, lvalue;
    njs_json_parse_t      *parse, json_parse;
    parse = &json_parse;

    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);
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;
}


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)) {
    state = njs_json_push_parse_state(vm, parse, &wrapper);
    if (njs_slow_path(state == NULL)) {
    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;
                }
            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;
                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;
                continue;
            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_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));
    return NJS_OK;
Fixed code:     njs_value_t                prop;
#define NJS_JSON_MAX_DEPTH     32
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_value_t           *text, value, lvalue, wrapper;
    njs_object_t          *obj;
    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);
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)
    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");
    ret = njs_value_property(vm, holder, name, &val);
    if (njs_slow_path(ret == NJS_ERROR)) {
    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)) {
            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;
                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);
                if (njs_slow_path(ret == NJS_ERROR)) {
                    goto done;
                }
            ret = njs_object_length(vm, &val, &length);
            if (njs_slow_path(ret == NJS_ERROR)) {
                return NJS_ERROR;
            }
            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;
                }
            }
    }
    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 ret;
    { "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 },

    { 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") },