Created
September 13, 2018 14:37
-
-
Save xeioex/d8a5c34c20853902ea0381b1097e8b82 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # HG changeset patch | |
| # User Dmitry Volyntsev <[email protected]> | |
| # Date 1536843137 -10800 | |
| # Thu Sep 13 15:52:17 2018 +0300 | |
| # Node ID a92ae4986bc04c512d180b3e7748027514ce729a | |
| # Parent c2cddf3b97b7874bccf569ea08731120fdf82199 | |
| Introduced sandboxing mode. | |
| Thanks to David CARLIER. | |
| diff --git a/njs/njs.c b/njs/njs.c | |
| --- a/njs/njs.c | |
| +++ b/njs/njs.c | |
| @@ -110,6 +110,8 @@ njs_vm_create(njs_vm_opt_t *options) | |
| return NULL; | |
| } | |
| + vm->options = *options; | |
| + | |
| if (options->shared != NULL) { | |
| vm->shared = options->shared; | |
| @@ -160,15 +162,11 @@ njs_vm_create(njs_vm_opt_t *options) | |
| nxt_lvlhsh_init(&vm->externals_hash); | |
| nxt_lvlhsh_init(&vm->external_prototypes_hash); | |
| - vm->ops = options->ops; | |
| - | |
| vm->trace.level = NXT_LEVEL_TRACE; | |
| vm->trace.size = 2048; | |
| vm->trace.handler = njs_parser_trace_handler; | |
| vm->trace.data = vm; | |
| - vm->trailer = options->trailer; | |
| - | |
| if (options->backtrace) { | |
| debug = nxt_array_create(4, sizeof(njs_function_debug_t), | |
| &njs_array_mem_proto, | |
| @@ -180,8 +178,7 @@ njs_vm_create(njs_vm_opt_t *options) | |
| vm->debug = debug; | |
| } | |
| - vm->accumulative = options->accumulative; | |
| - if (vm->accumulative) { | |
| + if (options->accumulative) { | |
| ret = njs_vm_init(vm); | |
| if (nxt_slow_path(ret != NXT_OK)) { | |
| return NULL; | |
| @@ -232,7 +229,7 @@ njs_vm_compile(njs_vm_t *vm, u_char **st | |
| return NJS_ERROR; | |
| } | |
| - if (vm->parser != NULL && !vm->accumulative) { | |
| + if (vm->parser != NULL && !vm->options.accumulative) { | |
| return NJS_ERROR; | |
| } | |
| @@ -307,7 +304,7 @@ njs_vm_clone(njs_vm_t *vm, njs_external_ | |
| nxt_thread_log_debug("CLONE:"); | |
| - if (vm->accumulative) { | |
| + if (vm->options.accumulative) { | |
| return NULL; | |
| } | |
| @@ -347,7 +344,7 @@ njs_vm_clone(njs_vm_t *vm, njs_external_ | |
| nvm->external_objects = externals; | |
| - nvm->ops = vm->ops; | |
| + nvm->options = vm->options; | |
| nvm->current = vm->current; | |
| diff --git a/njs/njs.h b/njs/njs.h | |
| --- a/njs/njs.h | |
| +++ b/njs/njs.h | |
| @@ -145,6 +145,7 @@ typedef struct { | |
| uint8_t trailer; /* 1 bit */ | |
| uint8_t accumulative; /* 1 bit */ | |
| uint8_t backtrace; /* 1 bit */ | |
| + uint8_t sandbox; /* 1 bit */ | |
| } njs_vm_opt_t; | |
| diff --git a/njs/njs_builtin.c b/njs/njs_builtin.c | |
| --- a/njs/njs_builtin.c | |
| +++ b/njs/njs_builtin.c | |
| @@ -256,6 +256,10 @@ njs_builtin_objects_create(njs_vm_t *vm) | |
| lhq.pool = vm->mem_cache_pool; | |
| for (i = NJS_MODULE_FS; i < NJS_MODULE_MAX; i++) { | |
| + if (vm->options.sandbox && !njs_sandbox_module(i)) { | |
| + continue; | |
| + } | |
| + | |
| module = nxt_mem_cache_zalloc(vm->mem_cache_pool, sizeof(njs_module_t)); | |
| if (nxt_slow_path(module == NULL)) { | |
| return NJS_ERROR; | |
| diff --git a/njs/njs_generator.c b/njs/njs_generator.c | |
| --- a/njs/njs_generator.c | |
| +++ b/njs/njs_generator.c | |
| @@ -2558,7 +2558,7 @@ njs_generator_temp_index_get(njs_vm_t *v | |
| scope = scope->parent; | |
| } | |
| - if (vm->accumulative && scope->type == NJS_SCOPE_GLOBAL) { | |
| + if (vm->options.accumulative && scope->type == NJS_SCOPE_GLOBAL) { | |
| /* | |
| * When non-clonable VM runs in accumulative mode | |
| * all global variables are allocated in absolute scope | |
| diff --git a/njs/njs_module.c b/njs/njs_module.c | |
| --- a/njs/njs_module.c | |
| +++ b/njs/njs_module.c | |
| @@ -74,3 +74,19 @@ const njs_object_init_t njs_require_fun | |
| NULL, | |
| 0, | |
| }; | |
| + | |
| + | |
| +nxt_bool_t | |
| +njs_sandbox_module(enum njs_module_e module) | |
| +{ | |
| + switch (module) { | |
| + case NJS_MODULE_CRYPTO: | |
| + return 1; | |
| + | |
| + case NJS_MODULE_FS: | |
| + default: | |
| + break; | |
| + } | |
| + | |
| + return 0; | |
| +} | |
| diff --git a/njs/njs_module.h b/njs/njs_module.h | |
| --- a/njs/njs_module.h | |
| +++ b/njs/njs_module.h | |
| @@ -15,6 +15,7 @@ typedef struct { | |
| njs_ret_t njs_module_require(njs_vm_t *vm, njs_value_t *args, | |
| nxt_uint_t nargs, njs_index_t unused); | |
| +nxt_bool_t njs_sandbox_module(enum njs_module_e module); | |
| extern const nxt_lvlhsh_proto_t njs_modules_hash_proto; | |
| diff --git a/njs/njs_parser.c b/njs/njs_parser.c | |
| --- a/njs/njs_parser.c | |
| +++ b/njs/njs_parser.c | |
| @@ -134,7 +134,7 @@ njs_parser(njs_vm_t *vm, njs_parser_t *p | |
| return NULL; | |
| } | |
| - if (token == NJS_TOKEN_CLOSE_BRACE && vm->trailer) { | |
| + if (token == NJS_TOKEN_CLOSE_BRACE && vm->options.trailer) { | |
| parser->lexer->start--; | |
| break; | |
| } | |
| @@ -351,7 +351,7 @@ njs_parser_statement(njs_vm_t *vm, njs_p | |
| return njs_parser_block_statement(vm, parser); | |
| case NJS_TOKEN_CLOSE_BRACE: | |
| - if (vm->trailer) { | |
| + if (vm->options.trailer) { | |
| parser->node = NULL; | |
| nxt_thread_log_debug("BLOCK END"); | |
| return token; | |
| diff --git a/njs/njs_shell.c b/njs/njs_shell.c | |
| --- a/njs/njs_shell.c | |
| +++ b/njs/njs_shell.c | |
| @@ -31,6 +31,7 @@ typedef struct { | |
| nxt_int_t version; | |
| nxt_int_t disassemble; | |
| nxt_int_t interactive; | |
| + nxt_int_t sandbox; | |
| } njs_opts_t; | |
| @@ -146,6 +147,7 @@ main(int argc, char **argv) | |
| vm_options.accumulative = 1; | |
| vm_options.backtrace = 1; | |
| + vm_options.sandbox = opts.sandbox; | |
| if (opts.interactive) { | |
| ret = njs_interactive_shell(&opts, &vm_options); | |
| @@ -170,6 +172,7 @@ njs_get_options(njs_opts_t *opts, int ar | |
| "Options:\n" | |
| " -v print njs version and exit.\n" | |
| " -d print disassembled code.\n" | |
| + " -s sandbox mode.\n" | |
| " <filename> | - run code from a file or stdin.\n"; | |
| ret = NXT_DONE; | |
| @@ -201,6 +204,10 @@ njs_get_options(njs_opts_t *opts, int ar | |
| opts->version = 1; | |
| break; | |
| + case 's': | |
| + opts->sandbox = 1; | |
| + break; | |
| + | |
| default: | |
| fprintf(stderr, "Unknown argument: \"%s\" " | |
| "try \"%s -h\" for available options\n", argv[i], argv[0]); | |
| diff --git a/njs/njs_time.c b/njs/njs_time.c | |
| --- a/njs/njs_time.c | |
| +++ b/njs/njs_time.c | |
| @@ -28,7 +28,7 @@ njs_set_timeout(njs_vm_t *vm, njs_value_ | |
| return NJS_ERROR; | |
| } | |
| - ops = vm->ops; | |
| + ops = vm->options.ops; | |
| if (nxt_slow_path(ops == NULL)) { | |
| njs_internal_error(vm, "not supported by host environment"); | |
| return NJS_ERROR; | |
| diff --git a/njs/njs_variable.c b/njs/njs_variable.c | |
| --- a/njs/njs_variable.c | |
| +++ b/njs/njs_variable.c | |
| @@ -123,7 +123,7 @@ njs_variable_add(njs_vm_t *vm, njs_parse | |
| return var; | |
| } | |
| - lhq.replace = vm->accumulative; | |
| + lhq.replace = vm->options.accumulative; | |
| lhq.value = var; | |
| lhq.pool = vm->mem_cache_pool; | |
| @@ -389,7 +389,7 @@ njs_variable_get(njs_vm_t *vm, njs_parse | |
| goto not_found; | |
| } | |
| - if (vm->accumulative && vs.scope->type == NJS_SCOPE_GLOBAL) { | |
| + if (vm->options.accumulative && vs.scope->type == NJS_SCOPE_GLOBAL) { | |
| /* | |
| * When non-clonable VM runs in accumulative mode all | |
| * global variables should be allocated in absolute scope | |
| diff --git a/njs/njs_vm.h b/njs/njs_vm.h | |
| --- a/njs/njs_vm.h | |
| +++ b/njs/njs_vm.h | |
| @@ -1036,7 +1036,7 @@ struct njs_vm_s { | |
| nxt_lvlhsh_t events_hash; | |
| nxt_queue_t posted_events; | |
| - njs_vm_ops_t *ops; | |
| + njs_vm_opt_t options; | |
| /* | |
| * The prototypes and constructors arrays must be together because | |
| @@ -1073,8 +1073,6 @@ struct njs_vm_s { | |
| nxt_array_t *backtrace; | |
| njs_trap_t trap:8; | |
| - uint8_t trailer; /* 1 bit */ | |
| - uint8_t accumulative; /* 1 bit */ | |
| }; | |
| diff --git a/njs/test/njs_expect_test.exp b/njs/test/njs_expect_test.exp | |
| --- a/njs/test/njs_expect_test.exp | |
| +++ b/njs/test/njs_expect_test.exp | |
| @@ -3,8 +3,15 @@ | |
| # Copyright (C) NGINX, Inc. | |
| # | |
| -proc njs_test {body} { | |
| - spawn -nottycopy njs | |
| +proc njs_test {body {opts ""}} { | |
| + | |
| + if {$opts eq ""} { | |
| + spawn -nottycopy njs | |
| + | |
| + } else { | |
| + spawn -nottycopy njs $opts | |
| + } | |
| + | |
| expect -re "interactive njs \\d+\.\\d+\.\\d+\r\n\r" | |
| expect "v.<Tab> -> the properties and prototype methods of v.\r | |
| type console.help() for more information\r | |
| @@ -23,6 +30,12 @@ type console.help() for more information | |
| expect eof | |
| } | |
| +proc njs_run {opts output} { | |
| + spawn -nottycopy njs $opts | |
| + expect -re $output | |
| + expect eof | |
| +} | |
| + | |
| njs_test { | |
| {"njs.version\r\n" | |
| "njs.version\r\n\*\.\*\.\*"} | |
| @@ -490,3 +503,23 @@ njs_test { | |
| {"fs.readFileSync('njs_test_file2')\r\n" | |
| "'ABCABC'\r\n>> "} | |
| } | |
| + | |
| +# CLI OPTIONS | |
| + | |
| +# version | |
| + | |
| +njs_run "-v" "\\d+\.\\d+\.\\d+" | |
| + | |
| +# disassemble | |
| + | |
| +njs_test { | |
| + {"1+1\r\n" | |
| + "00000 ADD*\r\n00040 STOP*\r\n\r\n2"} | |
| +} "-d" | |
| + | |
| +# sandboxing | |
| + | |
| +njs_test { | |
| + {"var fs = require('fs')\r\n" | |
| + "Error: Cannot find module 'fs'\r\n"} | |
| +} "-s" | |
| # HG changeset patch | |
| # User Dmitry Volyntsev <[email protected]> | |
| # Date 1536843358 -10800 | |
| # Thu Sep 13 15:55:58 2018 +0300 | |
| # Node ID 9ed020dadadb4341471af6b65ed5dd1dfef7fc05 | |
| # Parent a92ae4986bc04c512d180b3e7748027514ce729a | |
| Added njs_u32_to_string(). | |
| diff --git a/njs/njs_event.c b/njs/njs_event.c | |
| --- a/njs/njs_event.c | |
| +++ b/njs/njs_event.c | |
| @@ -6,7 +6,6 @@ | |
| #include <njs_core.h> | |
| #include <string.h> | |
| -#include <stdio.h> | |
| static nxt_int_t njs_event_hash_test(nxt_lvlhsh_query_t *lhq, void *data); | |
| @@ -44,13 +43,10 @@ njs_event_hash_test(nxt_lvlhsh_query_t * | |
| nxt_int_t | |
| njs_add_event(njs_vm_t *vm, njs_event_t *event) | |
| { | |
| - size_t size; | |
| nxt_int_t ret; | |
| nxt_lvlhsh_query_t lhq; | |
| - size = snprintf((char *) njs_string_short_start(&event->id), | |
| - NJS_STRING_SHORT, "%u", vm->event_id++); | |
| - njs_string_short_set(&event->id, size, size); | |
| + njs_u32_to_string(&event->id, vm->event_id++); | |
| njs_string_get(&event->id, &lhq.key); | |
| lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); | |
| diff --git a/njs/njs_json.c b/njs/njs_json.c | |
| --- a/njs/njs_json.c | |
| +++ b/njs/njs_json.c | |
| @@ -8,7 +8,6 @@ | |
| #include <njs_json.h> | |
| #include <njs_date.h> | |
| #include <njs_regexp.h> | |
| -#include <stdio.h> | |
| #include <string.h> | |
| @@ -1036,7 +1035,6 @@ memory_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; | |
| @@ -1053,9 +1051,7 @@ njs_json_parse_continuation_apply(njs_vm | |
| 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); | |
| + njs_u32_to_string(&arguments[1], state->index); | |
| arguments[2] = state->value.data.u.array->start[state->index]; | |
| state->type = NJS_JSON_ARRAY_REPLACED; | |
| @@ -1455,7 +1451,6 @@ 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; | |
| @@ -1482,9 +1477,7 @@ njs_json_stringify_to_json(njs_vm_t *vm, | |
| 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); | |
| + njs_u32_to_string(&arguments[1], state->index - 1); | |
| state->type = NJS_JSON_ARRAY_TO_JSON_REPLACED; | |
| break; | |
| @@ -1504,7 +1497,6 @@ 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; | |
| @@ -1526,9 +1518,7 @@ njs_json_stringify_replacer(njs_vm_t *vm | |
| 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); | |
| + njs_u32_to_string(&arguments[1], state->index - 1); | |
| arguments[2] = *value; | |
| state->type = NJS_JSON_ARRAY_REPLACED; | |
| diff --git a/njs/njs_number.h b/njs/njs_number.h | |
| --- a/njs/njs_number.h | |
| +++ b/njs/njs_number.h | |
| @@ -9,6 +9,7 @@ | |
| #include <math.h> | |
| +#include <stdio.h> | |
| uint32_t njs_value_to_index(const njs_value_t *value); | |
| @@ -56,6 +57,17 @@ njs_char_to_hex(u_char c) | |
| } | |
| +nxt_inline void | |
| +njs_u32_to_string(njs_value_t *value, uint32_t u32) | |
| +{ | |
| + size_t size; | |
| + | |
| + size = snprintf((char *) njs_string_short_start(value), | |
| + NJS_STRING_SHORT, "%u", u32); | |
| + njs_string_short_set(value, size, size); | |
| +} | |
| + | |
| + | |
| extern const njs_object_init_t njs_number_constructor_init; | |
| extern const njs_object_init_t njs_number_prototype_init; | |
| diff --git a/njs/njs_object.c b/njs/njs_object.c | |
| --- a/njs/njs_object.c | |
| +++ b/njs/njs_object.c | |
| @@ -5,7 +5,6 @@ | |
| */ | |
| #include <njs_core.h> | |
| -#include <stdio.h> | |
| #include <string.h> | |
| @@ -651,7 +650,6 @@ njs_object_keys(njs_vm_t *vm, njs_value_ | |
| njs_array_t * | |
| njs_object_keys_array(njs_vm_t *vm, const njs_value_t *object) | |
| { | |
| - size_t size; | |
| uint32_t i, n, keys_length, array_length; | |
| njs_value_t *value; | |
| njs_array_t *keys, *array; | |
| @@ -704,9 +702,7 @@ njs_object_keys_array(njs_vm_t *vm, cons | |
| * The maximum array index is 4294967294, so | |
| * it can be stored as a short string inside value. | |
| */ | |
| - size = snprintf((char *) njs_string_short_start(value), | |
| - NJS_STRING_SHORT, "%u", i); | |
| - njs_string_short_set(value, size, size); | |
| + njs_u32_to_string(value, i); | |
| } | |
| } | |
| # HG changeset patch | |
| # User Dmitry Volyntsev <[email protected]> | |
| # Date 1536843359 -10800 | |
| # Thu Sep 13 15:55:59 2018 +0300 | |
| # Node ID 45467a0d45acf3a913ff1cd2b728e89b52c4ffe6 | |
| # Parent 9ed020dadadb4341471af6b65ed5dd1dfef7fc05 | |
| Fixed Array.prototype.length setter. | |
| diff --git a/njs/njs_array.c b/njs/njs_array.c | |
| --- a/njs/njs_array.c | |
| +++ b/njs/njs_array.c | |
| @@ -377,7 +377,7 @@ njs_array_prototype_length(njs_vm_t *vm, | |
| njs_value_t *setval, njs_value_t *retval) | |
| { | |
| double num; | |
| - int32_t size; | |
| + int64_t size; | |
| uint32_t length; | |
| njs_ret_t ret; | |
| njs_value_t *val; | |
| @@ -399,7 +399,7 @@ njs_array_prototype_length(njs_vm_t *vm, | |
| return NJS_ERROR; | |
| } | |
| - size = (int32_t) (length - array->length); | |
| + size = (int64_t) length - array->length; | |
| if (size > 0) { | |
| ret = njs_array_expand(vm, array, 0, size); | |
| diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c | |
| --- a/njs/test/njs_unit_test.c | |
| +++ b/njs/test/njs_unit_test.c | |
| @@ -2790,9 +2790,15 @@ static njs_unit_test_t njs_test[] = | |
| { nxt_string("[].length = {}"), | |
| nxt_string("RangeError: Invalid array length") }, | |
| + { nxt_string("[].length = 2**32 - 1"), | |
| + nxt_string("MemoryError") }, | |
| + | |
| { nxt_string("[].length = 2**32"), | |
| nxt_string("RangeError: Invalid array length") }, | |
| + { nxt_string("[].length = 2**32 + 1"), | |
| + nxt_string("RangeError: Invalid array length") }, | |
| + | |
| { nxt_string("[].length = -1"), | |
| nxt_string("RangeError: Invalid array length") }, | |
| # HG changeset patch | |
| # User Dmitry Volyntsev <[email protected]> | |
| # Date 1536843359 -10800 | |
| # Thu Sep 13 15:55:59 2018 +0300 | |
| # Node ID 8c535b80e025e6e2fba30ed17fe82040585ec363 | |
| # Parent 45467a0d45acf3a913ff1cd2b728e89b52c4ffe6 | |
| Fixed njs_array_alloc() for length > 2**31. | |
| diff --git a/njs/njs_array.c b/njs/njs_array.c | |
| --- a/njs/njs_array.c | |
| +++ b/njs/njs_array.c | |
| @@ -117,7 +117,7 @@ njs_array_alloc(njs_vm_t *vm, uint32_t l | |
| goto memory_error; | |
| } | |
| - size = length + spare; | |
| + size = (size_t) length + spare; | |
| if (nxt_slow_path(size * sizeof(njs_value_t) < size)) { | |
| goto memory_error; | |
| # HG changeset patch | |
| # User Dmitry Volyntsev <[email protected]> | |
| # Date 1536843359 -10800 | |
| # Thu Sep 13 15:55:59 2018 +0300 | |
| # Node ID 9751de05d4d40bec57e3fe4f6a08438b93ebb2bf | |
| # Parent 8c535b80e025e6e2fba30ed17fe82040585ec363 | |
| Fixed njs_string_slice(). | |
| dst retval argument was ignored. | |
| diff --git a/njs/njs_string.c b/njs/njs_string.c | |
| --- a/njs/njs_string.c | |
| +++ b/njs/njs_string.c | |
| @@ -1288,7 +1288,7 @@ njs_string_slice_args(njs_slice_prop_t * | |
| nxt_noinline njs_ret_t | |
| njs_string_slice(njs_vm_t *vm, njs_value_t *dst, | |
| - const njs_string_prop_t *string, njs_slice_prop_t *slice) | |
| + const njs_string_prop_t *string, const njs_slice_prop_t *slice) | |
| { | |
| size_t size, n, length; | |
| const u_char *p, *start, *end; | |
| @@ -1325,10 +1325,10 @@ njs_string_slice(njs_vm_t *vm, njs_value | |
| } | |
| if (nxt_fast_path(size != 0)) { | |
| - return njs_string_new(vm, &vm->retval, start, size, length); | |
| + return njs_string_new(vm, dst, start, size, length); | |
| } | |
| - vm->retval = njs_string_empty; | |
| + *dst = njs_string_empty; | |
| return NXT_OK; | |
| } | |
| diff --git a/njs/njs_string.h b/njs/njs_string.h | |
| --- a/njs/njs_string.h | |
| +++ b/njs/njs_string.h | |
| @@ -146,7 +146,7 @@ njs_ret_t njs_string_constructor(njs_vm_ | |
| nxt_bool_t njs_string_eq(const njs_value_t *val1, const njs_value_t *val2); | |
| nxt_int_t njs_string_cmp(const njs_value_t *val1, const njs_value_t *val2); | |
| njs_ret_t njs_string_slice(njs_vm_t *vm, njs_value_t *dst, | |
| - const njs_string_prop_t *string, njs_slice_prop_t *slice); | |
| + const njs_string_prop_t *string, const njs_slice_prop_t *slice); | |
| const u_char *njs_string_offset(const u_char *start, const u_char *end, | |
| size_t index); | |
| nxt_noinline uint32_t njs_string_index(njs_string_prop_t *string, | |
| # HG changeset patch | |
| # User Dmitry Volyntsev <[email protected]> | |
| # Date 1536849241 -10800 | |
| # Thu Sep 13 17:34:01 2018 +0300 | |
| # Node ID e2084d6c90ecb6f57f3433639b36e578f923be1d | |
| # Parent 9751de05d4d40bec57e3fe4f6a08438b93ebb2bf | |
| Object property quering is refactored. | |
| njs_property_query() only returns property descriptors and | |
| does not try to invoke PROPERTY_HANDLER for | |
| AccessorDescriptors. | |
| njs_property_query_t.own can be used to query for an object's | |
| OwnProperty. | |
| njs_value_property() is introduced which corresponds to [[Get]] | |
| method from specification. | |
| This fixes #32 issue on Github. | |
| diff --git a/njs/njs_object.c b/njs/njs_object.c | |
| --- a/njs/njs_object.c | |
| +++ b/njs/njs_object.c | |
| @@ -246,13 +246,15 @@ njs_object_property(njs_vm_t *vm, const | |
| njs_ret_t | |
| njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object, | |
| - njs_value_t *property) | |
| + const njs_value_t *property) | |
| { | |
| uint32_t index; | |
| uint32_t (*hash)(const void *, size_t); | |
| njs_ret_t ret; | |
| + njs_array_t *array; | |
| njs_object_t *obj; | |
| njs_function_t *function; | |
| + njs_object_prop_t *prop; | |
| const njs_extern_t *ext_proto; | |
| hash = nxt_djb_hash; | |
| @@ -285,6 +287,34 @@ njs_property_query(njs_vm_t *vm, njs_pro | |
| if (nxt_fast_path(index < NJS_ARRAY_MAX_LENGTH)) { | |
| return njs_array_property_query(vm, pq, object, index); | |
| + | |
| + } else if (pq->query == NJS_PROPERTY_QUERY_GET) { | |
| + | |
| + /* FIXME: length should be a DataProperty */ | |
| + | |
| + njs_primitive_value_to_string(vm, &pq->value, property); | |
| + njs_string_get(&pq->value, &pq->lhq.key); | |
| + | |
| + if (nxt_slow_path(pq->lhq.key.length == 6 | |
| + && memcmp(pq->lhq.key.start, "length", 6) | |
| + == 0)) | |
| + { | |
| + prop = &pq->scratch; | |
| + | |
| + prop->name = (njs_value_t) njs_string("length"); | |
| + | |
| + array = object->data.u.array; | |
| + njs_value_number_set(&prop->value, array->length); | |
| + | |
| + prop->type = NJS_PROPERTY; | |
| + prop->configurable = 0; | |
| + prop->enumerable = 0; | |
| + prop->writable = 1; | |
| + | |
| + pq->lhq.value = prop; | |
| + | |
| + return NXT_OK; | |
| + } | |
| } | |
| } else { | |
| @@ -381,6 +411,7 @@ njs_object_property_query(njs_vm_t *vm, | |
| njs_value_t *value, njs_object_t *object) | |
| { | |
| njs_ret_t ret; | |
| + njs_object_t *proto; | |
| njs_object_prop_t *prop; | |
| pq->lhq.proto = &njs_object_hash_proto; | |
| @@ -392,70 +423,60 @@ njs_object_property_query(njs_vm_t *vm, | |
| } | |
| } | |
| + proto = object; | |
| + | |
| do { | |
| - pq->prototype = object; | |
| + pq->prototype = proto; | |
| - ret = nxt_lvlhsh_find(&object->hash, &pq->lhq); | |
| + /* FIXME: length and other shared properties should be Own property */ | |
| + | |
| + if (nxt_fast_path(!pq->own || proto == object)) { | |
| + ret = nxt_lvlhsh_find(&proto->hash, &pq->lhq); | |
| + | |
| + if (ret == NXT_OK) { | |
| + prop = pq->lhq.value; | |
| + | |
| + if (prop->type != NJS_WHITEOUT) { | |
| + pq->shared = 0; | |
| + | |
| + return ret; | |
| + } | |
| + | |
| + goto next; | |
| + } | |
| + } | |
| + | |
| + if (pq->query > NJS_PROPERTY_QUERY_IN) { | |
| + return NXT_DECLINED; | |
| + } | |
| + | |
| + ret = nxt_lvlhsh_find(&proto->shared_hash, &pq->lhq); | |
| if (ret == NXT_OK) { | |
| prop = pq->lhq.value; | |
| if (prop->type != NJS_WHITEOUT) { | |
| - pq->shared = 0; | |
| + pq->shared = 1; | |
| return ret; | |
| } | |
| - | |
| - goto next; | |
| } | |
| if (pq->query > NJS_PROPERTY_QUERY_IN) { | |
| - /* NXT_DECLINED */ | |
| - return ret; | |
| - } | |
| - | |
| - ret = nxt_lvlhsh_find(&object->shared_hash, &pq->lhq); | |
| - | |
| - if (ret == NXT_OK) { | |
| - pq->shared = 1; | |
| - | |
| - if (pq->query == NJS_PROPERTY_QUERY_GET) { | |
| - prop = pq->lhq.value; | |
| - | |
| - if (prop->type == NJS_PROPERTY_HANDLER) { | |
| - pq->scratch = *prop; | |
| - prop = &pq->scratch; | |
| - ret = prop->value.data.u.prop_handler(vm, value, NULL, | |
| - &prop->value); | |
| - | |
| - if (nxt_fast_path(ret == NXT_OK)) { | |
| - prop->type = NJS_PROPERTY; | |
| - pq->lhq.value = prop; | |
| - } | |
| - } | |
| - } | |
| - | |
| - return ret; | |
| - } | |
| - | |
| - if (pq->query > NJS_PROPERTY_QUERY_IN) { | |
| - /* NXT_DECLINED */ | |
| - return ret; | |
| + return NXT_DECLINED; | |
| } | |
| next: | |
| - object = object->__proto__; | |
| + proto = proto->__proto__; | |
| - } while (object != NULL); | |
| + } while (proto != NULL); | |
| if (njs_is_string(value)) { | |
| return NJS_STRING_VALUE; | |
| } | |
| - /* NXT_DECLINED */ | |
| - | |
| - return ret; | |
| + return NXT_DECLINED; | |
| } | |
| @@ -1037,12 +1058,9 @@ njs_object_get_own_property_descriptor(n | |
| lhq.proto = &njs_object_hash_proto; | |
| if (prop == NULL) { | |
| - pq.query = NJS_PROPERTY_QUERY_GET; | |
| - pq.lhq.key.length = 0; | |
| - pq.lhq.key.start = NULL; | |
| + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 1); | |
| - ret = njs_property_query(vm, &pq, (njs_value_t *) value, | |
| - (njs_value_t *) property); | |
| + ret = njs_property_query(vm, &pq, (njs_value_t *) value, property); | |
| if (ret != NXT_OK) { | |
| vm->retval = njs_value_void; | |
| @@ -1050,6 +1068,16 @@ njs_object_get_own_property_descriptor(n | |
| } | |
| prop = pq.lhq.value; | |
| + | |
| + if (prop->type == NJS_PROPERTY_HANDLER) { | |
| + pq.scratch = *prop; | |
| + prop = &pq.scratch; | |
| + ret = prop->value.data.u.prop_handler(vm, (njs_value_t *) value, | |
| + NULL, &prop->value); | |
| + if (nxt_slow_path(ret != NXT_OK)) { | |
| + return ret; | |
| + } | |
| + } | |
| } | |
| descriptor = njs_object_alloc(vm); | |
| diff --git a/njs/njs_object.h b/njs/njs_object.h | |
| --- a/njs/njs_object.h | |
| +++ b/njs/njs_object.h | |
| @@ -51,9 +51,17 @@ typedef struct { | |
| njs_object_t *prototype; | |
| uint8_t query; | |
| uint8_t shared; | |
| + uint8_t own; | |
| } njs_property_query_t; | |
| +#define njs_property_query_init(pq, _query, _own) \ | |
| + do { \ | |
| + (pq)->lhq.key.length = 0; \ | |
| + (pq)->query = _query; \ | |
| + (pq)->own = _own; \ | |
| + } while (0) | |
| + | |
| struct njs_object_init_s { | |
| nxt_str_t name; | |
| @@ -67,10 +75,12 @@ njs_object_t *njs_object_value_copy(njs_ | |
| 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, const njs_value_t *object); | |
| +njs_ret_t njs_value_property(njs_vm_t *vm, njs_value_t *value, | |
| + const njs_value_t *property, njs_value_t *retval); | |
| njs_object_prop_t *njs_object_property(njs_vm_t *vm, const njs_object_t *obj, | |
| nxt_lvlhsh_query_t *lhq); | |
| njs_ret_t njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, | |
| - njs_value_t *object, njs_value_t *property); | |
| + njs_value_t *object, const njs_value_t *property); | |
| nxt_int_t njs_object_hash_create(njs_vm_t *vm, nxt_lvlhsh_t *hash, | |
| const njs_object_prop_t *prop, nxt_uint_t n); | |
| njs_ret_t njs_object_constructor(njs_vm_t *vm, njs_value_t *args, | |
| diff --git a/njs/njs_vm.c b/njs/njs_vm.c | |
| --- a/njs/njs_vm.c | |
| +++ b/njs/njs_vm.c | |
| @@ -476,147 +476,13 @@ njs_ret_t | |
| njs_vmcode_property_get(njs_vm_t *vm, njs_value_t *object, | |
| njs_value_t *property) | |
| { | |
| - void *obj; | |
| - int32_t index; | |
| - uintptr_t data; | |
| - njs_ret_t ret; | |
| - njs_value_t *val, ext_val; | |
| - njs_slice_prop_t slice; | |
| - njs_string_prop_t string; | |
| - njs_object_prop_t *prop; | |
| - const njs_value_t *retval; | |
| - const njs_extern_t *ext_proto; | |
| - njs_property_query_t pq; | |
| - | |
| - pq.query = NJS_PROPERTY_QUERY_GET; | |
| - | |
| - ret = njs_property_query(vm, &pq, object, property); | |
| - | |
| - retval = &njs_value_void; | |
| - | |
| - switch (ret) { | |
| - | |
| - case NXT_OK: | |
| - prop = pq.lhq.value; | |
| - | |
| - switch (prop->type) { | |
| - | |
| - case NJS_METHOD: | |
| - if (pq.shared) { | |
| - ret = njs_method_private_copy(vm, &pq); | |
| - | |
| - if (nxt_slow_path(ret != NXT_OK)) { | |
| - return ret; | |
| - } | |
| - | |
| - prop = pq.lhq.value; | |
| - } | |
| - | |
| - /* Fall through. */ | |
| - | |
| - case NJS_PROPERTY: | |
| - retval = &prop->value; | |
| - break; | |
| - | |
| - default: | |
| - nxt_thread_log_alert("invalid property get type:%d", prop->type); | |
| - | |
| - return NXT_ERROR; | |
| - } | |
| - | |
| - break; | |
| - | |
| - case NXT_DECLINED: | |
| - case NJS_PRIMITIVE_VALUE: | |
| - break; | |
| - | |
| - case NJS_STRING_VALUE: | |
| - | |
| - /* string[n]. */ | |
| - | |
| - index = (int32_t) njs_value_to_index(property); | |
| - | |
| - if (nxt_fast_path(index >= 0)) { | |
| - slice.start = index; | |
| - slice.length = 1; | |
| - slice.string_length = njs_string_prop(&string, object); | |
| - | |
| - if (slice.start < slice.string_length) { | |
| - /* | |
| - * A single codepoint string fits in vm->retval | |
| - * so the function cannot fail. | |
| - */ | |
| - (void) njs_string_slice(vm, &vm->retval, &string, &slice); | |
| - | |
| - return sizeof(njs_vmcode_prop_get_t); | |
| - } | |
| - } | |
| - | |
| - break; | |
| - | |
| - case NJS_ARRAY_VALUE: | |
| - val = pq.lhq.value; | |
| - | |
| - if (njs_is_valid(val)) { | |
| - retval = val; | |
| - } | |
| - | |
| - break; | |
| - | |
| - case NJS_EXTERNAL_VALUE: | |
| - ext_proto = object->external.proto; | |
| - | |
| - ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq); | |
| - | |
| - if (ret == NXT_OK) { | |
| - ext_proto = pq.lhq.value; | |
| - | |
| - ext_val.type = NJS_EXTERNAL; | |
| - ext_val.data.truth = 1; | |
| - ext_val.external.proto = ext_proto; | |
| - ext_val.external.index = object->external.index; | |
| - | |
| - if ((ext_proto->type & NJS_EXTERN_OBJECT) != 0) { | |
| - retval = &ext_val; | |
| - break; | |
| - } | |
| - | |
| - data = ext_proto->data; | |
| - | |
| - } else { | |
| - data = (uintptr_t) &pq.lhq.key; | |
| - } | |
| - | |
| - vm->retval = njs_value_void; | |
| - | |
| - if (ext_proto->get != NULL) { | |
| - obj = njs_extern_object(vm, object); | |
| - | |
| - ret = ext_proto->get(vm, &vm->retval, obj, data); | |
| - if (nxt_slow_path(ret != NXT_OK)) { | |
| - return ret; | |
| - } | |
| - | |
| - /* The vm->retval is already retained by ext_proto->get(). */ | |
| - } | |
| - | |
| - if (ext_proto->type == NJS_EXTERN_METHOD) { | |
| - vm->retval.data.u.function = ext_proto->function; | |
| - vm->retval.type = NJS_FUNCTION; | |
| - vm->retval.data.truth = 1; | |
| - } | |
| - | |
| - return sizeof(njs_vmcode_prop_get_t); | |
| - | |
| - case NJS_TRAP: | |
| - case NXT_ERROR: | |
| - default: | |
| - | |
| + njs_ret_t ret; | |
| + | |
| + ret = njs_value_property(vm, object, property, &vm->retval); | |
| + if (ret == NXT_ERROR || ret == NJS_TRAP) { | |
| return ret; | |
| } | |
| - vm->retval = *retval; | |
| - | |
| /* GC: njs_retain(retval) */ | |
| return sizeof(njs_vmcode_prop_get_t); | |
| @@ -646,8 +512,7 @@ njs_vmcode_property_set(njs_vm_t *vm, nj | |
| code = (njs_vmcode_prop_set_t *) vm->current; | |
| value = njs_vmcode_operand(vm, code->value); | |
| - pq.lhq.key.length = 0; | |
| - pq.query = NJS_PROPERTY_QUERY_SET; | |
| + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 0); | |
| ret = njs_property_query(vm, &pq, object, property); | |
| @@ -767,7 +632,7 @@ njs_vmcode_property_in(njs_vm_t *vm, njs | |
| retval = &njs_value_false; | |
| - pq.query = NJS_PROPERTY_QUERY_IN; | |
| + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_IN, 0); | |
| ret = njs_property_query(vm, &pq, object, property); | |
| @@ -851,8 +716,7 @@ njs_vmcode_property_delete(njs_vm_t *vm, | |
| retval = &njs_value_false; | |
| - pq.lhq.key.length = 0; | |
| - pq.query = NJS_PROPERTY_QUERY_DELETE; | |
| + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_DELETE, 0); | |
| ret = njs_property_query(vm, &pq, object, property); | |
| @@ -1097,12 +961,10 @@ njs_ret_t | |
| njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object, | |
| njs_value_t *constructor) | |
| { | |
| - nxt_int_t ret; | |
| - njs_value_t *value; | |
| - njs_object_t *prototype, *proto; | |
| - njs_object_prop_t *prop; | |
| - const njs_value_t *retval; | |
| - njs_property_query_t pq; | |
| + nxt_int_t ret; | |
| + njs_value_t *value, val; | |
| + njs_object_t *prototype, *proto; | |
| + const njs_value_t *retval; | |
| static njs_value_t prototype_string = njs_string("prototype"); | |
| @@ -1114,13 +976,10 @@ njs_vmcode_instance_of(njs_vm_t *vm, njs | |
| retval = &njs_value_false; | |
| if (njs_is_object(object)) { | |
| - pq.query = NJS_PROPERTY_QUERY_GET; | |
| - | |
| - ret = njs_property_query(vm, &pq, constructor, &prototype_string); | |
| + ret = njs_value_property(vm, constructor, &prototype_string, &val); | |
| if (nxt_fast_path(ret == NXT_OK)) { | |
| - prop = pq.lhq.value; | |
| - value = &prop->value; | |
| + value = &val; | |
| /* TODO: test prop->value is object. */ | |
| @@ -2123,9 +1982,7 @@ njs_vmcode_method_frame(njs_vm_t *vm, nj | |
| method = (njs_vmcode_method_frame_t *) vm->current; | |
| - pq.lhq.key.length = 0; | |
| - pq.lhq.key.start = NULL; | |
| - pq.query = NJS_PROPERTY_QUERY_GET; | |
| + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0); | |
| ret = njs_property_query(vm, &pq, object, name); | |
| @@ -2134,6 +1991,16 @@ njs_vmcode_method_frame(njs_vm_t *vm, nj | |
| case NXT_OK: | |
| prop = pq.lhq.value; | |
| + if (prop->type == NJS_PROPERTY_HANDLER) { | |
| + pq.scratch = *prop; | |
| + prop = &pq.scratch; | |
| + ret = prop->value.data.u.prop_handler(vm, object, NULL, | |
| + &prop->value); | |
| + if (nxt_slow_path(ret != NXT_OK)) { | |
| + return ret; | |
| + } | |
| + } | |
| + | |
| ret = njs_function_frame_create(vm, &prop->value, object, method->nargs, | |
| method->code.ctor); | |
| break; | |
| @@ -3159,6 +3026,45 @@ njs_vmcode_string_argument(njs_vm_t *vm, | |
| } | |
| +static njs_ret_t | |
| +njs_vmcode_restart(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2) | |
| +{ | |
| + u_char *restart; | |
| + njs_ret_t ret; | |
| + njs_value_t *retval, *value1; | |
| + njs_native_frame_t *frame; | |
| + njs_vmcode_generic_t *vmcode; | |
| + | |
| + frame = vm->top_frame; | |
| + restart = frame->trap_restart; | |
| + frame->trap_restart = NULL; | |
| + vm->current = restart; | |
| + vmcode = (njs_vmcode_generic_t *) restart; | |
| + | |
| + value1 = &frame->trap_values[0]; | |
| + | |
| + if (frame->trap_reference) { | |
| + value1 = value1->data.u.value; | |
| + } | |
| + | |
| + ret = vmcode->code.operation(vm, value1, &frame->trap_values[1]); | |
| + | |
| + if (nxt_slow_path(ret == NJS_TRAP)) { | |
| + /* Trap handlers are not reentrant. */ | |
| + njs_internal_error(vm, "trap inside restart instruction"); | |
| + return NXT_ERROR; | |
| + } | |
| + | |
| + retval = njs_vmcode_operand(vm, vmcode->operand1); | |
| + | |
| + //njs_release(vm, retval); | |
| + | |
| + *retval = vm->retval; | |
| + | |
| + return ret; | |
| +} | |
| + | |
| + | |
| /* | |
| * A hint value is 0 for numbers and 1 for strings. The value chooses | |
| * method calls order specified by ECMAScript 5.1: "valueOf", "toString" | |
| @@ -3257,42 +3163,165 @@ njs_primitive_value(njs_vm_t *vm, njs_va | |
| } | |
| -static njs_ret_t | |
| -njs_vmcode_restart(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2) | |
| +/* | |
| + * ES5.1, 8.12.3: [[Get]]. | |
| + * NXT_OK property has been found in object, | |
| + * retval will contain the property's value | |
| + * | |
| + * NXT_DECLINED property was not found in object, | |
| + * NJS_TRAP the property trap must be called, | |
| + * NXT_ERROR exception has been thrown. | |
| + * retval will contain undefined | |
| + */ | |
| +njs_ret_t | |
| +njs_value_property(njs_vm_t *vm, njs_value_t *value, | |
| + const njs_value_t *property, njs_value_t *retval) | |
| { | |
| - u_char *restart; | |
| + void *obj; | |
| + int32_t index; | |
| + uintptr_t data; | |
| njs_ret_t ret; | |
| - njs_value_t *retval, *value1; | |
| - njs_native_frame_t *frame; | |
| - njs_vmcode_generic_t *vmcode; | |
| - | |
| - frame = vm->top_frame; | |
| - restart = frame->trap_restart; | |
| - frame->trap_restart = NULL; | |
| - vm->current = restart; | |
| - vmcode = (njs_vmcode_generic_t *) restart; | |
| - | |
| - value1 = &frame->trap_values[0]; | |
| - | |
| - if (frame->trap_reference) { | |
| - value1 = value1->data.u.value; | |
| + njs_value_t *val, ext_val; | |
| + njs_slice_prop_t slice; | |
| + njs_string_prop_t string; | |
| + njs_object_prop_t *prop; | |
| + const njs_extern_t *ext_proto; | |
| + njs_property_query_t pq; | |
| + | |
| + *retval = njs_value_void; | |
| + | |
| + njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0); | |
| + | |
| + ret = njs_property_query(vm, &pq, value, property); | |
| + | |
| + switch (ret) { | |
| + | |
| + case NXT_OK: | |
| + prop = pq.lhq.value; | |
| + | |
| + switch (prop->type) { | |
| + | |
| + case NJS_METHOD: | |
| + if (pq.shared) { | |
| + ret = njs_method_private_copy(vm, &pq); | |
| + | |
| + if (nxt_slow_path(ret != NXT_OK)) { | |
| + return ret; | |
| + } | |
| + | |
| + prop = pq.lhq.value; | |
| + } | |
| + | |
| + /* Fall through. */ | |
| + | |
| + case NJS_PROPERTY: | |
| + *retval = prop->value; | |
| + break; | |
| + | |
| + case NJS_PROPERTY_HANDLER: | |
| + pq.scratch = *prop; | |
| + prop = &pq.scratch; | |
| + ret = prop->value.data.u.prop_handler(vm, value, NULL, | |
| + &prop->value); | |
| + | |
| + if (nxt_fast_path(ret == NXT_OK)) { | |
| + *retval = prop->value; | |
| + } | |
| + | |
| + break; | |
| + | |
| + default: | |
| + njs_internal_error(vm, "invalid property get type:%d", prop->type); | |
| + | |
| + return NXT_ERROR; | |
| + } | |
| + | |
| + break; | |
| + | |
| + case NXT_DECLINED: | |
| + case NJS_PRIMITIVE_VALUE: | |
| + break; | |
| + | |
| + case NJS_STRING_VALUE: | |
| + | |
| + /* string[n]. */ | |
| + | |
| + index = (int32_t) njs_value_to_index(property); | |
| + | |
| + if (nxt_fast_path(index >= 0)) { | |
| + slice.start = index; | |
| + slice.length = 1; | |
| + slice.string_length = njs_string_prop(&string, value); | |
| + | |
| + if (slice.start < slice.string_length) { | |
| + /* | |
| + * A single codepoint string fits in retval | |
| + * so the function cannot fail. | |
| + */ | |
| + (void) njs_string_slice(vm, retval, &string, &slice); | |
| + } | |
| + } | |
| + | |
| + break; | |
| + | |
| + case NJS_ARRAY_VALUE: | |
| + val = pq.lhq.value; | |
| + | |
| + if (njs_is_valid(val)) { | |
| + *retval = *val; | |
| + } | |
| + | |
| + break; | |
| + | |
| + case NJS_EXTERNAL_VALUE: | |
| + ext_proto = value->external.proto; | |
| + | |
| + ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq); | |
| + | |
| + if (ret == NXT_OK) { | |
| + ext_proto = pq.lhq.value; | |
| + | |
| + ext_val.type = NJS_EXTERNAL; | |
| + ext_val.data.truth = 1; | |
| + ext_val.external.proto = ext_proto; | |
| + ext_val.external.index = value->external.index; | |
| + | |
| + if ((ext_proto->type & NJS_EXTERN_OBJECT) != 0) { | |
| + *retval = ext_val; | |
| + break; | |
| + } | |
| + | |
| + data = ext_proto->data; | |
| + | |
| + } else { | |
| + data = (uintptr_t) &pq.lhq.key; | |
| + } | |
| + | |
| + if (ext_proto->get != NULL) { | |
| + obj = njs_extern_object(vm, value); | |
| + | |
| + ret = ext_proto->get(vm, retval, obj, data); | |
| + if (nxt_slow_path(ret != NXT_OK)) { | |
| + return ret; | |
| + } | |
| + } | |
| + | |
| + if (ext_proto->type == NJS_EXTERN_METHOD) { | |
| + retval->data.u.function = ext_proto->function; | |
| + retval->type = NJS_FUNCTION; | |
| + retval->data.truth = 1; | |
| + } | |
| + | |
| + break; | |
| + | |
| + case NJS_TRAP: | |
| + case NXT_ERROR: | |
| + default: | |
| + | |
| + return ret; | |
| } | |
| - ret = vmcode->code.operation(vm, value1, &frame->trap_values[1]); | |
| - | |
| - if (nxt_slow_path(ret == NJS_TRAP)) { | |
| - /* Trap handlers are not reentrant. */ | |
| - njs_internal_error(vm, "trap inside restart instruction"); | |
| - return NXT_ERROR; | |
| - } | |
| - | |
| - retval = njs_vmcode_operand(vm, vmcode->operand1); | |
| - | |
| - //njs_release(vm, retval); | |
| - | |
| - *retval = vm->retval; | |
| - | |
| - return ret; | |
| + return njs_is_void(retval) ? NXT_DECLINED : NXT_OK; | |
| } | |
| diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c | |
| --- a/njs/test/njs_unit_test.c | |
| +++ b/njs/test/njs_unit_test.c | |
| @@ -7001,9 +7001,18 @@ static njs_unit_test_t njs_test[] = | |
| { nxt_string("Object.getOwnPropertyDescriptor([3,4], 1).value"), | |
| nxt_string("4") }, | |
| + { nxt_string("Object.getOwnPropertyDescriptor(Object.create({a:1}), 'a')"), | |
| + nxt_string("undefined") }, | |
| + | |
| { nxt_string("Object.getOwnPropertyDescriptor([], 'length').value"), | |
| nxt_string("0") }, | |
| + { nxt_string("Object.getOwnPropertyDescriptor(Array.of, 'length').value"), | |
| + nxt_string("0") }, | |
| + | |
| + { nxt_string("JSON.stringify(Object.getOwnPropertyDescriptor(new String('abc'), 'length'))"), | |
| + nxt_string("{\"value\":3,\"configurable\":false,\"enumerable\":false,\"writable\":false}") }, | |
| + | |
| { nxt_string("JSON.stringify(Object.getOwnPropertyDescriptor([3,4], 'length'))"), | |
| nxt_string("{\"value\":2,\"configurable\":false,\"enumerable\":false,\"writable\":true}") }, | |
| # HG changeset patch | |
| # User Dmitry Volyntsev <[email protected]> | |
| # Date 1536849242 -10800 | |
| # Thu Sep 13 17:34:02 2018 +0300 | |
| # Node ID 9f35cd7f1982667a666e31aba679770369cb566a | |
| # Parent e2084d6c90ecb6f57f3433639b36e578f923be1d | |
| Added njs_primitive_value_to_number(). | |
| diff --git a/njs/njs_number.c b/njs/njs_number.c | |
| --- a/njs/njs_number.c | |
| +++ b/njs/njs_number.c | |
| @@ -64,6 +64,24 @@ njs_value_to_index(const njs_value_t *va | |
| double | |
| +njs_primitive_value_to_number(const njs_value_t *value) | |
| +{ | |
| + if (nxt_fast_path(njs_is_numeric(value))) { | |
| + return value->data.u.number; | |
| + } | |
| + | |
| + return njs_string_to_number(value, 1); | |
| +} | |
| + | |
| + | |
| +uint32_t | |
| +njs_primitive_value_to_uint32(const njs_value_t *value) | |
| +{ | |
| + return njs_number_to_integer(njs_primitive_value_to_number(value)); | |
| +} | |
| + | |
| + | |
| +double | |
| njs_number_dec_parse(const u_char **start, const u_char *end) | |
| { | |
| return nxt_strtod(start, end); | |
| diff --git a/njs/njs_number.h b/njs/njs_number.h | |
| --- a/njs/njs_number.h | |
| +++ b/njs/njs_number.h | |
| @@ -13,6 +13,8 @@ | |
| uint32_t njs_value_to_index(const njs_value_t *value); | |
| +double njs_primitive_value_to_number(const njs_value_t *value); | |
| +uint32_t njs_primitive_value_to_uint32(const njs_value_t *value); | |
| double njs_number_dec_parse(const u_char **start, const u_char *end); | |
| uint64_t njs_number_oct_parse(const u_char **start, const u_char *end); | |
| uint64_t njs_number_bin_parse(const u_char **start, const u_char *end); | |
| # HG changeset patch | |
| # User Dmitry Volyntsev <[email protected]> | |
| # Date 1536849243 -10800 | |
| # Thu Sep 13 17:34:03 2018 +0300 | |
| # Node ID fcee00ca8136045ebfaf0743e2ac5f4a36ef4fb5 | |
| # Parent 9f35cd7f1982667a666e31aba679770369cb566a | |
| Fixed String.slice() for undefined arguments. | |
| diff --git a/njs/njs_string.c b/njs/njs_string.c | |
| --- a/njs/njs_string.c | |
| +++ b/njs/njs_string.c | |
| @@ -1239,45 +1239,43 @@ njs_string_slice_args(njs_slice_prop_t * | |
| ssize_t start, end, length; | |
| length = slice->string_length; | |
| - start = 0; | |
| - | |
| - if (nargs > 1) { | |
| - start = args[1].data.u.number; | |
| + | |
| + start = njs_arg(args, nargs, 1)->data.u.number; | |
| + | |
| + if (start < 0) { | |
| + start += length; | |
| if (start < 0) { | |
| - start += length; | |
| - | |
| - if (start < 0) { | |
| - start = 0; | |
| - } | |
| + start = 0; | |
| } | |
| - | |
| - if (start >= length) { | |
| - start = 0; | |
| - length = 0; | |
| + } | |
| + | |
| + if (start >= length) { | |
| + start = 0; | |
| + length = 0; | |
| + | |
| + } else { | |
| + if (!njs_is_void(njs_arg(args, nargs, 2))) { | |
| + end = njs_arg(args, nargs, 2)->data.u.number; | |
| } else { | |
| end = length; | |
| - | |
| - if (nargs > 2) { | |
| - end = args[2].data.u.number; | |
| - | |
| - if (end < 0) { | |
| - end += length; | |
| - } | |
| + } | |
| + | |
| + if (end < 0) { | |
| + end += length; | |
| + } | |
| + | |
| + if (length >= end) { | |
| + length = end - start; | |
| + | |
| + if (length < 0) { | |
| + start = 0; | |
| + length = 0; | |
| } | |
| - if (length >= end) { | |
| - length = end - start; | |
| - | |
| - if (length < 0) { | |
| - start = 0; | |
| - length = 0; | |
| - } | |
| - | |
| - } else { | |
| - length -= start; | |
| - } | |
| + } else { | |
| + length -= start; | |
| } | |
| } | |
| diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c | |
| --- a/njs/test/njs_unit_test.c | |
| +++ b/njs/test/njs_unit_test.c | |
| @@ -3800,6 +3800,18 @@ static njs_unit_test_t njs_test[] = | |
| { nxt_string("'abcdefgh'.slice(3)"), | |
| nxt_string("defgh") }, | |
| + { nxt_string("'abcdefgh'.slice(undefined, undefined)"), | |
| + nxt_string("abcdefgh") }, | |
| + | |
| + { nxt_string("'abcdefgh'.slice(undefined)"), | |
| + nxt_string("abcdefgh") }, | |
| + | |
| + { nxt_string("'abcdefgh'.slice(undefined, 1)"), | |
| + nxt_string("a") }, | |
| + | |
| + { nxt_string("'abcdefgh'.slice(3, undefined)"), | |
| + nxt_string("defgh") }, | |
| + | |
| { nxt_string("'abcde'.slice(50)"), | |
| nxt_string("") }, | |
| # HG changeset patch | |
| # User Dmitry Volyntsev <[email protected]> | |
| # Date 1536849243 -10800 | |
| # Thu Sep 13 17:34:03 2018 +0300 | |
| # Node ID 9f0373c6e4174675532d0ade7ece8cab08e24fbd | |
| # Parent fcee00ca8136045ebfaf0743e2ac5f4a36ef4fb5 | |
| Added njs_string_slice_string_prop(). | |
| diff --git a/njs/njs_string.c b/njs/njs_string.c | |
| --- a/njs/njs_string.c | |
| +++ b/njs/njs_string.c | |
| @@ -1284,46 +1284,60 @@ njs_string_slice_args(njs_slice_prop_t * | |
| } | |
| +nxt_noinline void | |
| +njs_string_slice_string_prop(njs_string_prop_t *dst, | |
| + const njs_string_prop_t *string, const njs_slice_prop_t *slice) | |
| +{ | |
| + size_t size, n, length; | |
| + const u_char *p, *start, *end; | |
| + | |
| + length = slice->length; | |
| + start = string->start; | |
| + | |
| + if (string->size == slice->string_length) { | |
| + /* Byte or ASCII string. */ | |
| + start += slice->start; | |
| + size = slice->length; | |
| + | |
| + if (string->length == 0) { | |
| + /* Byte string. */ | |
| + length = 0; | |
| + } | |
| + | |
| + } else { | |
| + /* UTF-8 string. */ | |
| + end = start + string->size; | |
| + start = njs_string_offset(start, end, slice->start); | |
| + | |
| + /* Evaluate size of the slice in bytes and ajdust length. */ | |
| + p = start; | |
| + n = length; | |
| + | |
| + do { | |
| + p = nxt_utf8_next(p, end); | |
| + n--; | |
| + } while (n != 0 && p < end); | |
| + | |
| + size = p - start; | |
| + length -= n; | |
| + } | |
| + | |
| + dst->start = (u_char *) start; | |
| + dst->length = length; | |
| + dst->size = size; | |
| +} | |
| + | |
| + | |
| nxt_noinline njs_ret_t | |
| njs_string_slice(njs_vm_t *vm, njs_value_t *dst, | |
| const njs_string_prop_t *string, const njs_slice_prop_t *slice) | |
| { | |
| - size_t size, n, length; | |
| - const u_char *p, *start, *end; | |
| - | |
| - length = slice->length; | |
| - start = string->start; | |
| - | |
| - if (string->size == slice->string_length) { | |
| - /* Byte or ASCII string. */ | |
| - start += slice->start; | |
| - size = slice->length; | |
| - | |
| - if (string->length == 0) { | |
| - /* Byte string. */ | |
| - length = 0; | |
| - } | |
| - | |
| - } else { | |
| - /* UTF-8 string. */ | |
| - end = start + string->size; | |
| - start = njs_string_offset(start, end, slice->start); | |
| - | |
| - /* Evaluate size of the slice in bytes and ajdust length. */ | |
| - p = start; | |
| - n = length; | |
| - | |
| - do { | |
| - p = nxt_utf8_next(p, end); | |
| - n--; | |
| - } while (n != 0 && p < end); | |
| - | |
| - size = p - start; | |
| - length -= n; | |
| - } | |
| - | |
| - if (nxt_fast_path(size != 0)) { | |
| - return njs_string_new(vm, dst, start, size, length); | |
| + njs_string_prop_t prop; | |
| + | |
| + njs_string_slice_string_prop(&prop, string, slice); | |
| + | |
| + if (nxt_fast_path(prop.size != 0)) { | |
| + return njs_string_new(vm, dst, prop.start, prop.size, prop.length); | |
| } | |
| *dst = njs_string_empty; | |
| diff --git a/njs/njs_string.h b/njs/njs_string.h | |
| --- a/njs/njs_string.h | |
| +++ b/njs/njs_string.h | |
| @@ -145,6 +145,8 @@ njs_ret_t njs_string_constructor(njs_vm_ | |
| nxt_uint_t nargs, njs_index_t unused); | |
| nxt_bool_t njs_string_eq(const njs_value_t *val1, const njs_value_t *val2); | |
| nxt_int_t njs_string_cmp(const njs_value_t *val1, const njs_value_t *val2); | |
| +nxt_noinline void njs_string_slice_string_prop(njs_string_prop_t *dst, | |
| + const njs_string_prop_t *string, const njs_slice_prop_t *slice); | |
| njs_ret_t njs_string_slice(njs_vm_t *vm, njs_value_t *dst, | |
| const njs_string_prop_t *string, const njs_slice_prop_t *slice); | |
| const u_char *njs_string_offset(const u_char *start, const u_char *end, | |
| # HG changeset patch | |
| # User Dmitry Volyntsev <[email protected]> | |
| # Date 1536849244 -10800 | |
| # Thu Sep 13 17:34:04 2018 +0300 | |
| # Node ID 8fa1991a255eb9c8ca6147f94d78fa9594887879 | |
| # Parent 9f0373c6e4174675532d0ade7ece8cab08e24fbd | |
| Hanling object values properly in Array.prototype.slice. | |
| diff --git a/njs/njs_array.c b/njs/njs_array.c | |
| --- a/njs/njs_array.c | |
| +++ b/njs/njs_array.c | |
| @@ -9,6 +9,19 @@ | |
| 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 length; | |
| +} njs_array_slice_t; | |
| + | |
| + | |
| +typedef struct { | |
| njs_continuation_t cont; | |
| njs_value_t *values; | |
| uint32_t max; | |
| @@ -67,6 +80,8 @@ typedef struct { | |
| } njs_array_sort_t; | |
| +static njs_ret_t njs_array_prototype_slice_continuation(njs_vm_t *vm, | |
| + njs_value_t *args, nxt_uint_t nargs, njs_index_t unused); | |
| static njs_ret_t njs_array_prototype_to_string_continuation(njs_vm_t *vm, | |
| njs_value_t *args, nxt_uint_t nargs, njs_index_t retval); | |
| static njs_ret_t njs_array_prototype_join_continuation(njs_vm_t *vm, | |
| @@ -434,55 +449,86 @@ static njs_ret_t | |
| njs_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs, | |
| njs_index_t unused) | |
| { | |
| - int32_t start, end, length; | |
| - uint32_t n; | |
| - njs_array_t *array; | |
| - njs_value_t *value; | |
| + njs_ret_t ret; | |
| + njs_array_slice_t *slice; | |
| + | |
| + static const njs_value_t njs_string_length = njs_string("length"); | |
| + | |
| + slice = njs_vm_continuation(vm); | |
| + slice->u.cont.function = njs_array_prototype_slice_continuation; | |
| + | |
| + ret = njs_value_property(vm, &args[0], &njs_string_length, &slice->length); | |
| + if (nxt_slow_path(ret == NXT_ERROR || ret == NJS_TRAP)) { | |
| + return ret; | |
| + } | |
| + | |
| + return njs_array_prototype_slice_continuation(vm, args, nargs, unused); | |
| +} | |
| + | |
| + | |
| +static njs_ret_t | |
| +njs_array_prototype_slice_continuation(njs_vm_t *vm, njs_value_t *args, | |
| + nxt_uint_t nargs, njs_index_t unused) | |
| +{ | |
| + size_t size; | |
| + u_char *dst; | |
| + int32_t start, end; | |
| + int64_t length; | |
| + uint32_t n; | |
| + njs_array_t *array; | |
| + njs_value_t *value, name, retval, *this; | |
| + const u_char *src, *last; | |
| + njs_slice_prop_t string_slice; | |
| + njs_array_slice_t *slice; | |
| + njs_string_prop_t string; | |
| start = 0; | |
| - length = 0; | |
| - | |
| - if (njs_is_array(&args[0])) { | |
| - length = args[0].data.u.array->length; | |
| - | |
| - if (nargs > 1) { | |
| - start = args[1].data.u.number; | |
| - | |
| - if (start < 0) { | |
| - start += length; | |
| - | |
| - if (start < 0) { | |
| - start = 0; | |
| - } | |
| - } | |
| - | |
| - if (start >= length) { | |
| + | |
| + slice = njs_vm_continuation(vm); | |
| + | |
| + if (nxt_slow_path(!njs_is_primitive(&slice->length))) { | |
| + njs_vm_trap_value(vm, &slice->length); | |
| + return njs_trap(vm, NJS_TRAP_NUMBER_ARG); | |
| + } | |
| + | |
| + length = njs_primitive_value_to_uint32(&slice->length); | |
| + | |
| + start = njs_arg(args, nargs, 1)->data.u.number; | |
| + | |
| + if (start < 0) { | |
| + start += length; | |
| + | |
| + if (start < 0) { | |
| + start = 0; | |
| + } | |
| + } | |
| + | |
| + if (start >= length) { | |
| + start = 0; | |
| + length = 0; | |
| + | |
| + } else { | |
| + if (!njs_is_void(njs_arg(args, nargs, 2))) { | |
| + end = njs_arg(args, nargs, 2)->data.u.number; | |
| + | |
| + } else { | |
| + end = length; | |
| + } | |
| + | |
| + if (end < 0) { | |
| + end += length; | |
| + } | |
| + | |
| + if (length >= end) { | |
| + length = end - start; | |
| + | |
| + if (length < 0) { | |
| start = 0; | |
| length = 0; | |
| - | |
| - } else { | |
| - end = length; | |
| - | |
| - if (nargs > 2) { | |
| - end = args[2].data.u.number; | |
| - | |
| - if (end < 0) { | |
| - end += length; | |
| - } | |
| - } | |
| - | |
| - if (length >= end) { | |
| - length = end - start; | |
| - | |
| - if (length < 0) { | |
| - start = 0; | |
| - length = 0; | |
| - } | |
| - | |
| - } else { | |
| - length -= start; | |
| - } | |
| } | |
| + | |
| + } else { | |
| + length -= start; | |
| } | |
| } | |
| @@ -496,14 +542,56 @@ njs_array_prototype_slice(njs_vm_t *vm, | |
| vm->retval.data.truth = 1; | |
| if (length != 0) { | |
| - value = args[0].data.u.array->start; | |
| n = 0; | |
| - | |
| - do { | |
| - /* GC: retain long string and object in values[start]. */ | |
| - array->start[n++] = value[start++]; | |
| - length--; | |
| - } while (length != 0); | |
| + this = &args[0]; | |
| + | |
| + if (nxt_fast_path(njs_is_array(this))) { | |
| + value = this->data.u.array->start; | |
| + | |
| + do { | |
| + /* GC: retain long string and object in values[start]. */ | |
| + array->start[n++] = value[start++]; | |
| + length--; | |
| + } while (length != 0); | |
| + | |
| + } else if (njs_is_string(this) || this->type == NJS_OBJECT_STRING) { | |
| + | |
| + if (this->type == NJS_OBJECT_STRING) { | |
| + this = &this->data.u.object_value->value; | |
| + } | |
| + | |
| + string_slice.start = start; | |
| + string_slice.length = length; | |
| + string_slice.string_length = njs_string_prop(&string, this); | |
| + | |
| + njs_string_slice_string_prop(&string, &string, &string_slice); | |
| + | |
| + src = string.start; | |
| + last = src + string.size; | |
| + | |
| + do { | |
| + value = &array->start[n++]; | |
| + dst = njs_string_short_start(value); | |
| + dst = nxt_utf8_copy(dst, &src, last); | |
| + size = dst - njs_string_short_start(value); | |
| + njs_string_short_set(value, size, (size == 1) ? 0 : 1); | |
| + | |
| + length--; | |
| + } while (length != 0); | |
| + | |
| + | |
| + } else if (njs_is_object(this)) { | |
| + | |
| + do { | |
| + njs_u32_to_string(&name, start++); | |
| + | |
| + njs_value_property(vm, this, &name, &retval); | |
| + | |
| + array->start[n++] = (!njs_is_void(&retval)) ? retval | |
| + : njs_value_invalid; | |
| + length--; | |
| + } while (length != 0); | |
| + } | |
| } | |
| return NXT_OK; | |
| @@ -2101,7 +2189,8 @@ static const njs_object_prop_t njs_arra | |
| { | |
| .type = NJS_METHOD, | |
| .name = njs_string("slice"), | |
| - .value = njs_native_function(njs_array_prototype_slice, 0, | |
| + .value = njs_native_function(njs_array_prototype_slice, | |
| + njs_continuation_size(njs_array_slice_t), | |
| NJS_OBJECT_ARG, NJS_INTEGER_ARG, NJS_INTEGER_ARG), | |
| }, | |
| diff --git a/njs/njs_object_hash.h b/njs/njs_object_hash.h | |
| --- a/njs/njs_object_hash.h | |
| +++ b/njs/njs_object_hash.h | |
| @@ -108,6 +108,16 @@ | |
| 'j'), 'o'), 'i'), 'n') | |
| +#define NJS_LENGTH_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, \ | |
| + 'l'), 'e'), 'n'), 'g'), 't'), 'h') | |
| + | |
| + | |
| #define NJS_NAME_HASH \ | |
| nxt_djb_hash_add( \ | |
| nxt_djb_hash_add( \ | |
| diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c | |
| --- a/njs/test/njs_unit_test.c | |
| +++ b/njs/test/njs_unit_test.c | |
| @@ -2981,12 +2981,87 @@ static njs_unit_test_t njs_test[] = | |
| { nxt_string("Array.prototype.slice(1,2)"), | |
| nxt_string("") }, | |
| + { nxt_string("Array.prototype.slice.call(undefined)"), | |
| + nxt_string("TypeError: cannot convert void to object") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call(1)"), | |
| + nxt_string("") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:1})"), | |
| + nxt_string("a") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:2})"), | |
| + nxt_string("a,b") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:4})"), | |
| + nxt_string("a,b,,") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:2}, 1)"), | |
| + nxt_string("b") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call({'0':'a', '1':'b', length:2}, 1, 2)"), | |
| + nxt_string("b") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call({length:'2'})"), | |
| + nxt_string(",") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call({length:new Number(3)})"), | |
| + nxt_string(",,") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call({length: { valueOf: function() { return 2; } }})"), | |
| + nxt_string(",") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call({ length: Object.create(null) })"), | |
| + nxt_string("TypeError: Cannot convert object to primitive value") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call({length:-1})"), | |
| + nxt_string("MemoryError") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call('αβZγ')"), | |
| + nxt_string("α,β,Z,γ") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call('αβZγ', 1)"), | |
| + nxt_string("β,Z,γ") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call('αβZγ', 2)"), | |
| + nxt_string("Z,γ") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call('αβZγ', 3)"), | |
| + nxt_string("γ") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call('αβZγ', 4)"), | |
| + nxt_string("") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call('αβZγ', 0, 1)"), | |
| + nxt_string("α") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call('αβZγ', 1, 2)"), | |
| + nxt_string("β") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call('αβZγ').length"), | |
| + nxt_string("4") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call('αβZγ')[1].length"), | |
| + nxt_string("1") }, | |
| + | |
| + { nxt_string("Array.prototype.slice.call(new String('αβZγ'))"), | |
| + nxt_string("α,β,Z,γ") }, | |
| + | |
| { nxt_string("Array.prototype.pop()"), | |
| nxt_string("undefined") }, | |
| { nxt_string("Array.prototype.shift()"), | |
| nxt_string("undefined") }, | |
| + { nxt_string("[0,1].slice()"), | |
| + nxt_string("0,1") }, | |
| + | |
| + { nxt_string("[0,1].slice(undefined)"), | |
| + nxt_string("0,1") }, | |
| + | |
| + { nxt_string("[0,1].slice(undefined, undefined)"), | |
| + nxt_string("0,1") }, | |
| + | |
| { nxt_string("[0,1,2,3,4].slice(1,4)"), | |
| nxt_string("1,2,3") }, | |
| @@ -9442,6 +9517,9 @@ static njs_unit_test_t njs_test[] = | |
| { nxt_string("njs.dump({a:1, b:[1,,2,{c:new Boolean(1)}]})"), | |
| nxt_string("{a:1,b:[1,<empty>,2,{c:[Boolean: true]}]}") }, | |
| + { nxt_string("njs.dump(Array.prototype.slice.call({'1':'b', length:2}))"), | |
| + nxt_string("[<empty>,'b']") }, | |
| + | |
| { nxt_string("njs.dump($r.props)"), | |
| nxt_string("{a:{type:\"property\",props:[\"getter\"]},b:{type:\"property\",props:[\"getter\"]}}") }, | |
| # HG changeset patch | |
| # User Dmitry Volyntsev <[email protected]> | |
| # Date 1536849246 -10800 | |
| # Thu Sep 13 17:34:06 2018 +0300 | |
| # Node ID 697e8ee2fd4a05a6979c7b7530d2605168946282 | |
| # Parent 8fa1991a255eb9c8ca6147f94d78fa9594887879 | |
| Added arguments object. | |
| This closes #19 issue on Github. | |
| diff --git a/njs/njs_disassembler.c b/njs/njs_disassembler.c | |
| --- a/njs/njs_disassembler.c | |
| +++ b/njs/njs_disassembler.c | |
| @@ -24,6 +24,8 @@ static njs_code_name_t code_names[] = { | |
| nxt_string("OBJECT ") }, | |
| { njs_vmcode_function, sizeof(njs_vmcode_function_t), | |
| nxt_string("FUNCTION ") }, | |
| + { njs_vmcode_arguments, sizeof(njs_vmcode_arguments_t), | |
| + nxt_string("ARGUMENTS ") }, | |
| { njs_vmcode_regexp, sizeof(njs_vmcode_regexp_t), | |
| nxt_string("REGEXP ") }, | |
| { njs_vmcode_object_copy, sizeof(njs_vmcode_object_copy_t), | |
| diff --git a/njs/njs_function.c b/njs/njs_function.c | |
| --- a/njs/njs_function.c | |
| +++ b/njs/njs_function.c | |
| @@ -8,6 +8,8 @@ | |
| #include <string.h> | |
| +static njs_ret_t njs_function_arguments_thrower(njs_vm_t *vm, | |
| + njs_value_t *value, njs_value_t *setval, njs_value_t *retval); | |
| static njs_ret_t njs_function_activate(njs_vm_t *vm, njs_function_t *function, | |
| njs_value_t *this, njs_value_t *args, nxt_uint_t nargs, njs_index_t retval); | |
| @@ -95,6 +97,137 @@ njs_function_value_copy(njs_vm_t *vm, nj | |
| } | |
| +/* | |
| + * ES5.1, 10.6: CreateArgumentsObject. | |
| + */ | |
| +njs_ret_t | |
| +njs_function_arguments_object_init(njs_vm_t *vm, njs_native_frame_t *frame) | |
| +{ | |
| + nxt_int_t ret; | |
| + nxt_uint_t nargs, n; | |
| + njs_value_t val, *value; | |
| + njs_object_t *obj; | |
| + njs_object_prop_t *prop; | |
| + nxt_lvlhsh_query_t lhq; | |
| + | |
| + static const njs_value_t njs_string_length = njs_string("length"); | |
| + static const njs_value_t njs_string_callee = njs_string("callee"); | |
| + static const njs_value_t njs_string_caller = njs_string("caller"); | |
| + | |
| + obj = njs_object_alloc(vm); | |
| + if (nxt_slow_path(obj == NULL)) { | |
| + return NXT_ERROR; | |
| + } | |
| + | |
| + nargs = frame->nargs; | |
| + | |
| + value = &val; | |
| + njs_value_number_set(value, nargs); | |
| + | |
| + prop = njs_object_prop_alloc(vm, &njs_string_length, value, 1); | |
| + if (nxt_slow_path(prop == NULL)) { | |
| + return NXT_ERROR; | |
| + } | |
| + | |
| + prop->enumerable = 0; | |
| + | |
| + lhq.value = prop; | |
| + lhq.key_hash = NJS_LENGTH_HASH; | |
| + njs_string_get(&prop->name, &lhq.key); | |
| + | |
| + lhq.replace = 0; | |
| + lhq.pool = vm->mem_cache_pool; | |
| + lhq.proto = &njs_object_hash_proto; | |
| + | |
| + ret = nxt_lvlhsh_insert(&obj->hash, &lhq); | |
| + | |
| + if (nxt_slow_path(ret != NXT_OK)) { | |
| + njs_internal_error(vm, "lvlhsh insert failed"); | |
| + return NXT_ERROR; | |
| + } | |
| + | |
| + prop = njs_object_prop_handler_alloc(vm, &njs_string_callee, | |
| + njs_function_arguments_thrower, 0); | |
| + if (nxt_slow_path(prop == NULL)) { | |
| + return NXT_ERROR; | |
| + } | |
| + | |
| + lhq.value = prop; | |
| + njs_string_get(&prop->name, &lhq.key); | |
| + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); | |
| + | |
| + lhq.replace = 0; | |
| + lhq.pool = vm->mem_cache_pool; | |
| + lhq.proto = &njs_object_hash_proto; | |
| + | |
| + ret = nxt_lvlhsh_insert(&obj->hash, &lhq); | |
| + | |
| + if (nxt_slow_path(ret != NXT_OK)) { | |
| + njs_internal_error(vm, "lvlhsh insert failed"); | |
| + return NXT_ERROR; | |
| + } | |
| + | |
| + prop = njs_object_prop_handler_alloc(vm, &njs_string_caller, | |
| + njs_function_arguments_thrower, 0); | |
| + if (nxt_slow_path(prop == NULL)) { | |
| + return NXT_ERROR; | |
| + } | |
| + | |
| + lhq.value = prop; | |
| + njs_string_get(&prop->name, &lhq.key); | |
| + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); | |
| + | |
| + lhq.replace = 0; | |
| + lhq.pool = vm->mem_cache_pool; | |
| + lhq.proto = &njs_object_hash_proto; | |
| + | |
| + ret = nxt_lvlhsh_insert(&obj->hash, &lhq); | |
| + | |
| + if (nxt_slow_path(ret != NXT_OK)) { | |
| + njs_internal_error(vm, "lvlhsh insert failed"); | |
| + return NXT_ERROR; | |
| + } | |
| + | |
| + for (n = 0; n < nargs; n++) { | |
| + njs_u32_to_string(&val, n); | |
| + | |
| + prop = njs_object_prop_alloc(vm, &val, &frame->arguments[n + 1], 1); | |
| + if (nxt_slow_path(prop == NULL)) { | |
| + return NXT_ERROR; | |
| + } | |
| + | |
| + lhq.value = prop; | |
| + njs_string_get(&prop->name, &lhq.key); | |
| + lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length); | |
| + | |
| + lhq.replace = 0; | |
| + lhq.pool = vm->mem_cache_pool; | |
| + lhq.proto = &njs_object_hash_proto; | |
| + | |
| + ret = nxt_lvlhsh_insert(&obj->hash, &lhq); | |
| + | |
| + if (nxt_slow_path(ret != NXT_OK)) { | |
| + njs_internal_error(vm, "lvlhsh insert failed"); | |
| + return NXT_ERROR; | |
| + } | |
| + | |
| + } | |
| + | |
| + frame->arguments_object = obj; | |
| + | |
| + return NXT_OK; | |
| +} | |
| + | |
| + | |
| +static njs_ret_t | |
| +njs_function_arguments_thrower(njs_vm_t *vm, njs_value_t *value, | |
| + njs_value_t *setval, njs_value_t *retval) | |
| +{ | |
| + njs_type_error(vm, "'caller', 'callee' properties may not be accessed"); | |
| + return NXT_ERROR; | |
| +} | |
| + | |
| + | |
| njs_ret_t | |
| njs_function_native_frame(njs_vm_t *vm, njs_function_t *function, | |
| const njs_value_t *this, njs_value_t *args, nxt_uint_t nargs, | |
| @@ -315,6 +448,7 @@ nxt_noinline njs_ret_t | |
| njs_function_call(njs_vm_t *vm, njs_index_t retval, size_t advance) | |
| { | |
| size_t size; | |
| + njs_ret_t ret; | |
| nxt_uint_t n, nesting; | |
| njs_frame_t *frame; | |
| njs_value_t *value; | |
| @@ -393,6 +527,13 @@ njs_function_call(njs_vm_t *vm, njs_inde | |
| vm->scopes[NJS_SCOPE_CLOSURE + n] = &closure->u.values; | |
| } | |
| + if (lambda->arguments_object) { | |
| + ret = njs_function_arguments_object_init(vm, &frame->native); | |
| + if (nxt_slow_path(ret != NXT_OK)) { | |
| + return NXT_ERROR; | |
| + } | |
| + } | |
| + | |
| vm->active_frame = frame; | |
| return NJS_APPLIED; | |
| diff --git a/njs/njs_function.h b/njs/njs_function.h | |
| --- a/njs/njs_function.h | |
| +++ b/njs/njs_function.h | |
| @@ -30,6 +30,8 @@ struct njs_function_lambda_s { | |
| /* Function internal block closures levels. */ | |
| uint8_t block_closures; /* 4 bits */ | |
| + uint8_t arguments_object;/* 1 bit */ | |
| + | |
| /* Initial values of local scope. */ | |
| njs_value_t *local_scope; | |
| @@ -102,7 +104,9 @@ struct njs_native_frame_s { | |
| njs_function_t *function; | |
| njs_native_frame_t *previous; | |
| + | |
| njs_value_t *arguments; | |
| + njs_object_t *arguments_object; | |
| njs_exception_t exception; | |
| @@ -147,6 +151,8 @@ struct njs_frame_s { | |
| njs_function_t *njs_function_alloc(njs_vm_t *vm); | |
| njs_function_t *njs_function_value_copy(njs_vm_t *vm, njs_value_t *value); | |
| njs_native_frame_t *njs_function_frame_alloc(njs_vm_t *vm, size_t size); | |
| +njs_ret_t njs_function_arguments_object_init(njs_vm_t *vm, | |
| + njs_native_frame_t *frame); | |
| njs_ret_t njs_function_prototype_create(njs_vm_t *vm, njs_value_t *value, | |
| njs_value_t *setval, njs_value_t *retval); | |
| njs_value_t *njs_function_property_prototype_create(njs_vm_t *vm, | |
| diff --git a/njs/njs_generator.c b/njs/njs_generator.c | |
| --- a/njs/njs_generator.c | |
| +++ b/njs/njs_generator.c | |
| @@ -14,6 +14,8 @@ static nxt_int_t njs_generate_name(njs_v | |
| njs_parser_node_t *node); | |
| static nxt_int_t njs_generate_builtin_object(njs_vm_t *vm, njs_parser_t *parser, | |
| njs_parser_node_t *node); | |
| +static nxt_int_t njs_generate_arguments_object(njs_vm_t *vm, | |
| + njs_parser_t *parser, njs_parser_node_t *node); | |
| static nxt_int_t njs_generate_variable(njs_vm_t *vm, njs_parser_t *parser, | |
| njs_parser_node_t *node); | |
| static nxt_int_t njs_generate_var_statement(njs_vm_t *vm, njs_parser_t *parser, | |
| @@ -308,6 +310,9 @@ njs_generator(njs_vm_t *vm, njs_parser_t | |
| case NJS_TOKEN_CLEAR_TIMEOUT: | |
| return njs_generate_builtin_object(vm, parser, node); | |
| + case NJS_TOKEN_ARGUMENTS: | |
| + return njs_generate_arguments_object(vm, parser, node); | |
| + | |
| case NJS_TOKEN_FUNCTION: | |
| return njs_generate_function_declaration(vm, parser, node); | |
| @@ -396,6 +401,29 @@ njs_generate_builtin_object(njs_vm_t *vm | |
| static nxt_int_t | |
| +njs_generate_arguments_object(njs_vm_t *vm, njs_parser_t *parser, | |
| + njs_parser_node_t *node) | |
| +{ | |
| + njs_vmcode_arguments_t *gen; | |
| + | |
| + parser->arguments_object = 1; | |
| + | |
| + node->index = njs_generator_object_dest_index(vm, parser, node); | |
| + if (nxt_slow_path(node->index == NJS_INDEX_ERROR)) { | |
| + return NXT_ERROR; | |
| + } | |
| + | |
| + njs_generate_code(parser, njs_vmcode_arguments_t, gen); | |
| + gen->code.operation = njs_vmcode_arguments; | |
| + gen->code.operands = NJS_VMCODE_1OPERAND; | |
| + gen->code.retval = NJS_VMCODE_RETVAL; | |
| + gen->retval = node->index; | |
| + | |
| + return NXT_OK; | |
| +} | |
| + | |
| + | |
| +static nxt_int_t | |
| njs_generate_variable(njs_vm_t *vm, njs_parser_t *parser, | |
| njs_parser_node_t *node) | |
| { | |
| @@ -2010,6 +2038,7 @@ njs_generate_function_scope(njs_vm_t *vm | |
| parser->code_size += node->scope->argument_closures | |
| * sizeof(njs_vmcode_move_t); | |
| + parser->arguments_object = 0; | |
| ret = njs_generate_scope(vm, parser, node); | |
| if (nxt_fast_path(ret == NXT_OK)) { | |
| @@ -2023,6 +2052,7 @@ njs_generate_function_scope(njs_vm_t *vm | |
| lambda->nesting = node->scope->nesting; | |
| lambda->closure_size = size; | |
| + lambda->arguments_object = parser->arguments_object; | |
| lambda->local_size = parser->scope_size; | |
| lambda->local_scope = parser->local_scope; | |
| diff --git a/njs/njs_lexer_keyword.c b/njs/njs_lexer_keyword.c | |
| --- a/njs/njs_lexer_keyword.c | |
| +++ b/njs/njs_lexer_keyword.c | |
| @@ -53,6 +53,7 @@ static const njs_keyword_t njs_keywords | |
| /* Builtin objects. */ | |
| { nxt_string("this"), NJS_TOKEN_THIS, 0 }, | |
| + { nxt_string("arguments"), NJS_TOKEN_ARGUMENTS, 0 }, | |
| { nxt_string("njs"), NJS_TOKEN_NJS, 0 }, | |
| { nxt_string("Math"), NJS_TOKEN_MATH, 0 }, | |
| { nxt_string("JSON"), NJS_TOKEN_JSON, 0 }, | |
| diff --git a/njs/njs_object.c b/njs/njs_object.c | |
| --- a/njs/njs_object.c | |
| +++ b/njs/njs_object.c | |
| @@ -203,6 +203,36 @@ njs_object_prop_alloc(njs_vm_t *vm, cons | |
| nxt_noinline njs_object_prop_t * | |
| +njs_object_prop_handler_alloc(njs_vm_t *vm, const njs_value_t *name, | |
| + njs_prop_handler_t handler, uint8_t attributes) | |
| +{ | |
| + njs_object_prop_t *prop; | |
| + | |
| + prop = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t), | |
| + sizeof(njs_object_prop_t)); | |
| + | |
| + if (nxt_fast_path(prop != NULL)) { | |
| + /* GC: retain. */ | |
| + njs_set_invalid(&prop->value); | |
| + prop->value.data.u.prop_handler = handler; | |
| + | |
| + /* GC: retain. */ | |
| + prop->name = *name; | |
| + | |
| + prop->type = NJS_PROPERTY_HANDLER; | |
| + prop->enumerable = attributes; | |
| + prop->writable = attributes; | |
| + prop->configurable = attributes; | |
| + return prop; | |
| + } | |
| + | |
| + njs_memory_error(vm); | |
| + | |
| + return NULL; | |
| +} | |
| + | |
| + | |
| +nxt_noinline njs_object_prop_t * | |
| njs_object_property(njs_vm_t *vm, const njs_object_t *object, | |
| nxt_lvlhsh_query_t *lhq) | |
| { | |
| @@ -455,6 +485,22 @@ njs_object_property_query(njs_vm_t *vm, | |
| if (ret == NXT_OK) { | |
| prop = pq->lhq.value; | |
| + if (pq->query == NJS_PROPERTY_QUERY_GET) { | |
| + prop = pq->lhq.value; | |
| + | |
| + if (prop->type == NJS_PROPERTY_HANDLER) { | |
| + pq->scratch = *prop; | |
| + prop = &pq->scratch; | |
| + ret = prop->value.data.u.prop_handler(vm, value, NULL, | |
| + &prop->value); | |
| + | |
| + if (nxt_fast_path(ret == NXT_OK)) { | |
| + prop->type = NJS_PROPERTY; | |
| + pq->lhq.value = prop; | |
| + } | |
| + } | |
| + } | |
| + | |
| if (prop->type != NJS_WHITEOUT) { | |
| pq->shared = 1; | |
| diff --git a/njs/njs_object.h b/njs/njs_object.h | |
| --- a/njs/njs_object.h | |
| +++ b/njs/njs_object.h | |
| @@ -87,6 +87,8 @@ njs_ret_t njs_object_constructor(njs_vm_ | |
| nxt_uint_t nargs, njs_index_t unused); | |
| njs_object_prop_t *njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name, | |
| const njs_value_t *value, uint8_t attributes); | |
| +nxt_noinline njs_object_prop_t *njs_object_prop_handler_alloc(njs_vm_t *vm, | |
| + const njs_value_t *name, njs_prop_handler_t handler, uint8_t attributes); | |
| njs_ret_t njs_primitive_prototype_get_proto(njs_vm_t *vm, njs_value_t *value, | |
| njs_value_t *setval, njs_value_t *retval); | |
| njs_ret_t njs_object_prototype_create(njs_vm_t *vm, njs_value_t *value, | |
| diff --git a/njs/njs_parser.c b/njs/njs_parser.c | |
| --- a/njs/njs_parser.c | |
| +++ b/njs/njs_parser.c | |
| @@ -455,6 +455,13 @@ njs_parser_function_declaration(njs_vm_t | |
| } | |
| if (token != NJS_TOKEN_NAME) { | |
| + if (token == NJS_TOKEN_ARGUMENTS || token == NJS_TOKEN_EVAL) { | |
| + njs_parser_syntax_error(vm, parser, "Identifier \"%.*s\" " | |
| + "is forbidden in function declaration", | |
| + (int) parser->lexer->text.length, | |
| + parser->lexer->text.start); | |
| + } | |
| + | |
| return NJS_TOKEN_ILLEGAL; | |
| } | |
| @@ -821,6 +828,13 @@ njs_parser_var_statement(njs_vm_t *vm, n | |
| } | |
| if (token != NJS_TOKEN_NAME) { | |
| + if (token == NJS_TOKEN_ARGUMENTS || token == NJS_TOKEN_EVAL) { | |
| + njs_parser_syntax_error(vm, parser, "Identifier \"%.*s\" " | |
| + "is forbidden in var declaration", | |
| + (int) parser->lexer->text.length, | |
| + parser->lexer->text.start); | |
| + } | |
| + | |
| return NJS_TOKEN_ILLEGAL; | |
| } | |
| @@ -1306,6 +1320,13 @@ njs_parser_for_var_statement(njs_vm_t *v | |
| } | |
| if (token != NJS_TOKEN_NAME) { | |
| + if (token == NJS_TOKEN_ARGUMENTS || token == NJS_TOKEN_EVAL) { | |
| + njs_parser_syntax_error(vm, parser, "Identifier \"%.*s\" " | |
| + "is forbidden in for-in var declaration", | |
| + (int) parser->lexer->text.length, | |
| + parser->lexer->text.start); | |
| + } | |
| + | |
| return NJS_TOKEN_ILLEGAL; | |
| } | |
| @@ -1973,6 +1994,24 @@ njs_parser_terminal(njs_vm_t *vm, njs_pa | |
| case NJS_TOKEN_JSON: | |
| return njs_parser_builtin_object(vm, parser, node); | |
| + case NJS_TOKEN_ARGUMENTS: | |
| + nxt_thread_log_debug("JS: arguments"); | |
| + | |
| + if (parser->scope->type <= NJS_SCOPE_GLOBAL) { | |
| + njs_parser_syntax_error(vm, parser, "\"%.*s\" object " | |
| + "in global scope", | |
| + (int) parser->lexer->text.length, | |
| + parser->lexer->text.start); | |
| + | |
| + return NJS_TOKEN_ILLEGAL; | |
| + } | |
| + | |
| + node->token = NJS_TOKEN_ARGUMENTS; | |
| + | |
| + parser->code_size += sizeof(njs_vmcode_arguments_t); | |
| + | |
| + break; | |
| + | |
| case NJS_TOKEN_OBJECT_CONSTRUCTOR: | |
| node->index = NJS_INDEX_OBJECT; | |
| break; | |
| diff --git a/njs/njs_parser.h b/njs/njs_parser.h | |
| --- a/njs/njs_parser.h | |
| +++ b/njs/njs_parser.h | |
| @@ -161,6 +161,7 @@ typedef enum { | |
| NJS_TOKEN_THROW, | |
| NJS_TOKEN_THIS, | |
| + NJS_TOKEN_ARGUMENTS, | |
| #define NJS_TOKEN_FIRST_OBJECT NJS_TOKEN_GLOBAL_THIS | |
| @@ -346,6 +347,8 @@ struct njs_parser_s { | |
| u_char *code_end; | |
| njs_parser_t *parent; | |
| + | |
| + nxt_uint_t arguments_object; | |
| }; | |
| diff --git a/njs/njs_parser_expression.c b/njs/njs_parser_expression.c | |
| --- a/njs/njs_parser_expression.c | |
| +++ b/njs/njs_parser_expression.c | |
| @@ -414,8 +414,19 @@ njs_parser_assignment_expression(njs_vm_ | |
| } | |
| if (!njs_parser_is_lvalue(parser->node)) { | |
| - njs_parser_ref_error(vm, parser, | |
| - "Invalid left-hand side in assignment"); | |
| + token = parser->node->token; | |
| + | |
| + if (token == NJS_TOKEN_ARGUMENTS || token == NJS_TOKEN_EVAL) { | |
| + njs_parser_syntax_error(vm, parser, "Identifier \"%s\" " | |
| + "is forbidden as left-hand in assignment", | |
| + (token == NJS_TOKEN_EVAL) ? "eval" | |
| + : "arguments"); | |
| + | |
| + } else { | |
| + njs_parser_ref_error(vm, parser, | |
| + "Invalid left-hand side in assignment"); | |
| + } | |
| + | |
| return NJS_TOKEN_ILLEGAL; | |
| } | |
| diff --git a/njs/njs_vm.c b/njs/njs_vm.c | |
| --- a/njs/njs_vm.c | |
| +++ b/njs/njs_vm.c | |
| @@ -415,6 +415,29 @@ njs_vmcode_function(njs_vm_t *vm, njs_va | |
| njs_ret_t | |
| +njs_vmcode_arguments(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2) | |
| +{ | |
| + njs_ret_t ret; | |
| + njs_frame_t *frame; | |
| + | |
| + frame = (njs_frame_t *) vm->active_frame; | |
| + | |
| + if (frame->native.arguments_object == NULL) { | |
| + ret = njs_function_arguments_object_init(vm, &frame->native); | |
| + if (nxt_slow_path(ret != NXT_OK)) { | |
| + return NXT_ERROR; | |
| + } | |
| + } | |
| + | |
| + vm->retval.data.u.object = frame->native.arguments_object; | |
| + vm->retval.type = NJS_OBJECT; | |
| + vm->retval.data.truth = 1; | |
| + | |
| + return sizeof(njs_vmcode_arguments_t); | |
| +} | |
| + | |
| + | |
| +njs_ret_t | |
| njs_vmcode_regexp(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2) | |
| { | |
| njs_regexp_t *regexp; | |
| diff --git a/njs/njs_vm.h b/njs/njs_vm.h | |
| --- a/njs/njs_vm.h | |
| +++ b/njs/njs_vm.h | |
| @@ -644,6 +644,12 @@ typedef struct { | |
| typedef struct { | |
| njs_vmcode_t code; | |
| njs_index_t retval; | |
| +} njs_vmcode_arguments_t; | |
| + | |
| + | |
| +typedef struct { | |
| + njs_vmcode_t code; | |
| + njs_index_t retval; | |
| uintptr_t length; | |
| } njs_vmcode_array_t; | |
| @@ -1112,6 +1118,8 @@ njs_ret_t njs_vmcode_array(njs_vm_t *vm, | |
| njs_value_t *inlvd2); | |
| njs_ret_t njs_vmcode_function(njs_vm_t *vm, njs_value_t *inlvd1, | |
| njs_value_t *invld2); | |
| +njs_ret_t njs_vmcode_arguments(njs_vm_t *vm, njs_value_t *inlvd1, | |
| + njs_value_t *invld2); | |
| njs_ret_t njs_vmcode_regexp(njs_vm_t *vm, njs_value_t *inlvd1, | |
| njs_value_t *invld2); | |
| njs_ret_t njs_vmcode_object_copy(njs_vm_t *vm, njs_value_t *value, | |
| diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c | |
| --- a/njs/test/njs_unit_test.c | |
| +++ b/njs/test/njs_unit_test.c | |
| @@ -5568,6 +5568,77 @@ static njs_unit_test_t njs_test[] = | |
| "var b = a(); b(2)"), | |
| nxt_string("3") }, | |
| + /* arguments object. */ | |
| + | |
| + { nxt_string("var arguments"), | |
| + nxt_string("SyntaxError: Identifier \"arguments\" is forbidden in var declaration in 1") }, | |
| + | |
| + { nxt_string("for (var arguments in []) {}"), | |
| + nxt_string("SyntaxError: Identifier \"arguments\" is forbidden in for-in var declaration in 1") }, | |
| + | |
| + { nxt_string("function arguments(){}"), | |
| + nxt_string("SyntaxError: Identifier \"arguments\" is forbidden in function declaration in 1") }, | |
| + | |
| + { nxt_string("(function () {arguments = [];})"), | |
| + nxt_string("SyntaxError: Identifier \"arguments\" is forbidden as left-hand in assignment in 1") }, | |
| + | |
| + { nxt_string("(function(){return arguments[0];})(1,2,3)"), | |
| + nxt_string("1") }, | |
| + | |
| + { nxt_string("(function(){return arguments[2];})(1,2,3)"), | |
| + nxt_string("3") }, | |
| + | |
| + { nxt_string("(function(){return arguments[3];})(1,2,3)"), | |
| + nxt_string("undefined") }, | |
| + | |
| + { nxt_string("(function(a,b,c){return a;})(1,2,3)"), | |
| + nxt_string("1") }, | |
| + | |
| + { nxt_string("(function(a,b,c){arguments[0] = 4; return a;})(1,2,3)"), | |
| + nxt_string("1") }, | |
| + | |
| + { nxt_string("(function(a,b,c){a = 4; return arguments[0];})(1,2,3)"), | |
| + nxt_string("1") }, | |
| + | |
| + { nxt_string("function check(v) {if (v == false) {throw TypeError('Too few arguments')}}; " | |
| + "function f() {check(arguments.length > 1); return 1}; f()"), | |
| + nxt_string("TypeError: Too few arguments") }, | |
| + | |
| + { nxt_string("function check(v) {if (v == false) {throw TypeError('Too few arguments')}}; " | |
| + "function f() {check(arguments.length > 1); return 1}; f(1,2)"), | |
| + nxt_string("1") }, | |
| + | |
| + { nxt_string("(function(a,b){delete arguments[0]; return arguments[0]})(1,1)"), | |
| + nxt_string("undefined") }, | |
| + | |
| + { nxt_string("(function(){return arguments.length;})()"), | |
| + nxt_string("0") }, | |
| + | |
| + { nxt_string("(function(){return arguments.length;})(1,2,3)"), | |
| + nxt_string("3") }, | |
| + | |
| + { nxt_string("(function(){arguments.length = 1; return arguments.length;})(1,2,3)"), | |
| + nxt_string("1") }, | |
| + | |
| + { nxt_string("(function(){return arguments.callee;})()"), | |
| + nxt_string("TypeError: 'caller', 'callee' properties may not be accessed") }, | |
| + | |
| + { nxt_string("(function(){return arguments.caller;})()"), | |
| + nxt_string("TypeError: 'caller', 'callee' properties may not be accessed") }, | |
| + | |
| + { nxt_string("(function(){return arguments.callee;})()"), | |
| + nxt_string("TypeError: 'caller', 'callee' properties may not be accessed") }, | |
| + | |
| + { nxt_string("function sum() { var args = Array.prototype.slice.call(arguments); " | |
| + "return args.reduce(function(prev, curr) {return prev + curr})};" | |
| + "[sum(1), sum(1,2), sum(1,2,3), sum(1,2,3,4)]"), | |
| + nxt_string("1,3,6,10") }, | |
| + | |
| + { nxt_string("function concat(sep) { var args = Array.prototype.slice.call(arguments, 1); " | |
| + "return args.join(sep)};" | |
| + "[concat('.',1,2,3), concat('+',1,2,3,4)]"), | |
| + nxt_string("1.2.3,1+2+3+4") }, | |
| + | |
| /* Scopes. */ | |
| { nxt_string("function f(x) { a = x } var a; f(5); a"), |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment