Skip to content

Commit

Permalink
Fixed JSON.parse() when reviver function is provided.
Browse files Browse the repository at this point in the history
This closes #480 issue on Github.
  • Loading branch information
xeioex committed May 4, 2022
1 parent 9d68e0e commit 2ad0ea2
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 177 deletions.
257 changes: 80 additions & 177 deletions src/njs_json.c
Expand Up @@ -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;
Expand All @@ -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);

Expand Down Expand Up @@ -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))) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}


Expand Down
10 changes: 10 additions & 0 deletions src/test/njs_benchmark.c
Expand Up @@ -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"),
Expand Down
25 changes: 25 additions & 0 deletions src/test/njs_unit_test.c
Expand Up @@ -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()"),
Expand Down

0 comments on commit 2ad0ea2

Please sign in to comment.