From: Dmitry Volyntsev Date: Tue, 3 Oct 2017 18:24:58 +0000 (+0300) Subject: JSON object. X-Git-Tag: 0.1.14~7 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=0a421064db395ab88c2c7287998fd4269b99af7a;p=njs.git JSON object. --- diff --git a/Makefile b/Makefile index 18a8e67c..c33f262d 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,7 @@ $(NXT_BUILDDIR)/libnjs.a: \ $(NXT_BUILDDIR)/njs_string.o \ $(NXT_BUILDDIR)/njs_object.o \ $(NXT_BUILDDIR)/njs_array.o \ + $(NXT_BUILDDIR)/njs_json.o \ $(NXT_BUILDDIR)/njs_function.o \ $(NXT_BUILDDIR)/njs_regexp.o \ $(NXT_BUILDDIR)/njs_date.o \ @@ -48,6 +49,7 @@ $(NXT_BUILDDIR)/libnjs.a: \ $(NXT_BUILDDIR)/njs_string.o \ $(NXT_BUILDDIR)/njs_object.o \ $(NXT_BUILDDIR)/njs_array.o \ + $(NXT_BUILDDIR)/njs_json.o \ $(NXT_BUILDDIR)/njs_function.o \ $(NXT_BUILDDIR)/njs_regexp.o \ $(NXT_BUILDDIR)/njs_date.o \ @@ -211,6 +213,18 @@ $(NXT_BUILDDIR)/njs_array.o: \ -I$(NXT_LIB) -Injs \ njs/njs_array.c +$(NXT_BUILDDIR)/njs_json.o: \ + $(NXT_BUILDDIR)/libnxt.a \ + njs/njscript.h \ + njs/njs_vm.h \ + njs/njs_object.h \ + njs/njs_json.c \ + + $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_json.o $(NXT_CFLAGS) \ + -I$(NXT_LIB) -Injs \ + njs/njs_json.c + + $(NXT_BUILDDIR)/njs_function.o: \ $(NXT_BUILDDIR)/libnxt.a \ njs/njscript.h \ diff --git a/njs/njs_array.c b/njs/njs_array.c index ef9ac759..8465e2d4 100644 --- a/njs/njs_array.c +++ b/njs/njs_array.c @@ -157,7 +157,7 @@ njs_array_alloc(njs_vm_t *vm, uint32_t length, uint32_t spare) } -static njs_ret_t +njs_ret_t njs_array_add(njs_vm_t *vm, njs_array_t *array, njs_value_t *value) { njs_ret_t ret; diff --git a/njs/njs_array.h b/njs/njs_array.h index 320c2d95..7b6e7c10 100644 --- a/njs/njs_array.h +++ b/njs/njs_array.h @@ -16,6 +16,7 @@ njs_array_t *njs_array_alloc(njs_vm_t *vm, uint32_t length, uint32_t spare); +njs_ret_t njs_array_add(njs_vm_t *vm, njs_array_t *array, njs_value_t *value); njs_ret_t njs_array_string_add(njs_vm_t *vm, njs_array_t *array, u_char *start, size_t size, size_t length); njs_ret_t njs_array_expand(njs_vm_t *vm, njs_array_t *array, uint32_t prepend, diff --git a/njs/njs_builtin.c b/njs/njs_builtin.c index 53d699a7..6aae9e99 100644 --- a/njs/njs_builtin.c +++ b/njs/njs_builtin.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,7 @@ static nxt_int_t njs_builtin_completions(njs_vm_t *vm, size_t *size, const njs_object_init_t *njs_object_init[] = { NULL, /* global this */ &njs_math_object_init, /* Math */ + &njs_json_object_init, /* JSON */ }; diff --git a/njs/njs_generator.c b/njs/njs_generator.c index db8b5c35..7219f567 100644 --- a/njs/njs_generator.c +++ b/njs/njs_generator.c @@ -298,6 +298,7 @@ njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) case NJS_TOKEN_GLOBAL_THIS: case NJS_TOKEN_MATH: + case NJS_TOKEN_JSON: case NJS_TOKEN_EVAL: case NJS_TOKEN_TO_STRING: case NJS_TOKEN_IS_NAN: diff --git a/njs/njs_json.c b/njs/njs_json.c new file mode 100644 index 00000000..9bcc9030 --- /dev/null +++ b/njs/njs_json.c @@ -0,0 +1,2088 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +typedef struct { + njs_vm_t *vm; + nxt_mem_cache_pool_t *pool; + nxt_uint_t depth; + u_char *start; + u_char *end; +} njs_json_parse_ctx_t; + + +typedef struct { + njs_value_t value; + + uint8_t written; /* 1 bit */ + + enum { + NJS_JSON_OBJECT_START, + NJS_JSON_OBJECT_CONTINUE, + NJS_JSON_OBJECT_TO_JSON_REPLACED, + NJS_JSON_OBJECT_REPLACED, + NJS_JSON_ARRAY_START, + NJS_JSON_ARRAY_CONTINUE, + NJS_JSON_ARRAY_TO_JSON_REPLACED, + NJS_JSON_ARRAY_REPLACED + } type:8; + + uint32_t index; + njs_array_t *keys; + njs_value_t *prop_value; +} njs_json_state_t; + + +typedef struct { + union { + njs_continuation_t cont; + u_char padding[NJS_CONTINUATION_SIZE]; + } u; + /* + * This retval value must be aligned so the continuation is padded + * to aligned size. + */ + njs_value_t retval; + + nxt_array_t stack; + njs_json_state_t *state; + njs_function_t *function; +} njs_json_parse_t; + + +typedef struct njs_chb_node_s njs_chb_node_t; + +struct njs_chb_node_s { + njs_chb_node_t *next; + u_char *start; + u_char *pos; + u_char *end; +}; + + +typedef struct { + union { + njs_continuation_t cont; + u_char padding[NJS_CONTINUATION_SIZE]; + } u; + /* + * This retval value must be aligned so the continuation is padded + * to aligned size. + */ + njs_value_t retval; + njs_value_t key; + + njs_vm_t *vm; + nxt_mem_cache_pool_t *pool; + njs_chb_node_t *nodes; + njs_chb_node_t *last; + nxt_array_t stack; + njs_json_state_t *state; + + njs_value_t replacer; + nxt_str_t space; +} njs_json_stringify_t; + + +static u_char *njs_json_parse_value(njs_json_parse_ctx_t *ctx, + njs_value_t *value, u_char *p); +static u_char *njs_json_parse_object(njs_json_parse_ctx_t *ctx, + njs_value_t *value, u_char *p); +static u_char *njs_json_parse_array(njs_json_parse_ctx_t *ctx, + njs_value_t *value, u_char *p); +static u_char *njs_json_parse_string(njs_json_parse_ctx_t *ctx, + njs_value_t *value, u_char *p); +static u_char *njs_json_parse_number(njs_json_parse_ctx_t *ctx, + njs_value_t *value, u_char *p); +nxt_inline uint32_t njs_json_unicode(const u_char *p); +static u_char *njs_json_skip_space(u_char *start, u_char *end); + +static njs_ret_t njs_json_parse_continuation(njs_vm_t *vm, + njs_value_t *args, nxt_uint_t nargs, njs_index_t unused); +static njs_ret_t njs_json_parse_continuation_apply(njs_vm_t *vm, + njs_json_parse_t *parse); +static njs_json_state_t *njs_json_push_parse_state(njs_vm_t *vm, + njs_json_parse_t *parse, njs_value_t *value); +static njs_json_state_t *njs_json_pop_parse_state(njs_json_parse_t *parse); +static void njs_json_parse_exception(njs_json_parse_ctx_t *ctx, + const char* msg, u_char *pos); + +static njs_ret_t njs_json_stringify_continuation(njs_vm_t *vm, + njs_value_t *args, nxt_uint_t nargs, njs_index_t unused); +static njs_function_t *njs_object_to_json_function(njs_vm_t *vm, + njs_value_t *value); +static njs_ret_t njs_json_stringify_to_json(njs_vm_t *vm, + njs_json_stringify_t* stringify, njs_function_t *function, + njs_value_t *key, njs_value_t *value); +static njs_ret_t njs_json_stringify_replacer(njs_vm_t *vm, + njs_json_stringify_t* stringify, njs_value_t *key, njs_value_t *value); +static njs_ret_t njs_json_stringify_array(njs_vm_t *vm, + njs_json_stringify_t *stringify); +static njs_json_state_t *njs_json_push_stringify_state(njs_vm_t *vm, + njs_json_stringify_t *stringify, njs_value_t *value); +static njs_json_state_t *njs_json_pop_stringify_state( + njs_json_stringify_t *stringify); +static void njs_json_stringify_exception(njs_json_stringify_t *stringify, + const char* msg); + +static nxt_int_t njs_json_append_value(njs_json_stringify_t *stringify, + njs_value_t *value); +static nxt_int_t njs_json_append_string(njs_json_stringify_t *stringify, + njs_value_t *value); +static nxt_int_t njs_json_append_number(njs_json_stringify_t *stringify, + njs_value_t *value); + +static njs_value_t *njs_json_wrap_value(njs_vm_t *vm, njs_value_t *value); + + +#define NJS_JSON_BUF_MIN_SIZE 128 + +#define njs_json_buf_written(stringify, bytes) \ + (stringify)->last->pos += (bytes); + +#define njs_json_buf_node_size(n) (size_t) ((n)->pos - (n)->start) +#define njs_json_buf_node_room(n) (size_t) ((n)->end - (n)->pos) + +static nxt_int_t njs_json_buf_append(njs_json_stringify_t *stringify, + const char* msg, size_t len); +static u_char *njs_json_buf_reserve(njs_json_stringify_t *stringify, + size_t size); +static nxt_int_t njs_json_buf_pullup(njs_json_stringify_t *stringify, + nxt_str_t *str); + + +static njs_ret_t +njs_json_parse(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + njs_index_t unused) +{ + u_char *p, *end; + njs_value_t arg, *value, *wrapper; + njs_json_parse_t *parse; + njs_string_prop_t string; + njs_json_parse_ctx_t ctx; + + value = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_value_t)); + if (nxt_slow_path(value == NULL)) { + vm->exception = &njs_exception_memory_error; + return NXT_ERROR; + } + + if (nargs < 2) { + arg = njs_string_void; + } else { + arg = args[1]; + } + + (void) njs_string_prop(&string, &arg); + + p = string.start; + end = p + string.size; + + ctx.vm = vm; + ctx.pool = vm->mem_cache_pool; + ctx.depth = 32; + ctx.start = string.start; + ctx.end = end; + + p = njs_json_skip_space(p, end); + if (nxt_slow_path(p == end)) { + njs_json_parse_exception(&ctx, "Unexpected end of input", p); + return NXT_ERROR; + } + + p = njs_json_parse_value(&ctx, value, p); + if (nxt_slow_path(p == NULL)) { + return NXT_ERROR; + } + + p = njs_json_skip_space(p, end); + if (nxt_slow_path(p != end)) { + njs_json_parse_exception(&ctx, "Unexpected token", p); + return NXT_ERROR; + } + + if (nargs >= 3 && njs_is_function(&args[2]) && njs_is_object(value)) { + wrapper = njs_json_wrap_value(vm, value); + if (nxt_slow_path(wrapper == NULL)) { + goto memory_error; + } + + parse = njs_vm_continuation(vm); + parse->u.cont.function = njs_json_parse_continuation; + parse->function = args[2].data.u.function; + + if (nxt_array_init(&parse->stack, NULL, 4, sizeof(njs_json_state_t), + &njs_array_mem_proto, vm->mem_cache_pool) + == NULL) + { + goto memory_error; + } + + if (njs_json_push_parse_state(vm, parse, wrapper) == NULL) { + goto memory_error; + } + + return njs_json_parse_continuation(vm, args, nargs, unused); + } + + vm->retval = *value; + + return NXT_OK; + +memory_error: + + vm->exception = &njs_exception_memory_error; + return NXT_ERROR; +} + + +static njs_ret_t +njs_json_stringify(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + njs_index_t unused) +{ + double num; + nxt_int_t i; + njs_ret_t ret; + njs_value_t *wrapper; + njs_json_stringify_t *stringify; + + if (nargs < 2) { + vm->retval = njs_value_void; + return NXT_OK; + } + + stringify = njs_vm_continuation(vm); + stringify->vm = vm; + stringify->pool = vm->mem_cache_pool; + stringify->u.cont.function = njs_json_stringify_continuation; + stringify->nodes = NULL; + stringify->last = NULL; + + if (nargs >= 3 && (njs_is_function(&args[2]) || njs_is_array(&args[2]))) { + stringify->replacer = args[2]; + if (njs_is_array(&args[2])) { + ret = njs_json_stringify_array(vm, stringify); + if (nxt_slow_path(ret != NXT_OK)) { + goto memory_error; + } + } + + } else { + stringify->replacer = njs_value_void; + } + + stringify->space.length = 0; + + if (nargs >= 4 && (njs_is_string(&args[3]) || njs_is_number(&args[3]))) { + if (njs_is_string(&args[3])) { + njs_string_get(&args[3], &stringify->space); + stringify->space.length = nxt_min(stringify->space.length, 10); + + } else { + num = args[3].data.u.number; + if (!isnan(num) && !isinf(num) && num > 0) { + num = nxt_min(num, 10); + + stringify->space.length = (size_t) num; + stringify->space.start = nxt_mem_cache_alloc(vm->mem_cache_pool, + (size_t) num + 1); + if (nxt_slow_path(stringify->space.start == NULL)) { + goto memory_error; + } + + for (i = 0; i < (int) num; i++) { + stringify->space.start[i] = ' '; + } + } + } + } + + if (nxt_array_init(&stringify->stack, NULL, 4, sizeof(njs_json_state_t), + &njs_array_mem_proto, vm->mem_cache_pool) + == NULL) + { + goto memory_error; + } + + wrapper = njs_json_wrap_value(vm, &args[1]); + if (nxt_slow_path(wrapper == NULL)) { + goto memory_error; + } + + if (njs_json_push_stringify_state(vm, stringify, wrapper) == NULL) { + goto memory_error; + } + + return njs_json_stringify_continuation(vm, args, nargs, unused); + +memory_error: + + vm->exception = &njs_exception_memory_error; + return NXT_ERROR; +} + + +static u_char * +njs_json_parse_value(njs_json_parse_ctx_t *ctx, njs_value_t *value, u_char *p) +{ + switch (*p) { + case '{': + return njs_json_parse_object(ctx, value, p); + + case '[': + return njs_json_parse_array(ctx, value, p); + + case '"': + return njs_json_parse_string(ctx, value, p); + + case 't': + if (nxt_fast_path(ctx->end - p >= 4 && memcmp(p, "true", 4) == 0)) { + *value = njs_value_true; + + return p + 4; + } + + goto error; + + case 'f': + if (nxt_fast_path(ctx->end - p >= 5 && memcmp(p, "false", 5) == 0)) { + *value = njs_value_false; + + return p + 5; + } + + goto error; + + case 'n': + if (nxt_fast_path(ctx->end - p >= 4 && memcmp(p, "null", 4) == 0)) { + *value = njs_value_null; + + return p + 4; + } + + goto error; + } + + if (nxt_fast_path(*p == '-' || (*p - '0') <= 9)) { + return njs_json_parse_number(ctx, value, p); + } + +error: + + njs_json_parse_exception(ctx, "Unexpected token", p); + + return NULL; +} + + +static u_char * +njs_json_parse_object(njs_json_parse_ctx_t *ctx, njs_value_t *value, u_char *p) +{ + nxt_int_t ret; + njs_object_t *object; + njs_value_t *prop_name, *prop_value; + njs_object_prop_t *prop; + nxt_lvlhsh_query_t lhq; + + if (nxt_slow_path(--ctx->depth == 0)) { + njs_json_parse_exception(ctx, "Nested too deep", p); + return NULL; + } + + object = njs_object_alloc(ctx->vm); + if (nxt_slow_path(object == NULL)) { + goto memory_error; + } + + prop = NULL; + + for ( ;; ) { + p = njs_json_skip_space(p + 1, ctx->end); + if (nxt_slow_path(p == ctx->end)) { + goto error_end; + } + + if (*p != '"') { + if (nxt_fast_path(*p == '}')) { + if (nxt_slow_path(prop != NULL)) { + njs_json_parse_exception(ctx, "Trailing comma", p - 1); + return NULL; + } + + break; + } + + goto error_token; + } + + prop_name = nxt_mem_cache_alloc(ctx->pool, sizeof(njs_value_t)); + if (nxt_slow_path(prop_name == NULL)) { + goto memory_error; + } + + p = njs_json_parse_string(ctx, prop_name, p); + if (nxt_slow_path(p == NULL)) { + /* The exception is set by the called function. */ + return NULL; + } + + p = njs_json_skip_space(p, ctx->end); + if (nxt_slow_path(p == ctx->end || *p != ':')) { + goto error_token; + } + + p = njs_json_skip_space(p + 1, ctx->end); + if (nxt_slow_path(p == ctx->end)) { + goto error_end; + } + + prop_value = nxt_mem_cache_alloc(ctx->pool, sizeof(njs_value_t)); + if (nxt_slow_path(prop_value == NULL)) { + goto memory_error; + } + + p = njs_json_parse_value(ctx, prop_value, p); + if (nxt_slow_path(p == NULL)) { + /* The exception is set by the called function. */ + return NULL; + } + + prop = njs_object_prop_alloc(ctx->vm, prop_name, prop_value, 1); + if (nxt_slow_path(prop == NULL)) { + goto memory_error; + } + + njs_string_get(prop_name, &lhq.key); + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); + lhq.value = prop; + lhq.replace = 1; + lhq.pool = ctx->pool; + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_insert(&object->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + ctx->vm->exception = &njs_exception_internal_error; + return NULL; + } + + p = njs_json_skip_space(p, ctx->end); + if (nxt_slow_path(p == ctx->end)) { + goto error_end; + } + + if (*p != ',') { + if (nxt_fast_path(*p == '}')) { + break; + } + + goto error_token; + } + } + + value->data.u.object = object; + value->type = NJS_OBJECT; + value->data.truth = 1; + + ctx->depth++; + + return p + 1; + +error_token: + + njs_json_parse_exception(ctx, "Unexpected token", p); + + return NULL; + +error_end: + + njs_json_parse_exception(ctx, "Unexpected end of input", p); + + return NULL; + +memory_error: + + ctx->vm->exception = &njs_exception_memory_error; + + return NULL; +} + + +static u_char * +njs_json_parse_array(njs_json_parse_ctx_t *ctx, njs_value_t *value, u_char *p) +{ + nxt_int_t ret; + njs_array_t *array; + njs_value_t *element; + + if (nxt_slow_path(--ctx->depth == 0)) { + njs_json_parse_exception(ctx, "Nested too deep", p); + return NULL; + } + + array = njs_array_alloc(ctx->vm, 0, 0); + if (nxt_slow_path(array == NULL)) { + ctx->vm->exception = &njs_exception_memory_error; + return NULL; + } + + element = NULL; + + for ( ;; ) { + p = njs_json_skip_space(p + 1, ctx->end); + if (nxt_slow_path(p == ctx->end)) { + goto error_end; + } + + if (*p == ']') { + if (nxt_slow_path(element != NULL)) { + njs_json_parse_exception(ctx, "Trailing comma", p - 1); + return NULL; + } + + break; + } + + element = nxt_mem_cache_alloc(ctx->pool, sizeof(njs_value_t)); + if (nxt_slow_path(element == NULL)) { + ctx->vm->exception = &njs_exception_memory_error; + return NULL; + } + + p = njs_json_parse_value(ctx, element, p); + if (nxt_slow_path(p == NULL)) { + return NULL; + } + + ret = njs_array_add(ctx->vm, array, element); + if (nxt_slow_path(ret != NXT_OK)) { + ctx->vm->exception = &njs_exception_internal_error; + return NULL; + } + + p = njs_json_skip_space(p, ctx->end); + if (nxt_slow_path(p == ctx->end)) { + goto error_end; + } + + if (*p != ',') { + if (nxt_fast_path(*p == ']')) { + break; + } + + goto error_token; + } + } + + value->data.u.array = array; + value->type = NJS_ARRAY; + value->data.truth = 1; + + ctx->depth++; + + return p + 1; + +error_token: + + njs_json_parse_exception(ctx, "Unexpected token", p); + + return NULL; + +error_end: + + njs_json_parse_exception(ctx, "Unexpected end of input", p); + + return NULL; +} + + +static u_char * +njs_json_parse_string(njs_json_parse_ctx_t *ctx, njs_value_t *value, u_char *p) +{ + u_char *s, ch, *last, *start; + size_t size, surplus; + ssize_t length; + uint32_t utf, utf_low; + njs_ret_t ret; + + enum { + sw_usual = 0, + sw_escape, + sw_encoded1, + sw_encoded2, + sw_encoded3, + sw_encoded4, + } state; + + start = p + 1; + + state = 0; + surplus = 0; + + for (p = start; p < ctx->end; p++) { + ch = *p; + + switch (state) { + + case sw_usual: + + if (ch == '"') { + break; + } + + if (ch == '\\') { + state = sw_escape; + continue; + } + + if (nxt_fast_path(ch >= ' ')) { + continue; + } + + njs_json_parse_exception(ctx, "Forbidden source char", p); + + return NULL; + + case sw_escape: + + switch (ch) { + case '"': + case '\\': + case '/': + case 'n': + case 'r': + case 't': + case 'b': + case 'f': + surplus++; + state = sw_usual; + continue; + + case 'u': + /* + * Basic unicode 6 bytes "\uXXXX" in JSON + * and up to 3 bytes in UTF-8. + * + * Surrogate pair: 12 bytes "\uXXXX\uXXXX" in JSON + * and 3 or 4 bytes in UTF-8. + */ + surplus += 3; + state = sw_encoded1; + continue; + } + + njs_json_parse_exception(ctx, "Unknown escape char", p); + + return NULL; + + case sw_encoded1: + case sw_encoded2: + case sw_encoded3: + case sw_encoded4: + + if (nxt_fast_path((ch >= '0' && ch <= '9') + || (ch >= 'A' && ch <= 'F') + || (ch >= 'a' && ch <= 'f'))) + { + state = (state == sw_encoded4) ? sw_usual : state + 1; + continue; + } + + njs_json_parse_exception(ctx, "Invalid Unicode escape sequence", p); + + return NULL; + } + + break; + } + + if (nxt_slow_path(p == ctx->end)) { + njs_json_parse_exception(ctx, "Unexpected end of input", p); + return NULL; + } + + /* Points to the ending quote mark. */ + last = p; + + size = last - start - surplus; + + if (surplus != 0) { + p = start; + + start = nxt_mem_cache_alloc(ctx->pool, size); + if (nxt_slow_path(start == NULL)) { + ctx->vm->exception = &njs_exception_memory_error; + return NULL; + } + + s = start; + + do { + ch = *p++; + + if (ch != '\\') { + *s++ = ch; + continue; + } + + ch = *p++; + + switch (ch) { + case '"': + case '\\': + case '/': + *s++ = ch; + continue; + + case 'n': + *s++ = '\n'; + continue; + + case 'r': + *s++ = '\r'; + continue; + + case 't': + *s++ = '\t'; + continue; + + case 'b': + *s++ = '\b'; + continue; + + case 'f': + *s++ = '\f'; + continue; + } + + /* "\uXXXX": Unicode escape sequence. */ + + utf = njs_json_unicode(p); + p += 4; + + if (utf >= 0xd800 && utf <= 0xdfff) { + + /* Surrogate pair. */ + + if (utf > 0xdbff || p[0] != '\\' || p[1] != 'u') { + njs_json_parse_exception(ctx, "Invalid Unicode char", p); + return NULL; + } + + p += 2; + + utf_low = njs_json_unicode(p); + p += 4; + + if (nxt_slow_path(utf_low < 0xdc00 || utf_low > 0xdfff)) { + njs_json_parse_exception(ctx, "Invalid surrogate pair", p); + return NULL; + } + + utf = 0x10000 + ((utf - 0xd800) << 10) + (utf_low - 0xdc00); + } + + s = nxt_utf8_encode(s, utf); + + } while (p != last); + + size = s - start; + } + + length = nxt_utf8_length(start, size); + if (nxt_slow_path(length < 0)) { + length = 0; + } + + ret = njs_string_create(ctx->vm, value, start, size, length); + if (nxt_slow_path(ret != NXT_OK)) { + ctx->vm->exception = &njs_exception_memory_error; + return NULL; + } + + return last + 1; +} + + +static u_char * +njs_json_parse_number(njs_json_parse_ctx_t *ctx, njs_value_t *value, u_char *p) +{ + u_char *start; + double num; + nxt_int_t sign; + + sign = 1; + + if (*p == '-') { + if (p + 1 == ctx->end) { + goto error; + } + + p++; + sign = -1; + } + + start = p; + num = njs_number_dec_parse(&p, ctx->end); + if (p != start) { + *value = njs_value_zero; + value->data.u.number = sign * num; + + return p; + } + +error: + + njs_json_parse_exception(ctx, "Unexpected number", p); + + return NULL; +} + + +nxt_inline uint32_t +njs_json_unicode(const u_char *p) +{ + u_char c; + uint32_t utf; + nxt_uint_t i; + + utf = 0; + + for (i = 0; i < 4; i++) { + utf <<= 4; + c = p[i] | 0x20; + c -= '0'; + if (c > 9) { + c += '0' - 'a' + 10; + } + + utf |= c; + } + + return utf; +} + + +static u_char * +njs_json_skip_space(u_char *start, u_char *end) +{ + u_char *p; + + for (p = start; nxt_fast_path(p != end); p++) { + + switch (*p) { + case ' ': + case '\t': + case '\r': + case '\n': + continue; + } + + break; + } + + return p; +} + + +#define njs_json_is_non_empty(_value) \ + (((_value)->type == NJS_OBJECT) \ + && !nxt_lvlhsh_is_empty(&(_value)->data.u.object->hash)) \ + || (((_value)->type == NJS_ARRAY) && (_value)->data.u.array->length != 0) + + +static njs_ret_t +njs_json_parse_continuation(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, + njs_index_t unused) +{ + nxt_int_t ret; + njs_value_t *key, *value; + njs_json_state_t *state; + njs_json_parse_t *parse; + njs_object_prop_t *prop; + nxt_lvlhsh_query_t lhq; + + parse = njs_vm_continuation(vm); + state = parse->state; + + for ( ;; ) { + switch (state->type) { + case NJS_JSON_OBJECT_START: + if (state->index < state->keys->length) { + key = &state->keys->start[state->index]; + njs_string_get(key, &lhq.key); + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_find(&state->value.data.u.object->hash, &lhq); + if (nxt_slow_path(ret == NXT_DECLINED)) { + state->index++; + break; + } + + prop = lhq.value; + state->prop_value = &prop->value; + + if (njs_json_is_non_empty(&prop->value)) { + state = njs_json_push_parse_state(vm, parse, &prop->value); + if (state == NULL) { + goto memory_error; + } + + break; + } + + } else { + state = njs_json_pop_parse_state(parse); + if (state == NULL) { + vm->retval = parse->retval; + + return NXT_OK; + } + } + + return njs_json_parse_continuation_apply(vm, parse); + + case NJS_JSON_OBJECT_REPLACED: + key = &state->keys->start[state->index]; + njs_string_get(key, &lhq.key); + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); + lhq.replace = 1; + lhq.proto = &njs_object_hash_proto; + lhq.pool = vm->mem_cache_pool; + + if (njs_is_void(&parse->retval)) { + ret = nxt_lvlhsh_delete(&state->value.data.u.object->hash, + &lhq); + + } else { + prop = njs_object_prop_alloc(vm, key, &parse->retval, 1); + if (nxt_slow_path(prop == NULL)) { + goto memory_error; + } + + lhq.value = prop; + ret = nxt_lvlhsh_insert(&state->value.data.u.object->hash, + &lhq); + } + + if (nxt_slow_path(ret != NXT_OK)) { + vm->exception = &njs_exception_internal_error; + return NXT_ERROR; + } + + state->index++; + state->type = NJS_JSON_OBJECT_START; + + break; + + case NJS_JSON_ARRAY_START: + if (state->index < state->value.data.u.array->length) { + value = &state->value.data.u.array->start[state->index]; + + if (njs_json_is_non_empty(value)) { + state = njs_json_push_parse_state(vm, parse, value); + if (state == NULL) { + goto memory_error; + } + + break; + } + + } else { + state = njs_json_pop_parse_state(parse); + } + + return njs_json_parse_continuation_apply(vm, parse); + + case NJS_JSON_ARRAY_REPLACED: + value = &state->value.data.u.array->start[state->index]; + *value = parse->retval; + + state->index++; + state->type = NJS_JSON_ARRAY_START; + + break; + + default: + vm->exception = &njs_exception_internal_error; + return NXT_ERROR; + } + } + +memory_error: + + vm->exception = &njs_exception_memory_error; + + return NXT_ERROR; +} + + +static njs_ret_t +njs_json_parse_continuation_apply(njs_vm_t *vm, njs_json_parse_t *parse) +{ + size_t size; + njs_value_t arguments[3]; + njs_json_state_t *state; + + state = parse->state; + + arguments[0] = state->value; + + switch (state->type) { + case NJS_JSON_OBJECT_START: + arguments[1] = state->keys->start[state->index]; + arguments[2] = *state->prop_value; + + state->type = NJS_JSON_OBJECT_REPLACED; + break; + + case NJS_JSON_ARRAY_START: + size = snprintf((char *) njs_string_short_start(&arguments[1]), + NJS_STRING_SHORT, "%u", state->index); + njs_string_short_set(&arguments[1], size, size); + arguments[2] = state->value.data.u.array->start[state->index]; + + state->type = NJS_JSON_ARRAY_REPLACED; + break; + + default: + vm->exception = &njs_exception_internal_error; + return NXT_ERROR; + } + + njs_set_invalid(&parse->retval); + + return njs_function_apply(vm, parse->function, arguments, 3, + (njs_index_t) &parse->retval); +} + + +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; + + state = nxt_array_add(&parse->stack, &njs_array_mem_proto, + vm->mem_cache_pool); + if (state != NULL) { + state = nxt_array_last(&parse->stack); + state->value = *value; + state->index = 0; + + if (njs_is_array(value)) { + state->type = NJS_JSON_ARRAY_START; + + } else { + state->type = NJS_JSON_OBJECT_START; + state->prop_value = NULL; + state->keys = njs_object_keys_array(vm, value); + if (state->keys == NULL) { + return NULL; + } + } + } + + parse->state = state; + + return state; +} + + +static njs_json_state_t * +njs_json_pop_parse_state(njs_json_parse_t *parse) +{ + if (parse->stack.items > 1) { + parse->stack.items--; + parse->state = nxt_array_last(&parse->stack); + return parse->state; + } + + return NULL; +} + + +static void +njs_json_parse_exception(njs_json_parse_ctx_t *ctx, const char* msg, + u_char *pos) +{ + size_t size; + ssize_t length; + njs_ret_t ret; + njs_value_t *exception; + + static u_char buf[256]; + + exception = nxt_mem_cache_alloc(ctx->pool, sizeof(njs_value_t)); + if (nxt_slow_path(exception == NULL)) { + ctx->vm->exception = &njs_exception_memory_error; + return; + } + + length = nxt_utf8_length(ctx->start, pos - ctx->start); + if (nxt_slow_path(length < 0)) { + length = 0; + } + + size = snprintf((char *) buf, sizeof(buf), + "SyntaxError: %s at position %zu", msg, length); + + ret = njs_string_new(ctx->vm, exception, buf, size, size); + if (nxt_slow_path(ret != NXT_OK)) { + ctx->vm->exception = &njs_exception_memory_error; + return; + } + + ctx->vm->exception = exception; +} + + +#define njs_is_object_or_array(value) \ + (((value)->type == NJS_OBJECT) || ((value)->type == NJS_ARRAY)) + + +#define njs_json_stringify_append(str, len) \ + ret = njs_json_buf_append(stringify, str, len); \ + if (ret != NXT_OK) { \ + goto memory_error; \ + } + + +#define njs_json_stringify_indent(times) \ + if (stringify->space.length != 0) { \ + njs_json_stringify_append("\n", 1); \ + for (i = 0; i < (nxt_int_t) (times) - 1; i++) { \ + njs_json_stringify_append((char *) stringify->space.start, \ + stringify->space.length); \ + } \ + } + + +#define njs_json_stringify_append_key(key) \ + if (state->written) { \ + njs_json_stringify_append(",", 1); \ + njs_json_stringify_indent(stringify->stack.items); \ + } \ + \ + state->written = 1; \ + njs_json_append_string(stringify, key); \ + njs_json_stringify_append(":", 1); \ + if (stringify->space.length != 0) { \ + njs_json_stringify_append(" ", 1); \ + } + + +#define njs_json_stringify_append_value(value) \ + state->written = 1; \ + ret = njs_json_append_value(stringify, value); \ + if (nxt_slow_path(ret != NXT_OK)) { \ + goto memory_error; \ + } + + +static njs_ret_t +njs_json_stringify_continuation(njs_vm_t *vm, njs_value_t *args, + nxt_uint_t nargs, njs_index_t unused) +{ + ssize_t length; + nxt_int_t i; + njs_ret_t ret; + nxt_str_t str; + njs_value_t *key, *value; + njs_function_t *to_json; + njs_json_state_t *state; + njs_object_prop_t *prop; + nxt_lvlhsh_query_t lhq; + njs_json_stringify_t *stringify; + + stringify = njs_vm_continuation(vm); + state = stringify->state; + + for ( ;; ) { + switch (state->type) { + case NJS_JSON_OBJECT_START: + njs_json_stringify_append("{", 1); + njs_json_stringify_indent(stringify->stack.items); + state->type = NJS_JSON_OBJECT_CONTINUE; + + /* Fall through. */ + + case NJS_JSON_OBJECT_CONTINUE: + if (state->index >= state->keys->length) { + njs_json_stringify_indent(stringify->stack.items - 1); + njs_json_stringify_append("}", 1); + + state = njs_json_pop_stringify_state(stringify); + if (state == NULL) { + goto done; + } + + break; + } + + key = &state->keys->start[state->index++]; + njs_string_get(key, &lhq.key); + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); + lhq.proto = &njs_object_hash_proto; + + ret = nxt_lvlhsh_find(&state->value.data.u.object->hash, &lhq); + if (nxt_slow_path(ret == NXT_DECLINED)) { + break; + } + + prop = lhq.value; + + if (!prop->enumerable + || njs_is_void(&prop->value) + || njs_is_function(&prop->value)) + { + break; + } + + if (njs_is_object(&prop->value)) { + to_json = njs_object_to_json_function(vm, &prop->value); + if (to_json != NULL) { + return njs_json_stringify_to_json(vm, stringify, to_json, + &prop->name, + &prop->value); + } + } + + if (njs_is_function(&stringify->replacer)) { + return njs_json_stringify_replacer(vm, stringify, &prop->name, + &prop->value); + } + + njs_json_stringify_append_key(&prop->name); + + if (njs_is_object_or_array(&prop->value)) { + state = njs_json_push_stringify_state(vm, stringify, + &prop->value); + if (state == NULL) { + return NXT_ERROR; + } + + break; + } + + njs_json_stringify_append_value(&prop->value); + + break; + + case NJS_JSON_OBJECT_TO_JSON_REPLACED: + if (njs_is_void(&stringify->retval)) { + state->type = NJS_JSON_OBJECT_CONTINUE; + break; + } + + if (njs_is_function(&stringify->replacer)) { + return njs_json_stringify_replacer(vm, stringify, + &stringify->key, + &stringify->retval); + } + + /* Fall through. */ + + case NJS_JSON_OBJECT_REPLACED: + state->type = NJS_JSON_OBJECT_CONTINUE; + + if (njs_is_void(&stringify->retval)) { + break; + } + + njs_json_stringify_append_key(&stringify->key); + + value = &stringify->retval; + if (njs_is_object(value)) { + state = njs_json_push_stringify_state(vm, stringify, value); + if (state == NULL) { + return NXT_ERROR; + } + + break; + } + + njs_json_stringify_append_value(value); + + break; + + case NJS_JSON_ARRAY_START: + njs_json_stringify_append("[", 1); + njs_json_stringify_indent(stringify->stack.items); + state->type = NJS_JSON_ARRAY_CONTINUE; + + /* Fall through. */ + + case NJS_JSON_ARRAY_CONTINUE: + if (state->index >= state->value.data.u.array->length) { + njs_json_stringify_indent(stringify->stack.items - 1); + njs_json_stringify_append("]", 1); + + state = njs_json_pop_stringify_state(stringify); + if (state == NULL) { + goto done; + } + + break; + } + + if (state->written) { + njs_json_stringify_append(",", 1); + njs_json_stringify_indent(stringify->stack.items); + } + + value = &state->value.data.u.array->start[state->index++]; + + if (njs_is_object(value)) { + to_json = njs_object_to_json_function(vm, value); + if (to_json != NULL) { + return njs_json_stringify_to_json(vm, stringify, to_json, + NULL, value); + } + + } + + if (njs_is_function(&stringify->replacer)) { + return njs_json_stringify_replacer(vm, stringify, NULL, value); + } + + if (njs_is_object_or_array(value)) { + state = njs_json_push_stringify_state(vm, stringify, value); + if (state == NULL) { + return NXT_ERROR; + } + + break; + } + + njs_json_stringify_append_value(value); + + break; + + case NJS_JSON_ARRAY_TO_JSON_REPLACED: + if (!njs_is_void(&stringify->retval) + && njs_is_function(&stringify->replacer)) + { + return njs_json_stringify_replacer(vm, stringify, NULL, + &stringify->retval); + } + + /* Fall through. */ + + case NJS_JSON_ARRAY_REPLACED: + state->type = NJS_JSON_ARRAY_CONTINUE; + + if (njs_is_object_or_array(&stringify->retval)) { + state = njs_json_push_stringify_state(vm, stringify, + &stringify->retval); + if (state == NULL) { + return NXT_ERROR; + } + + break; + } + + njs_json_stringify_append_value(&stringify->retval); + + break; + } + } + +done: + + ret = njs_json_buf_pullup(stringify, &str); + if (nxt_slow_path(ret != NXT_OK)) { + goto memory_error; + } + + /* + * The value to stringify is wrapped as '{"": value}'. + * An empty object means empty result. + */ + if (str.length <= sizeof("{\n\n}") - 1) { + vm->retval = njs_value_void; + return NXT_OK; + } + + /* Stripping the wrapper's data. */ + + str.start += sizeof("{\"\":") - 1; + str.length -= sizeof("{\"\":}") - 1; + + if (stringify->space.length != 0) { + str.start += sizeof("\n ") - 1; + str.length -= sizeof("\n \n") - 1; + } + + length = nxt_utf8_length(str.start, str.length); + if (nxt_slow_path(length < 0)) { + length = 0; + } + + return njs_string_create(vm, &vm->retval, str.start, str.length, length); + +memory_error: + + vm->exception = &njs_exception_memory_error; + return NXT_ERROR; +} + + +static njs_function_t * +njs_object_to_json_function(njs_vm_t *vm, njs_value_t *value) +{ + njs_object_prop_t *prop; + nxt_lvlhsh_query_t lhq; + + lhq.key_hash = NJS_TO_JSON_HASH; + lhq.key = nxt_string_value("toJSON"); + + prop = njs_object_property(vm, value->data.u.object, &lhq); + + if (prop != NULL && njs_is_function(&prop->value)) { + return prop->value.data.u.function; + } + + return NULL; +} + + +static njs_ret_t +njs_json_stringify_to_json(njs_vm_t *vm, njs_json_stringify_t* stringify, + njs_function_t *function, njs_value_t *key, njs_value_t *value) +{ + size_t size; + njs_value_t arguments[2]; + njs_json_state_t *state; + + njs_set_invalid(&stringify->retval); + + state = stringify->state; + + arguments[0] = *value; + + switch (state->type) { + case NJS_JSON_OBJECT_START: + case NJS_JSON_OBJECT_CONTINUE: + if (key != NULL) { + arguments[1] = *key; + stringify->key = *key; + + } else { + njs_string_short_set(&arguments[1], 0, 0); + njs_string_short_set(&stringify->key, 0, 0); + } + + state->type = NJS_JSON_OBJECT_TO_JSON_REPLACED; + break; + + case NJS_JSON_ARRAY_START: + case NJS_JSON_ARRAY_CONTINUE: + size = snprintf((char *) njs_string_short_start(&arguments[1]), + NJS_STRING_SHORT, "%u", state->index - 1); + njs_string_short_set(&arguments[1], size, size); + + state->type = NJS_JSON_ARRAY_TO_JSON_REPLACED; + break; + + default: + vm->exception = &njs_exception_internal_error; + return NXT_ERROR; + } + + return njs_function_apply(vm, function, arguments, 2, + (njs_index_t) &stringify->retval); +} + + +static njs_ret_t +njs_json_stringify_replacer(njs_vm_t *vm, njs_json_stringify_t* stringify, + njs_value_t *key, njs_value_t *value) +{ + size_t size; + njs_value_t arguments[3]; + njs_json_state_t *state; + + state = stringify->state; + + arguments[0] = state->value; + + switch (state->type) { + case NJS_JSON_OBJECT_START: + case NJS_JSON_OBJECT_CONTINUE: + case NJS_JSON_OBJECT_TO_JSON_REPLACED: + arguments[1] = *key; + stringify->key = *key; + arguments[2] = *value; + + state->type = NJS_JSON_OBJECT_REPLACED; + break; + + case NJS_JSON_ARRAY_START: + case NJS_JSON_ARRAY_CONTINUE: + case NJS_JSON_ARRAY_TO_JSON_REPLACED: + size = snprintf((char *) njs_string_short_start(&arguments[1]), + NJS_STRING_SHORT, "%u", state->index - 1); + njs_string_short_set(&arguments[1], size, size); + arguments[2] = *value; + + state->type = NJS_JSON_ARRAY_REPLACED; + break; + + default: + vm->exception = &njs_exception_internal_error; + return NXT_ERROR; + } + + njs_set_invalid(&stringify->retval); + + return njs_function_apply(vm, stringify->replacer.data.u.function, + arguments, 3, (njs_index_t) &stringify->retval); +} + + +static njs_ret_t +njs_json_stringify_array(njs_vm_t *vm, njs_json_stringify_t *stringify) +{ + njs_ret_t ret; + uint32_t i, n, k, properties_length, array_length; + njs_value_t *value, num_value; + njs_array_t *properties, *array; + + properties_length = 1; + array = stringify->replacer.data.u.array; + array_length = array->length; + + for (i = 0; i < array_length; i++) { + if (njs_is_valid(&array->start[i])) { + properties_length++; + } + } + + properties = njs_array_alloc(vm, properties_length, NJS_ARRAY_SPARE); + if (nxt_slow_path(properties == NULL)) { + return NXT_ERROR; + } + + n = 0; + properties->start[n++] = njs_string_empty; + + for (i = 0; i < array_length; i++) { + value = &array->start[i]; + + if (!njs_is_valid(&array->start[i])) { + continue; + } + + switch (value->type) { + case NJS_OBJECT_NUMBER: + value = &value->data.u.object_value->value; + /* Fall through. */ + + case NJS_NUMBER: + ret = njs_number_to_string(vm, &num_value, value); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + + value = &num_value; + break; + + case NJS_OBJECT_STRING: + value = &value->data.u.object_value->value; + break; + + case NJS_STRING: + break; + + default: + continue; + } + + for (k = 0; k < n; k ++) { + if (njs_values_strict_equal(value, &properties->start[k]) == 1) { + break; + } + } + + if (k == n) { + properties->start[n++] = *value; + } + } + + properties->length = n; + stringify->replacer.data.u.array = properties; + + return NXT_OK; +} + + +static njs_json_state_t * +njs_json_push_stringify_state(njs_vm_t *vm, njs_json_stringify_t *stringify, + njs_value_t *value) +{ + njs_json_state_t *state; + + if (stringify->stack.items >= 32) { + njs_json_stringify_exception(stringify, + "Nested too deep or a cyclic structure"); + return NULL; + } + + state = nxt_array_add(&stringify->stack, &njs_array_mem_proto, + vm->mem_cache_pool); + if (nxt_slow_path(state == NULL)) { + vm->exception = &njs_exception_memory_error; + return NULL; + } + + state = nxt_array_last(&stringify->stack); + state->value = *value; + state->index = 0; + state->written = 0; + + if (njs_is_array(value)) { + state->type = NJS_JSON_ARRAY_START; + + } else { + state->type = NJS_JSON_OBJECT_START; + state->prop_value = NULL; + + if (njs_is_array(&stringify->replacer)) { + state->keys = stringify->replacer.data.u.array; + + } else { + state->keys = njs_object_keys_array(vm, value); + if (state->keys == NULL) { + return NULL; + } + } + } + + stringify->state = state; + return state; +} + + +static njs_json_state_t * +njs_json_pop_stringify_state(njs_json_stringify_t *stringify) +{ + if (stringify->stack.items > 1) { + stringify->stack.items--; + stringify->state = nxt_array_last(&stringify->stack); + stringify->state->written = 1; + return stringify->state; + } + + return NULL; +} + + +static void +njs_json_stringify_exception(njs_json_stringify_t *stringify, const char* msg) +{ + size_t size; + njs_ret_t ret; + njs_value_t *exception; + + static u_char buf[256]; + + exception = nxt_mem_cache_alloc(stringify->pool, sizeof(njs_value_t)); + if (nxt_slow_path(exception == NULL)) { + stringify->vm->exception = &njs_exception_memory_error; + return; + } + + size = snprintf((char *) buf, sizeof(buf), "TypeError: %s", msg); + + ret = njs_string_new(stringify->vm, exception, buf, size, size); + if (nxt_slow_path(ret != NXT_OK)) { + stringify->vm->exception = &njs_exception_memory_error; + return; + } + + stringify->vm->exception = exception; +} + + +static nxt_int_t +njs_json_append_value(njs_json_stringify_t *stringify, njs_value_t *value) +{ + switch (value->type) { + case NJS_OBJECT_STRING: + value = &value->data.u.object_value->value; + /* Fall through. */ + + case NJS_STRING: + return njs_json_append_string(stringify, value); + + case NJS_OBJECT_NUMBER: + value = &value->data.u.object_value->value; + /* Fall through. */ + + case NJS_NUMBER: + return njs_json_append_number(stringify, value); + + case NJS_OBJECT_BOOLEAN: + value = &value->data.u.object_value->value; + /* Fall through. */ + + case NJS_BOOLEAN: + if (njs_is_true(value)) { + return njs_json_buf_append(stringify, "true", 4); + + } else { + return njs_json_buf_append(stringify, "false", 5); + } + + case NJS_VOID: + case NJS_NULL: + case NJS_FUNCTION: + return njs_json_buf_append(stringify, "null", 4); + + default: + njs_json_stringify_exception(stringify, "Non-serializable object"); + return NXT_DECLINED; + } +} + + +static nxt_int_t +njs_json_append_string(njs_json_stringify_t *stringify, njs_value_t *value) +{ + u_char c, *dst, *dst_end; + size_t length; + const u_char *p, *end; + njs_string_prop_t str; + + static char hex2char[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + (void) njs_string_prop(&str, value); + + p = str.start; + end = p + str.size; + length = str.length; + + dst = njs_json_buf_reserve(stringify, 64); + if (nxt_slow_path(dst == NULL)) { + return NXT_ERROR; + } + + dst_end = dst + 64; + + *dst++ = '\"'; + + while (p < end) { + + if (*p < ' ' || *p == '\"' || *p == '\\') { + c = (u_char) *p++; + *dst++ = '\\'; + + switch (c) { + case '\\': + *dst++ = '\\'; + break; + case '"': + *dst++ = '\"'; + break; + case '\r': + *dst++ = 'r'; + break; + case '\n': + *dst++ = 'n'; + break; + case '\t': + *dst++ = 't'; + break; + case '\b': + *dst++ = 'b'; + break; + case '\f': + *dst++ = 'f'; + break; + default: + *dst++ = 'u'; + *dst++ = '0'; + *dst++ = '0'; + *dst++ = hex2char[(c & 0xf0) >> 4]; + *dst++ = hex2char[c & 0x0f]; + } + } + + /* + * Control characters less than space are encoded using 6 bytes + * "\uXXXX". Checking there is at least 6 bytes of destination storage + * space. + */ + + while (p < end && (dst_end - dst) > 6) { + if (*p < ' ' || *p == '\"' || *p == '\\') { + break; + } + + if (length != 0) { + /* UTF-8 or ASCII string. */ + dst = nxt_utf8_copy(dst, &p, end); + + } else { + /* Byte string. */ + *dst++ = *p++; + } + } + + if (dst_end - dst <= 6) { + njs_json_buf_written(stringify, dst - stringify->last->pos); + + dst = njs_json_buf_reserve(stringify, 64); + if (nxt_slow_path(dst == NULL)) { + return NXT_ERROR; + } + + dst_end = dst + 64; + } + } + + njs_json_buf_written(stringify, dst - stringify->last->pos); + njs_json_buf_append(stringify, "\"", 1); + + return NXT_OK; +} + + +static nxt_int_t +njs_json_append_number(njs_json_stringify_t *stringify, njs_value_t *value) +{ + u_char *p; + size_t size; + double num; + + num = value->data.u.number; + + if (isnan(num) || isinf(num)) { + return njs_json_buf_append(stringify, "null", 4); + + } else { + p = njs_json_buf_reserve(stringify, 64); + if (nxt_slow_path(p == NULL)) { + return NXT_ERROR; + } + + size = njs_num_to_buf(num, p, 64); + + njs_json_buf_written(stringify, size); + } + + return NXT_OK; +} + + +/* + * Wraps a value as '{"": }'. + */ +static njs_value_t * +njs_json_wrap_value(njs_vm_t *vm, njs_value_t *value) +{ + nxt_int_t ret; + njs_value_t *wrapper; + njs_object_prop_t *prop; + nxt_lvlhsh_query_t lhq; + + wrapper = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_value_t)); + if (nxt_slow_path(wrapper == NULL)) { + return NULL; + } + + wrapper->data.u.object = njs_object_alloc(vm); + if (nxt_slow_path(wrapper->data.u.object == NULL)) { + return NULL; + } + + wrapper->type = NJS_OBJECT; + wrapper->data.truth = 1; + + lhq.replace = 0; + lhq.proto = &njs_object_hash_proto; + lhq.pool = vm->mem_cache_pool; + lhq.key = nxt_string_value(""); + lhq.key_hash = NXT_DJB_HASH_INIT; + + prop = njs_object_prop_alloc(vm, &njs_string_empty, value, 1); + if (nxt_slow_path(prop == NULL)) { + return NULL; + } + + lhq.value = prop; + + ret = nxt_lvlhsh_insert(&wrapper->data.u.object->hash, &lhq); + if (nxt_slow_path(ret != NXT_OK)) { + return NULL; + } + + return wrapper; +} + + +static nxt_int_t +njs_json_buf_append(njs_json_stringify_t *stringify, const char* msg, + size_t len) +{ + u_char *p; + + p = njs_json_buf_reserve(stringify, len); + if (nxt_slow_path(p == NULL)) { + return NXT_ERROR; + } + + memcpy(p, msg, len); + + njs_json_buf_written(stringify, len); + + return NXT_OK; +} + + +static u_char * +njs_json_buf_reserve(njs_json_stringify_t *stringify, size_t size) +{ + njs_chb_node_t *n; + + if (nxt_slow_path(size == 0)) { + return NULL; + } + + n = stringify->last; + + if (nxt_fast_path(n != NULL && njs_json_buf_node_room(n) >= size)) { + return n->pos; + } + + if (size < NJS_JSON_BUF_MIN_SIZE) { + size = NJS_JSON_BUF_MIN_SIZE; + } + + n = nxt_mem_cache_alloc(stringify->pool, sizeof(njs_chb_node_t) + size); + if (nxt_slow_path(n == NULL)) { + return NULL; + } + + n->next = NULL; + n->start = (u_char *) n + sizeof(njs_chb_node_t); + n->pos = n->start; + n->end = n->pos + size; + + if (stringify->last != NULL) { + stringify->last->next = n; + + } else { + stringify->nodes = n; + } + + stringify->last = n; + + return n->start; +} + + +static nxt_int_t +njs_json_buf_pullup(njs_json_stringify_t *stringify, nxt_str_t *str) +{ + u_char *start; + size_t size; + njs_chb_node_t *n; + + n = stringify->nodes; + + if (n == NULL) { + str->length = 0; + str->start = NULL; + return NXT_OK; + } + + if (n->next == NULL) { + str->length = njs_json_buf_node_size(n); + str->start = n->start; + return NXT_OK; + } + + size = 0; + + while (n != NULL) { + size += njs_json_buf_node_size(n); + n = n->next; + } + + start = nxt_mem_cache_alloc(stringify->pool, size); + if (nxt_slow_path(start == NULL)) { + return NXT_ERROR; + } + + n = stringify->nodes; + str->length = size; + str->start = start; + + while (n != NULL) { + size = njs_json_buf_node_size(n); + memcpy(start, n->start, size); + start += size; + n = n->next; + } + + return NXT_OK; +} + + +static const njs_object_prop_t njs_json_object_properties[] = +{ + /* JSON.parse(). */ + { + .type = NJS_METHOD, + .name = njs_string("parse"), + .value = njs_native_function(njs_json_parse, + njs_continuation_size(njs_json_parse_t), + NJS_SKIP_ARG, NJS_STRING_ARG, + NJS_OBJECT_ARG), + }, + + /* JSON.stringify(). */ + { + .type = NJS_METHOD, + .name = njs_string("stringify"), + .value = njs_native_function(njs_json_stringify, + njs_continuation_size(njs_json_stringify_t), + NJS_SKIP_ARG, NJS_SKIP_ARG, NJS_SKIP_ARG, + NJS_SKIP_ARG), + }, +}; + + +const njs_object_init_t njs_json_object_init = { + nxt_string("JSON"), + njs_json_object_properties, + nxt_nitems(njs_json_object_properties), +}; diff --git a/njs/njs_json.h b/njs/njs_json.h new file mode 100644 index 00000000..179bd151 --- /dev/null +++ b/njs/njs_json.h @@ -0,0 +1,14 @@ + +/* + * Copyright (C) Dmitry Volyntsev + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NJS_JSON_H_INCLUDED_ +#define _NJS_JSON_H_INCLUDED_ + + +extern const njs_object_init_t njs_json_object_init; + + +#endif /* _NJS_JSON_H_INCLUDED_ */ diff --git a/njs/njs_lexer_keyword.c b/njs/njs_lexer_keyword.c index a6f4313a..9bba043a 100644 --- a/njs/njs_lexer_keyword.c +++ b/njs/njs_lexer_keyword.c @@ -69,6 +69,7 @@ static const njs_keyword_t njs_keywords[] = { { nxt_string("this"), NJS_TOKEN_THIS, 0 }, { nxt_string("Math"), NJS_TOKEN_MATH, 0 }, + { nxt_string("JSON"), NJS_TOKEN_JSON, 0 }, /* Builtin functions. */ diff --git a/njs/njs_number.c b/njs/njs_number.c index 4e19d7de..bd731699 100644 --- a/njs/njs_number.c +++ b/njs/njs_number.c @@ -257,9 +257,8 @@ njs_ret_t njs_number_to_string(njs_vm_t *vm, njs_value_t *string, const njs_value_t *number) { - double n, num; + double num; size_t size; - const char *fmt; const njs_value_t *value; u_char buf[128]; @@ -278,32 +277,42 @@ njs_number_to_string(njs_vm_t *vm, njs_value_t *string, } } else { - n = fabs(num); + size = njs_num_to_buf(num, buf, sizeof(buf)); - if (n == 0) { - fmt = "%g"; + return njs_string_new(vm, string, buf, size, size); + } - } else if (n < 1) { - fmt = "%f"; + *string = *value; - } else if (n < 1000000) { - fmt = "%g"; + return NXT_OK; +} - } else if (n < 1e20) { - fmt = "%1.f"; - } else { - fmt = "%1.e"; - } +size_t +njs_num_to_buf(double num, u_char *buf, size_t size) +{ + double n; + const char *fmt; - size = snprintf((char *) buf, sizeof(buf), fmt, num); + n = fabs(num); - return njs_string_new(vm, string, buf, size, size); - } + if (n == 0) { + fmt = "%g"; - *string = *value; + } else if (n < 1) { + fmt = "%f"; - return NXT_OK; + } else if (n < 1000000) { + fmt = "%g"; + + } else if (n < 1e20) { + fmt = "%1.f"; + + } else { + fmt = "%1.e"; + } + + return snprintf((char *) buf, size, fmt, num); } diff --git a/njs/njs_number.h b/njs/njs_number.h index ee3c3b5e..3fabdcab 100644 --- a/njs/njs_number.h +++ b/njs/njs_number.h @@ -17,6 +17,7 @@ uint64_t njs_number_hex_parse(u_char **start, u_char *end); int64_t njs_number_radix_parse(u_char **start, u_char *end, uint8_t radix); njs_ret_t njs_number_to_string(njs_vm_t *vm, njs_value_t *string, const njs_value_t *number); +size_t njs_num_to_buf(double num, u_char *buf, size_t size); njs_ret_t njs_number_constructor(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused); njs_ret_t njs_number_global_is_nan(njs_vm_t *vm, njs_value_t *args, diff --git a/njs/njs_object.c b/njs/njs_object.c index 2cb03a2c..bb6e84c2 100644 --- a/njs/njs_object.c +++ b/njs/njs_object.c @@ -320,6 +320,29 @@ njs_object_create(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, static njs_ret_t njs_object_keys(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_index_t unused) +{ + njs_array_t *keys; + + if (nargs < 2 || !njs_is_object(&args[1])) { + vm->exception = &njs_exception_type_error; + return NXT_ERROR; + } + + keys = njs_object_keys_array(vm, &args[1]); + if (keys == NULL) { + vm->exception = &njs_exception_memory_error; + return NXT_ERROR; + } + + vm->retval.data.u.array = keys; + vm->retval.type = NJS_ARRAY; + vm->retval.data.truth = 1; + + return NXT_OK; +} + +njs_array_t* +njs_object_keys_array(njs_vm_t *vm, njs_value_t *object) { size_t size; uint32_t i, n, keys_length, array_length; @@ -329,17 +352,12 @@ njs_object_keys(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, njs_object_prop_t *prop; nxt_lvlhsh_each_t lhe; - if (nargs < 2 || !njs_is_object(&args[1])) { - vm->exception = &njs_exception_type_error; - return NXT_ERROR; - } - array = NULL; keys_length = 0; array_length = 0; - if (njs_is_array(&args[1])) { - array = args[1].data.u.array; + if (njs_is_array(object)) { + array = object->data.u.array; array_length = array->length; for (i = 0; i < array_length; i++) { @@ -351,7 +369,7 @@ njs_object_keys(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, nxt_lvlhsh_each_init(&lhe, &njs_object_hash_proto); - hash = &args[1].data.u.object->hash; + hash = &object->data.u.object->hash; for ( ;; ) { prop = nxt_lvlhsh_each(hash, &lhe); @@ -367,7 +385,7 @@ njs_object_keys(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, keys = njs_array_alloc(vm, keys_length, NJS_ARRAY_SPARE); if (nxt_slow_path(keys == NULL)) { - return NXT_ERROR; + return NULL; } n = 0; @@ -399,11 +417,7 @@ njs_object_keys(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, } } - vm->retval.data.u.array = keys; - vm->retval.type = NJS_ARRAY; - vm->retval.data.truth = 1; - - return NXT_OK; + return keys; } diff --git a/njs/njs_object.h b/njs/njs_object.h index bee87d3e..14a57d50 100644 --- a/njs/njs_object.h +++ b/njs/njs_object.h @@ -42,6 +42,7 @@ njs_object_t *njs_object_alloc(njs_vm_t *vm); njs_object_t *njs_object_value_copy(njs_vm_t *vm, njs_value_t *value); njs_object_t *njs_object_value_alloc(njs_vm_t *vm, const njs_value_t *value, nxt_uint_t type); +njs_array_t *njs_object_keys_array(njs_vm_t *vm, njs_value_t *object); njs_object_prop_t *njs_object_property(njs_vm_t *vm, njs_object_t *obj, nxt_lvlhsh_query_t *lhq); nxt_int_t njs_object_hash_create(njs_vm_t *vm, nxt_lvlhsh_t *hash, @@ -63,5 +64,4 @@ njs_ret_t njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args, extern const njs_object_init_t njs_object_constructor_init; extern const njs_object_init_t njs_object_prototype_init; - #endif /* _NJS_OBJECT_H_INCLUDED_ */ diff --git a/njs/njs_object_hash.h b/njs/njs_object_hash.h index 3a9edd01..ba8f6c9b 100644 --- a/njs/njs_object_hash.h +++ b/njs/njs_object_hash.h @@ -92,6 +92,16 @@ 'p'), 'r'), 'o'), 't'), 'o'), 't'), 'y'), 'p'), 'e') +#define NJS_TO_JSON_HASH \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add( \ + nxt_djb_hash_add(NXT_DJB_HASH_INIT, \ + 't'), 'o'), 'J'), 'S'), 'O'), 'N') + + #define NJS_TO_STRING_HASH \ nxt_djb_hash_add( \ nxt_djb_hash_add( \ diff --git a/njs/njs_parser.c b/njs/njs_parser.c index f2b06a07..4b99dd80 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -1989,6 +1989,7 @@ njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token) /* Fall through. */ case NJS_TOKEN_MATH: + case NJS_TOKEN_JSON: return njs_parser_builtin_object(vm, parser, node); case NJS_TOKEN_OBJECT_CONSTRUCTOR: diff --git a/njs/njs_parser.h b/njs/njs_parser.h index 13dfbda8..f39fa419 100644 --- a/njs/njs_parser.h +++ b/njs/njs_parser.h @@ -166,6 +166,7 @@ typedef enum { NJS_TOKEN_GLOBAL_THIS, NJS_TOKEN_MATH, + NJS_TOKEN_JSON, NJS_TOKEN_OBJECT_CONSTRUCTOR, NJS_TOKEN_ARRAY_CONSTRUCTOR, diff --git a/njs/njs_vm.h b/njs/njs_vm.h index 0f74dde9..79a6f122 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -797,7 +797,8 @@ enum njs_constructor_e { enum njs_object_e { NJS_OBJECT_THIS = 0, NJS_OBJECT_MATH, -#define NJS_OBJECT_MAX (NJS_OBJECT_MATH + 1) + NJS_OBJECT_JSON, +#define NJS_OBJECT_MAX (NJS_OBJECT_JSON + 1) }; diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index ec07deaa..69caedd4 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -7837,6 +7837,595 @@ static njs_unit_test_t njs_test[] = { nxt_string("parseFloat('-5.7e+abc')"), nxt_string("-5.7") }, + /* JSON.parse() */ + + { nxt_string("JSON.parse('null')"), + nxt_string("null") }, + + { nxt_string("JSON.parse('true')"), + nxt_string("true") }, + + { nxt_string("JSON.parse('false')"), + nxt_string("false") }, + + { nxt_string("JSON.parse('0')"), + nxt_string("0") }, + + { nxt_string("JSON.parse('-1234.56e2')"), + nxt_string("-123456") }, + + { nxt_string("typeof(JSON.parse('true'))"), + nxt_string("boolean") }, + + { nxt_string("typeof(JSON.parse('false'))"), + nxt_string("boolean") }, + + { nxt_string("typeof(JSON.parse('1'))"), + nxt_string("number") }, + + { nxt_string("typeof(JSON.parse('\"\"'))"), + nxt_string("string") }, + + { nxt_string("typeof(JSON.parse('{}'))"), + nxt_string("object") }, + + { nxt_string("typeof(JSON.parse('[]'))"), + nxt_string("object") }, + + { nxt_string("JSON.parse('\"abc\"')"), + nxt_string("abc") }, + + { nxt_string("JSON.parse('\"\\\\\"\"')"), + nxt_string("\"") }, + + { nxt_string("JSON.parse('\"\\\\n\"')"), + nxt_string("\n") }, + + { nxt_string("JSON.parse('\"\\\\t\"')"), + nxt_string("\t") }, + + { nxt_string("JSON.parse('\"ab\\\\\"c\"')"), + nxt_string("ab\"c") }, + + { nxt_string("JSON.parse('\"abcdefghijklmopqr\\\\\"s\"')"), + nxt_string("abcdefghijklmopqr\"s") }, + + { nxt_string("JSON.parse('\"ab\\\\\"c\"').length"), + nxt_string("4") }, + + { nxt_string("JSON.parse('\"аб\\\\\"в\"')"), + nxt_string("аб\"в") }, + + { nxt_string("JSON.parse('\"аб\\\\\"в\"').length"), + nxt_string("4") }, + + { nxt_string("JSON.parse('\"абвгдеёжзийкл\"').length"), + nxt_string("13") }, + + { nxt_string("JSON.parse('\"абвгдеёжзийкл\"').length"), + nxt_string("13") }, + + { nxt_string("JSON.parse('\"\\\\u03B1\"')"), + nxt_string("α") }, + + { nxt_string("JSON.parse('\"\\\\uD801\\\\uDC00\"')"), + nxt_string("𐐀") }, + + { nxt_string("JSON.parse('\"\\\\u03B1\"') == JSON.parse('\"\\\\u03b1\"')"), + nxt_string("true") }, + + { nxt_string("JSON.parse('\"\\\\u03B1\"').length"), + nxt_string("1") }, + + { nxt_string("JSON.parse('{\"a\":1}').a"), + nxt_string("1") }, + + { nxt_string("JSON.parse('{\"a\":1,\"a\":2}').a"), + nxt_string("2") }, + + { nxt_string("JSON.parse('{ \"a\" : \"b\" }').a"), + nxt_string("b") }, + + { nxt_string("JSON.parse('{\"a\":{\"b\":1}}').a.b"), + nxt_string("1") }, + + { nxt_string("JSON.parse('[{}, true ,1.1e2, {\"a\":[3,\"b\"]}]')[3].a[1]"), + nxt_string("b") }, + + { nxt_string("var o = JSON.parse('{\"a\":2}');" + "Object.getOwnPropertyDescriptor(o, 'a').configurable"), + nxt_string("true") }, + + { nxt_string("var o = JSON.parse('{\"a\":2}');" + "Object.getOwnPropertyDescriptor(o, 'a').writable"), + nxt_string("true") }, + + { nxt_string("var o = JSON.parse('{\"a\":2}');" + "Object.getOwnPropertyDescriptor(o, 'a').enumerable"), + nxt_string("true") }, + + { nxt_string("var o = JSON.parse('{\"a\":2}');" + "o.a = 3; o.a"), + nxt_string("3") }, + + { nxt_string("var o = JSON.parse('{\"a\":2}');" + "o.b = 3; o.b"), + nxt_string("3") }, + + { nxt_string("var o = JSON.parse('{}', function(k, v) {return v;}); o"), + nxt_string("[object Object]") }, + + { nxt_string("var o = JSON.parse('{\"a\":2, \"b\":4, \"a\":{}}'," + " function(k, v) {return undefined;});" + "o"), + nxt_string("undefined") }, + + { nxt_string("var o = JSON.parse('{\"a\":2, \"c\":4, \"b\":\"x\"}'," + " function(k, v) {if (k === '' || typeof v === 'number') return v });" + "Object.keys(o)"), + nxt_string("a,c") }, + + { nxt_string("var o = JSON.parse('{\"a\":2, \"b\":{}}'," + " function(k, v) {return k;});" + "o+typeof(o)"), + nxt_string("string") }, + + { nxt_string("var o = JSON.parse('[\"a\", \"b\"]'," + " function(k, v) {return v;});" + "o"), + nxt_string("a,b") }, + + { nxt_string("var o = JSON.parse('{\"a\":[1,{\"b\":1},3]}'," + " function(k, v) {return v;});" + "o.a[1].b"), + nxt_string("1") }, + + { nxt_string("var o = JSON.parse('{\"a\":[1,2]}'," + " function(k, v) {if (k === '' || k === 'a') {return v;}});" + "o.a"), + nxt_string(",") }, + + { nxt_string("var o = JSON.parse('{\"a\":[1,2]}'," + " function(k, v) {return (k === '' || k === 'a') ? v : v*2});" + "o.a"), + nxt_string("2,4") }, + + { nxt_string("var o = JSON.parse('{\"a\":2, \"b\":{\"c\":[\"xx\"]}}'," + " function(k, v) {return typeof v === 'number' ? v * 2 : v;});" + "o.a+o.b.c[0]"), + nxt_string("4xx") }, + + { nxt_string("var o = JSON.parse('{\"aa\":{\"b\":1}, \"abb\":1, \"c\":1}'," + " function(k, v) {return (k === '' || /^a/.test(k)) ? v : undefined;});" + "Object.keys(o)"), + nxt_string("aa,abb") }, + + { nxt_string("var o = JSON.parse('{\"a\":\"x\"}'," + " function(k, v) {if (k === 'a') {this.b='y';} return v});" + "o.a+o.b"), + nxt_string("xy") }, + + { nxt_string("var o = JSON.parse('{\"a\":\"x\"}'," + " function(k, v) {return (k === 'a' ? {x:1} : v)});" + "o.a.x"), + nxt_string("1") }, + + { nxt_string("var keys = []; var o = JSON.parse('{\"a\":2, \"b\":{\"c\":\"xx\"}}'," + " function(k, v) {keys.push(k); return v;});" + "keys"), + nxt_string("a,c,b,") }, + + { nxt_string("var args = []; var o = JSON.parse('[2,{\"a\":3}]'," + " function(k, v) {args.push(k+\":\"+v); return v;});" + "args.join('|')"), + nxt_string("0:2|a:3|1:[object Object]|:2,[object Object]") }, + + { nxt_string("JSON.parse()"), + nxt_string("SyntaxError: Unexpected token at position 0") }, + + { nxt_string("JSON.parse([])"), + nxt_string("SyntaxError: Unexpected end of input at position 0") }, + + { nxt_string("JSON.parse('')"), + nxt_string("SyntaxError: Unexpected end of input at position 0") }, + + { nxt_string("JSON.parse('fals')"), + nxt_string("SyntaxError: Unexpected token at position 0") }, + + { nxt_string("JSON.parse(' t')"), + nxt_string("SyntaxError: Unexpected token at position 1") }, + + { nxt_string("JSON.parse('nu')"), + nxt_string("SyntaxError: Unexpected token at position 0") }, + + { nxt_string("JSON.parse('-')"), + nxt_string("SyntaxError: Unexpected number at position 0") }, + + { nxt_string("JSON.parse('--')"), + nxt_string("SyntaxError: Unexpected number at position 1") }, + + { nxt_string("JSON.parse('1-')"), + nxt_string("SyntaxError: Unexpected token at position 1") }, + + { nxt_string("JSON.parse('1ee1')"), + nxt_string("SyntaxError: Unexpected token at position 1") }, + + { nxt_string("JSON.parse('1eg')"), + nxt_string("SyntaxError: Unexpected token at position 1") }, + + { nxt_string("JSON.parse('0x01')"), + nxt_string("SyntaxError: Unexpected token at position 1") }, + + { nxt_string("JSON.parse('\"абв')"), + nxt_string("SyntaxError: Unexpected end of input at position 4") }, + + { nxt_string("JSON.parse('\"\b')"), + nxt_string("SyntaxError: Forbidden source char at position 1") }, + + { nxt_string("JSON.parse('\"\\\\u')"), + nxt_string("SyntaxError: Unexpected end of input at position 3") }, + + { nxt_string("JSON.parse('\"\\\\q\"')"), + nxt_string("SyntaxError: Unknown escape char at position 2") }, + + { nxt_string("JSON.parse('\"\\\\uDC01\"')"), + nxt_string("SyntaxError: Invalid Unicode char at position 7") }, + + { nxt_string("JSON.parse('\"\\\\uD801\\\\uE000\"')"), + nxt_string("SyntaxError: Invalid surrogate pair at position 13") }, + + { nxt_string("JSON.parse('{')"), + nxt_string("SyntaxError: Unexpected end of input at position 1") }, + + { nxt_string("JSON.parse('{{')"), + nxt_string("SyntaxError: Unexpected token at position 1") }, + + { nxt_string("JSON.parse('{[')"), + nxt_string("SyntaxError: Unexpected token at position 1") }, + + { nxt_string("JSON.parse('{\"a\"')"), + nxt_string("SyntaxError: Unexpected token at position 4") }, + + { nxt_string("JSON.parse('{\"a\":')"), + nxt_string("SyntaxError: Unexpected end of input at position 5") }, + + { nxt_string("JSON.parse('{\"a\":{')"), + nxt_string("SyntaxError: Unexpected end of input at position 6") }, + + { nxt_string("JSON.parse('{\"a\":{}')"), + nxt_string("SyntaxError: Unexpected end of input at position 7") }, + + { nxt_string("JSON.parse('{\"a\":{}g')"), + nxt_string("SyntaxError: Unexpected token at position 7") }, + + { nxt_string("JSON.parse('{\"a\":{},')"), + nxt_string("SyntaxError: Unexpected end of input at position 8") }, + + { nxt_string("JSON.parse('{\"a\":{},}')"), + nxt_string("SyntaxError: Trailing comma at position 7") }, + + { nxt_string("JSON.parse('{\"a\":{},,')"), + nxt_string("SyntaxError: Unexpected token at position 8") }, + + { nxt_string("JSON.parse('{\"a\":{},,}')"), + nxt_string("SyntaxError: Unexpected token at position 8") }, + + { nxt_string("JSON.parse('[')"), + nxt_string("SyntaxError: Unexpected end of input at position 1") }, + + { nxt_string("JSON.parse('[q')"), + nxt_string("SyntaxError: Unexpected token at position 1") }, + + { nxt_string("JSON.parse('[\"a')"), + nxt_string("SyntaxError: Unexpected end of input at position 3") }, + + { nxt_string("JSON.parse('[1 ')"), + nxt_string("SyntaxError: Unexpected end of input at position 3") }, + + { nxt_string("JSON.parse('[1,]')"), + nxt_string("SyntaxError: Trailing comma at position 2") }, + + { nxt_string("JSON.parse('[1 , 5 ')"), + nxt_string("SyntaxError: Unexpected end of input at position 7") }, + + { nxt_string("JSON.parse('{\"a\":'.repeat(32))"), + nxt_string("SyntaxError: Nested too deep at position 155") }, + + { nxt_string("JSON.parse('['.repeat(32))"), + nxt_string("SyntaxError: Nested too deep at position 31") }, + + { nxt_string("var o = JSON.parse('{', function(k, v) {return v;});o"), + nxt_string("SyntaxError: Unexpected end of input at position 1") }, + + { nxt_string("var o = JSON.parse('{\"a\":1}', " + " function(k, v) {return v.a.a;}); o"), + nxt_string("TypeError") }, + + /* JSON.stringify() */ + + { nxt_string("JSON.stringify()"), + nxt_string("undefined") }, + + { nxt_string("JSON.stringify('')"), + nxt_string("\"\"") }, + + { nxt_string("JSON.stringify('abc')"), + nxt_string("\"abc\"") }, + + { nxt_string("JSON.stringify(new String('abc'))"), + nxt_string("\"abc\"") }, + + { nxt_string("JSON.stringify(123)"), + nxt_string("123") }, + + { nxt_string("JSON.stringify(new Number(123))"), + nxt_string("123") }, + + { nxt_string("JSON.stringify(true)"), + nxt_string("true") }, + + { nxt_string("JSON.stringify(false)"), + nxt_string("false") }, + + { nxt_string("JSON.stringify(new Boolean(1))"), + nxt_string("true") }, + + { nxt_string("JSON.stringify(new Boolean(0))"), + nxt_string("false") }, + + { nxt_string("JSON.stringify(null)"), + nxt_string("null") }, + + { nxt_string("JSON.stringify(undefined)"), + nxt_string("undefined") }, + + { nxt_string("JSON.stringify({})"), + nxt_string("{}") }, + + { nxt_string("JSON.stringify([])"), + nxt_string("[]") }, + + { nxt_string("JSON.stringify({a:\"b\",c:19,e:null,t:true,f:false})"), + nxt_string("{\"a\":\"b\",\"c\":19,\"e\":null,\"t\":true,\"f\":false}") }, + + { nxt_string("JSON.stringify({a:1, b:undefined})"), + nxt_string("{\"a\":1}") }, + + { nxt_string("var o = {a:1, c:2};" + "Object.defineProperty(o, 'b', {enumerable:false, value:3});" + "JSON.stringify(o)"), + nxt_string("{\"a\":1,\"c\":2}") }, + + { nxt_string("JSON.stringify({a:{}, b:[function(v){}]})"), + nxt_string("{\"a\":{},\"b\":[null]}") }, + + /* Ignoring named properties of an array. */ + + { nxt_string("var a = [1,2]; a.a = 1;" + "JSON.stringify(a)"), + nxt_string("[1,2]") }, + + { nxt_string("JSON.stringify({a:{b:{c:{d:1}, e:function(v){}}}})"), + nxt_string("{\"a\":{\"b\":{\"c\":{\"d\":1}}}}") }, + + { nxt_string("JSON.stringify([[\"b\",undefined],1,[5],{a:1}])"), + nxt_string("[[\"b\",null],1,[5],{\"a\":1}]") }, + + { nxt_string("var json = '{\"a\":{\"b\":{\"c\":{\"d\":1},\"e\":[true]}}}';" + "json == JSON.stringify(JSON.parse(json))"), + nxt_string("true") }, + + { nxt_string("var json = '{\"a\":\"абв\",\"b\":\"α\"}';" + "json == JSON.stringify(JSON.parse(json))"), + nxt_string("true") }, + + /* Multibyte characters: z - 1 byte, α - 2 bytes, 𐐀 - 4 bytes */ + + { nxt_string("JSON.stringify('α𐐀z'.repeat(10))"), + nxt_string("\"α𐐀zα𐐀zα𐐀zα𐐀zα𐐀zα𐐀zα𐐀zα𐐀zα𐐀zα𐐀z\"") }, + + { nxt_string("JSON.stringify('α𐐀z'.repeat(10)).length"), + nxt_string("32") }, + + { nxt_string("JSON.stringify('a\nbc')"), + nxt_string("\"a\\nbc\"") }, + + { nxt_string("JSON.stringify('а\tбв')"), + nxt_string("\"а\\tбв\"") }, + + { nxt_string("JSON.stringify('\n\t\r\"\f\b ')"), + nxt_string("\"\\n\\t\\r\\\"\\f\\b \"") }, + + { nxt_string("JSON.stringify('\x00\x01\x02\x1f')"), + nxt_string("\"\\u0000\\u0001\\u0002\\u001F\"") }, + + { nxt_string("JSON.stringify('abc\x00')"), + nxt_string("\"abc\\u0000\"") }, + + { nxt_string("JSON.stringify('\x00zz')"), + nxt_string("\"\\u0000zz\"") }, + + { nxt_string("JSON.stringify('\x00')"), + nxt_string("\"\\u0000\"") }, + + { nxt_string("JSON.stringify('a\x00z')"), + nxt_string("\"a\\u0000z\"") }, + + { nxt_string("JSON.stringify('\x00z\x00')"), + nxt_string("\"\\u0000z\\u0000\"") }, + + { nxt_string("var i, s, r = true;" + " for (i = 0; i < 128; i++) {" + " s = 'α𐐀z'.repeat(i);" + " r &= (JSON.stringify(s) == ('\"' + s + '\"'));" + "}; r"), + nxt_string("1") }, + + { nxt_string("JSON.stringify('\\u0000'.repeat(10)) == ('\"' + '\\\\u0000'.repeat(10) + '\"')"), + nxt_string("true") }, + + { nxt_string("JSON.stringify('abc'.repeat(100)).length"), + nxt_string("302") }, + + { nxt_string("JSON.stringify('абв'.repeat(100)).length"), + nxt_string("302") }, + + /* Byte strings. */ + + { nxt_string("JSON.stringify('\\u00CE\\u00B1\\u00C2\\u00B6'.toBytes())"), + nxt_string("\"α¶\"") }, + + { nxt_string("JSON.stringify('µ§±®'.toBytes())"), + nxt_string("\"\xB5\xA7\xB1\xAE\"") }, + + /* Optional arguments. */ + + { nxt_string("JSON.stringify(undefined, undefined, 1)"), + nxt_string("undefined") }, + + { nxt_string("JSON.stringify([{a:1,b:{c:2}},1], undefined, 0)"), + nxt_string("[{\"a\":1,\"b\":{\"c\":2}},1]") }, + + { nxt_string("JSON.stringify([{a:1,b:{c:2}},1], undefined, 1)"), + nxt_string("[\n {\n \"a\": 1,\n \"b\": {\n \"c\": 2\n }\n },\n 1\n]") }, + + { nxt_string("JSON.stringify([{a:1,b:{c:2}},1], undefined, ' ')"), + nxt_string("[\n {\n \"a\": 1,\n \"b\": {\n \"c\": 2\n }\n },\n 1\n]") }, + + { nxt_string("JSON.stringify([{a:1,b:{c:2}},1], undefined, '#')"), + nxt_string("[\n#{\n##\"a\": 1,\n##\"b\": {\n###\"c\": 2\n##}\n#},\n#1\n]") }, + + { nxt_string("JSON.stringify([1], undefined, 'AAAAABBBBBC')"), + nxt_string("[\nAAAAABBBBB1\n]") }, + + { nxt_string("JSON.stringify([1], undefined, 11)"), + nxt_string("[\n 1\n]") }, + + { nxt_string("JSON.stringify([{a:1,b:{c:2}},1], undefined, -1)"), + nxt_string("[{\"a\":1,\"b\":{\"c\":2}},1]") }, + + { nxt_string("JSON.stringify([{a:1,b:{c:2}},1], undefined, new Date())"), + nxt_string("[{\"a\":1,\"b\":{\"c\":2}},1]") }, + + { nxt_string("JSON.stringify({toJSON:function(k){}})"), + nxt_string("undefined") }, + + { nxt_string("JSON.stringify({toJSON:function(k){return k}})"), + nxt_string("\"\"") }, + + { nxt_string("JSON.stringify(new Date(1308895323625))"), + nxt_string("\"2011-06-24T06:02:03.625Z\"") }, + + { nxt_string("JSON.stringify({a:new Date(1308895323625)})"), + nxt_string("{\"a\":\"2011-06-24T06:02:03.625Z\"}") }, + + { nxt_string("JSON.stringify({b:{toJSON:function(k){return undefined}}})"), + nxt_string("{}") }, + + { nxt_string("JSON.stringify({b:{toJSON:function(k){}},c:1})"), + nxt_string("{\"c\":1}") }, + + { nxt_string("JSON.stringify({b:{toJSON:function(k){return k}}})"), + nxt_string("{\"b\":\"b\"}") }, + + { nxt_string("JSON.stringify({a:1,b:new Date(1308895323625),c:2})"), + nxt_string("{\"a\":1,\"b\":\"2011-06-24T06:02:03.625Z\",\"c\":2}") }, + + { nxt_string("JSON.stringify({a:{b:new Date(1308895323625)}})"), + nxt_string("{\"a\":{\"b\":\"2011-06-24T06:02:03.625Z\"}}") }, + + { nxt_string("function key(k){return k}; function und(k){}" + "JSON.stringify([{toJSON:key},{toJSON:und},{toJSON:key}])"), + nxt_string("[\"0\",null,\"2\"]") }, + + { nxt_string("JSON.stringify({b:{a:1,c:[2]}}, function(k,v){return v})"), + nxt_string("{\"b\":{\"a\":1,\"c\":[2]}}") }, + + { nxt_string("JSON.stringify([{a:1}, 2], function(k,v){return v})"), + nxt_string("[{\"a\":1},2]") }, + + { nxt_string("JSON.stringify({a:{toJSON:function(k){}}}, function(k,v){return v})"), + nxt_string("{}") }, + + { nxt_string("JSON.stringify({a:{toJSON:function(k){return 1}}}, function(k,v){return v})"), + nxt_string("{\"a\":1}") }, + + { nxt_string("JSON.stringify([{toJSON:function(k){}}], function(k,v){return v})"), + nxt_string("[null]") }, + + { nxt_string("JSON.stringify([{toJSON:function(k){return 1}}], function(k,v){return v})"), + nxt_string("[1]") }, + + { nxt_string("JSON.stringify({a:new Date(1308895323625)}, function(k,v){return v})"), + nxt_string("{\"a\":\"2011-06-24T06:02:03.625Z\"}") }, + + { nxt_string("JSON.stringify([new Date(1308895323625)], function(k,v){return v})"), + nxt_string("[\"2011-06-24T06:02:03.625Z\"]") }, + + { nxt_string("JSON.stringify([new Date(1308895323625)], " + " function(k,v){return (typeof v === 'string') ? v.toLowerCase() : v})"), + nxt_string("[\"2011-06-24t06:02:03.625z\"]") }, + + { nxt_string("JSON.stringify([new Date(1308895323625)], " + " function(k,v){return (typeof v === 'string') ? v.toLowerCase() : v}, '#')"), + nxt_string("[\n#\"2011-06-24t06:02:03.625z\"\n]") }, + + { nxt_string("JSON.stringify({a:new Date(1308895323625),b:1,c:'a'}, " + " function(k,v){return (typeof v === 'string') ? undefined : v})"), + nxt_string("{\"b\":1}") }, + + { nxt_string("JSON.stringify({a:new Date(1308895323625),b:1,c:'a'}, " + " function(k,v){return (typeof v === 'string') ? undefined : v}, '#')"), + nxt_string("{\n#\"b\": 1\n}") }, + + { nxt_string("JSON.stringify([new Date(1308895323625),1,'a'], " + " function(k,v){return (typeof v === 'string') ? undefined : v})"), + nxt_string("[null,1,null]") }, + + { nxt_string("var keys = []; var o = JSON.stringify({a:2, b:{c:1}}," + " function(k, v) {keys.push(k); return v;});" + "keys"), + nxt_string(",a,b,c") }, + + { nxt_string("JSON.stringify(['a', 'b', 'c'], " + " function(i, v) { if (i === '0') {return undefined} " + " else if (i == 1) {return 2} " + " else {return v}})"), + nxt_string("[null,2,\"c\"]") }, + + { nxt_string("JSON.stringify({a:2, b:{c:1}}," + " function(k, v) {delete this['b']; return v;})"), + nxt_string("{\"a\":2}") }, + + { nxt_string("JSON.stringify(JSON.parse('{\"a\":1,\"b\":2}', " + " function(k, v) {delete this['b']; return v;}))"), + nxt_string("{\"a\":1}") }, + + { nxt_string("var keys = []; var o = JSON.stringify([[1,2],{a:3}, 4]," + " function(k, v) {keys.push(k); return v;});" + "keys"), + nxt_string(",0,0,1,1,a,2") }, + + { nxt_string("JSON.stringify({b:{a:1,c:[2]}}, ['a', undefined, 'b', {}, 'a'])"), + nxt_string("{\"b\":{\"a\":1}}") }, + + { nxt_string("JSON.stringify({b:{a:1,c:[2]}}, [new String('a'), new String('b')])"), + nxt_string("{\"b\":{\"a\":1}}") }, + + { nxt_string("JSON.stringify({'1':1,'2':2,'3':3}, [1, new Number(2)])"), + nxt_string("{\"1\":1,\"2\":2}") }, + + { nxt_string("var objs = []; var o = JSON.stringify({a:1}," + " function(k, v) {objs.push(this); return v});" + "JSON.stringify(objs)"), + nxt_string("[{\"\":{\"a\":1}},{\"a\":1}]") }, + + { nxt_string("var a = []; a[0] = a; JSON.stringify(a)"), + nxt_string("TypeError: Nested too deep or a cyclic structure") }, + + { nxt_string("var a = {}; a.a = a; JSON.stringify(a)"), + nxt_string("TypeError: Nested too deep or a cyclic structure") }, + /* Trick: number to boolean. */ { nxt_string("var a = 0; !!a"), diff --git a/nxt/nxt_utf8.h b/nxt/nxt_utf8.h index b6305bfc..8362b40a 100644 --- a/nxt/nxt_utf8.h +++ b/nxt/nxt_utf8.h @@ -76,6 +76,36 @@ nxt_utf8_prev(const u_char *p) } +nxt_inline u_char * +nxt_utf8_copy(u_char *dst, const u_char **src, const u_char *end) +{ + u_char c; + const u_char *p; + + p = *src; + c = *p++; + *dst++ = c; + + if ((c & 0x80) != 0) { + + do { + c = *p; + + if ((c & 0xC0) != 0x80) { + break; + } + + *dst++ = c; + p++; + + } while (p < end); + } + + *src = p; + return dst; +} + + #define nxt_utf8_size(u) \ ((u < 0x80) ? 1 : ((u < 0x0800) ? 2 : ((u < 0x10000) ? 3 : 4)))