From 158f54e6e8d87eaa9d817147a9022bcd7563434f Mon Sep 17 00:00:00 2001 From: Alexander Borisov Date: Fri, 30 Apr 2021 16:02:28 +0300 Subject: [PATCH] Introduced let implementation. This closes #105 issue on GitHub. --- src/njs_builtin.c | 4 + src/njs_disassembler.c | 11 ++ src/njs_generator.c | 330 ++++++++++++++++++++++++++++++++++++--- src/njs_module.c | 3 +- src/njs_parser.c | 222 +++++++++++++++++++++----- src/njs_parser.h | 5 + src/njs_scope.c | 6 +- src/njs_scope.h | 32 +++- src/njs_variable.c | 56 +++++-- src/njs_variable.h | 3 +- src/njs_vmcode.c | 117 ++++++++++---- src/njs_vmcode.h | 12 ++ src/test/njs_benchmark.c | 11 ++ src/test/njs_unit_test.c | 237 ++++++++++++++++++++++++++++ 14 files changed, 938 insertions(+), 111 deletions(-) diff --git a/src/njs_builtin.c b/src/njs_builtin.c index dae0710f..abb6ceee 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -972,6 +972,10 @@ njs_global_this_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop, var = node->variable; + if (var->type == NJS_VARIABLE_LET) { + return NJS_DECLINED; + } + value = njs_scope_valid_value(vm, var->index); if (var->type == NJS_VARIABLE_FUNCTION && njs_is_undefined(value)) { diff --git a/src/njs_disassembler.c b/src/njs_disassembler.c index 87d6fdda..3a193871 100644 --- a/src/njs_disassembler.c +++ b/src/njs_disassembler.c @@ -139,6 +139,17 @@ static njs_code_name_t code_names[] = { { NJS_VMCODE_THROW, sizeof(njs_vmcode_throw_t), njs_str("THROW ") }, + { NJS_VMCODE_LET, sizeof(njs_vmcode_variable_t), + njs_str("LET ") }, + + { NJS_VMCODE_LET_UPDATE, sizeof(njs_vmcode_variable_t), + njs_str("LET UPDATE ") }, + + { NJS_VMCODE_INITIALIZATION_TEST, sizeof(njs_vmcode_variable_t), + njs_str("INIT TEST ") }, + + { NJS_VMCODE_NOT_INITIALIZED, sizeof(njs_vmcode_variable_t), + njs_str("NOT INIT ") }, }; diff --git a/src/njs_generator.c b/src/njs_generator.c index 4b1e7e30..55f274f7 100644 --- a/src/njs_generator.c +++ b/src/njs_generator.c @@ -67,6 +67,8 @@ static njs_int_t njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator, njs_variable_t **retvar); static njs_int_t njs_generate_var_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); +static njs_int_t njs_generate_let(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node, njs_variable_t *var); static njs_int_t njs_generate_if_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_cond_expression(njs_vm_t *vm, @@ -79,6 +81,10 @@ static njs_int_t njs_generate_do_while_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_for_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); +static njs_int_t njs_generate_for_let_update(njs_vm_t *vm, + njs_generator_t *generator, njs_parser_node_t *node, size_t depth); +static njs_int_t njs_generate_for_resolve_closure(njs_vm_t *vm, + njs_parser_node_t *node, size_t depth); static njs_int_t njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static njs_int_t njs_generate_start_block(njs_vm_t *vm, @@ -261,6 +267,9 @@ static njs_int_t njs_generate_reference_error(njs_vm_t *vm, ##__VA_ARGS__) +#define NJS_GENERATE_MAX_DEPTH 4096 + + static const njs_str_t no_label = njs_str(""); static const njs_str_t return_label = njs_str("@return"); /* GCC and Clang complain about NULL argument passed to memcmp(). */ @@ -277,6 +286,7 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) switch (node->token_type) { case NJS_TOKEN_VAR: + case NJS_TOKEN_LET: return njs_generate_var_statement(vm, generator, node); case NJS_TOKEN_IF: @@ -479,7 +489,7 @@ njs_generator(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { njs_int_t ret; - if (njs_slow_path(generator->count++ > 4096)) { + if (njs_slow_path(generator->count++ > NJS_GENERATE_MAX_DEPTH)) { njs_range_error(vm, "Maximum call stack size exceeded"); return NJS_ERROR; } @@ -492,6 +502,25 @@ njs_generator(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) } +static njs_int_t +njs_generate_wo_dest(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node) +{ + njs_int_t ret; + njs_parser_scope_t *scope; + + scope = njs_function_scope(node->scope); + + scope->dest_disable = 1; + + ret = njs_generator(vm, generator, node); + + scope->dest_disable = 0; + + return ret; +} + + static u_char * njs_generate_reserve(njs_vm_t *vm, njs_generator_t *generator, size_t size) { @@ -591,6 +620,8 @@ njs_generate_name(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { njs_variable_t *var; + njs_parser_scope_t *scope; + njs_vmcode_variable_t *variable; njs_vmcode_function_copy_t *copy; var = njs_variable_reference(vm, node); @@ -605,6 +636,20 @@ njs_generate_name(njs_vm_t *vm, njs_generator_t *generator, copy->retval = node->index; } + if (var->init) { + return NJS_OK; + } + + if (var->type == NJS_VARIABLE_LET) { + scope = njs_function_scope(node->scope); + + if (scope->dest_disable) { + njs_generate_code(generator, njs_vmcode_variable_t, variable, + NJS_VMCODE_NOT_INITIALIZED, 1, node); + variable->dst = node->index; + } + } + return NJS_OK; } @@ -614,6 +659,8 @@ njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node, njs_reference_type_t type, njs_variable_t **retvar) { njs_variable_t *var; + njs_parser_scope_t *scope; + njs_vmcode_variable_t *variable; njs_vmcode_function_copy_t *copy; var = njs_variable_reference(vm, node); @@ -641,10 +688,43 @@ njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator, copy->retval = node->index; } + if (var->init) { + return NJS_OK; + } + + if (var->type == NJS_VARIABLE_LET) { + scope = njs_function_scope(node->scope); + + if ((!scope->dest_disable && njs_function_scope(var->scope) == scope)) { + njs_generate_code(generator, njs_vmcode_variable_t, variable, + NJS_VMCODE_NOT_INITIALIZED, 1, node); + variable->dst = node->index; + } + } + return NJS_OK; } +static njs_int_t +njs_generate_variable_wo_dest(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node, njs_reference_type_t type, njs_variable_t **retvar) +{ + njs_int_t ret; + njs_parser_scope_t *scope; + + scope = njs_function_scope(node->scope); + + scope->dest_disable = 1; + + ret = njs_generate_variable(vm, generator, node, type, retvar); + + scope->dest_disable = 0; + + return ret; +} + + static njs_int_t njs_generate_var_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) @@ -656,26 +736,50 @@ njs_generate_var_statement(njs_vm_t *vm, njs_generator_t *generator, lvalue = node->left; - ret = njs_generate_variable(vm, generator, lvalue, NJS_DECLARATION, &var); + ret = njs_generate_variable_wo_dest(vm, generator, lvalue, + NJS_DECLARATION, &var); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - lvalue->index = var->index; expr = node->right; if (expr == NULL) { /* Variable is only declared. */ + if (var->type == NJS_VARIABLE_LET) { + ret = njs_generate_let(vm, generator, node, var); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + + var->init = 1; + return NJS_OK; } - expr->dest = lvalue; + if (var->type == NJS_VARIABLE_LET) { + ret = njs_generate_wo_dest(vm, generator, expr); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } - ret = njs_generator(vm, generator, expr); - if (njs_slow_path(ret != NJS_OK)) { - return ret; + ret = njs_generate_let(vm, generator, node, var); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + } else { + expr->dest = lvalue; + + ret = njs_generator(vm, generator, expr); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } } + var->init = 1; + /* * lvalue and expression indexes are equal if the expression is an * empty object or expression result is stored directly in variable. @@ -692,6 +796,20 @@ njs_generate_var_statement(njs_vm_t *vm, njs_generator_t *generator, } +static njs_int_t +njs_generate_let(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node, njs_variable_t *var) +{ + njs_vmcode_variable_t *code; + + njs_generate_code(generator, njs_vmcode_variable_t, code, + NJS_VMCODE_LET, 0, node); + code->dst = var->index; + + return NJS_OK; +} + + static njs_int_t njs_generate_if_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) @@ -1089,7 +1207,7 @@ njs_generate_for_statement(njs_vm_t *vm, njs_generator_t *generator, { njs_int_t ret; njs_jump_off_t jump_offset, loop_offset; - njs_parser_node_t *condition, *update; + njs_parser_node_t *condition, *update, *init; njs_vmcode_jump_t *jump; njs_vmcode_cond_jump_t *cond_jump; @@ -1113,9 +1231,20 @@ njs_generate_for_statement(njs_vm_t *vm, njs_generator_t *generator, return ret; } + init = node->left; node = node->right; condition = node->left; + /* + * Closures can occur in conditional and loop updates. This must be + * foreseen in order to generate optimized code for let updates. + */ + + ret = njs_generate_for_resolve_closure(vm, condition, generator->count); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + /* GCC complains about uninitialized jump_offset. */ jump_offset = 0; @@ -1142,10 +1271,20 @@ njs_generate_for_statement(njs_vm_t *vm, njs_generator_t *generator, /* The loop update. */ - njs_generate_patch_block(vm, generator, generator->block->continuation); - update = node->right; + ret = njs_generate_for_resolve_closure(vm, update, generator->count); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_generate_for_let_update(vm, generator, init, generator->count); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + njs_generate_patch_block(vm, generator, generator->block->continuation); + ret = njs_generator(vm, generator, update); if (njs_slow_path(ret != NJS_OK)) { return ret; @@ -1185,14 +1324,96 @@ njs_generate_for_statement(njs_vm_t *vm, njs_generator_t *generator, } +static njs_int_t +njs_generate_for_let_update(njs_vm_t *vm, njs_generator_t *generator, + njs_parser_node_t *node, size_t depth) +{ + njs_parser_node_t *let; + njs_vmcode_variable_t *code_var; + njs_variable_reference_t *ref; + + if (node == NULL) { + return NJS_OK; + } + + if (depth >= NJS_GENERATE_MAX_DEPTH) { + return NJS_ERROR; + } + + if (node->token_type != NJS_TOKEN_STATEMENT) { + return NJS_OK; + } + + let = node->right; + + if (let->token_type != NJS_TOKEN_LET) { + return NJS_OK; + } + + ref = &let->left->u.reference; + + if (ref->variable->closure) { + njs_generate_code(generator, njs_vmcode_variable_t, code_var, + NJS_VMCODE_LET_UPDATE, 0, let); + code_var->dst = let->left->index; + } + + return njs_generate_for_let_update(vm, generator, node->left, depth + 1); +} + + +static njs_int_t +njs_generate_for_resolve_closure(njs_vm_t *vm, njs_parser_node_t *node, + size_t depth) +{ + njs_int_t ret; + njs_bool_t closure; + njs_variable_t *var; + + if (node == NULL) { + return NJS_OK; + } + + if (node->token_type == NJS_TOKEN_NAME) { + var = njs_variable_resolve(vm, node); + + if (njs_fast_path(var != NULL)) { + closure = njs_variable_closure_test(node->scope, var->scope); + + if (closure) { + var->closure = 1; + } + } + } + + if (depth >= NJS_GENERATE_MAX_DEPTH) { + njs_range_error(vm, "Maximum call stack size exceeded"); + return NJS_ERROR; + } + + ret = njs_generate_for_resolve_closure(vm, node->left, depth + 1); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_generate_for_resolve_closure(vm, node->right, depth + 1); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + return NJS_OK; +} + + static njs_int_t njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { njs_int_t ret; njs_index_t index; + njs_variable_t *var; njs_jump_off_t loop_offset, prop_offset; - njs_parser_node_t *foreach; + njs_parser_node_t *foreach, *name; njs_vmcode_prop_next_t *prop_next; njs_vmcode_prop_foreach_t *prop_foreach; @@ -1205,15 +1426,36 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator, /* The object. */ foreach = node->left; + name = foreach->left->right; - ret = njs_generator(vm, generator, foreach->left); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } + if (name != NULL) { + name = name->left; - ret = njs_generator(vm, generator, foreach->right); - if (njs_slow_path(ret != NJS_OK)) { - return ret; + ret = njs_generate_variable_wo_dest(vm, generator, name, + NJS_DECLARATION, &var); + if (njs_slow_path(ret != NJS_OK)) { + return NJS_ERROR; + } + + foreach->left->index = name->index; + + ret = njs_generator(vm, generator, foreach->right); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + var->init = 1; + + } else { + ret = njs_generator(vm, generator, foreach->left); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + ret = njs_generator(vm, generator, foreach->right); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } } njs_generate_code(generator, njs_vmcode_prop_foreach_t, prop_foreach, @@ -1239,6 +1481,14 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator, /* The loop iterator. */ + if (name != NULL) { + ret = njs_generate_for_let_update(vm, generator, foreach->left, + generator->count); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + } + njs_generate_patch_block(vm, generator, generator->block->continuation); njs_code_set_jump_offset(generator, njs_vmcode_prop_foreach_t, prop_offset); @@ -1564,12 +1814,38 @@ static njs_int_t njs_generate_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_int_t ret; + njs_int_t ret; + njs_variable_t *var; + njs_parser_node_t *right; + njs_vmcode_variable_t *code; + + right = node->right; + + if (right != NULL && right->token_type == NJS_TOKEN_NAME) { + var = njs_variable_reference(vm, right); + if (njs_slow_path(var == NULL)) { + goto statement; + } + + if (!var->init && var->type == NJS_VARIABLE_LET) { + njs_generate_code(generator, njs_vmcode_variable_t, code, + NJS_VMCODE_INITIALIZATION_TEST, 0, right); + code->dst = right->index; + } + + if (node->left == NULL) { + return NJS_OK; + } + + node = node->left; + } + +statement: ret = njs_generate_children(vm, generator, node); if (njs_fast_path(ret == NJS_OK)) { - return njs_generate_node_index_release(vm, generator, node->right); + return njs_generate_node_index_release(vm, generator, right); } return ret; @@ -3311,8 +3587,9 @@ static njs_index_t njs_generate_dest_index(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_index_t ret; - njs_parser_node_t *dest; + njs_index_t ret; + njs_parser_node_t *dest; + njs_parser_scope_t *scope; ret = njs_generate_children_indexes_release(vm, generator, node); if (njs_slow_path(ret != NJS_OK)) { @@ -3322,7 +3599,11 @@ njs_generate_dest_index(njs_vm_t *vm, njs_generator_t *generator, dest = node->dest; if (dest != NULL && dest->index != NJS_INDEX_NONE) { - return dest->index; + scope = njs_function_scope(node->scope); + + if (!scope->dest_disable) { + return dest->index; + } } return njs_generate_node_temp_index_get(vm, generator, node); @@ -3386,7 +3667,8 @@ njs_generate_temp_index_get(njs_vm_t *vm, njs_generator_t *generator, return NJS_ERROR; } - return njs_scope_index(scope->type, scope->temp++, NJS_LEVEL_TEMP); + return njs_scope_index(scope->type, scope->temp++, NJS_LEVEL_TEMP, + NJS_VARIABLE_VAR); } diff --git a/src/njs_module.c b/src/njs_module.c index fa3212ab..fb67b9bf 100644 --- a/src/njs_module.c +++ b/src/njs_module.c @@ -574,7 +574,8 @@ njs_module_insert(njs_parser_t *parser, njs_module_t *module) scope = njs_parser_global_scope(parser); vm = parser->vm; - module->index = njs_scope_index(scope->type, scope->items, NJS_LEVEL_LOCAL); + module->index = njs_scope_index(scope->type, scope->items, NJS_LEVEL_LOCAL, + NJS_VARIABLE_VAR); scope->items++; if (vm->modules == NULL) { diff --git a/src/njs_parser.c b/src/njs_parser.c index 2b41ae73..616b832f 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -238,6 +238,8 @@ static njs_int_t njs_parser_statement_list_next(njs_parser_t *parser, static njs_int_t njs_parser_statement_list_item(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); +static njs_int_t njs_parser_lexical_declaration(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_variable_statement(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_variable_declaration_list(njs_parser_t *parser, @@ -287,7 +289,8 @@ static njs_int_t njs_parser_iteration_statement_for(njs_parser_t *parser, static njs_int_t njs_parser_iteration_statement_for_map(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_for_var_binding_or_var_list(njs_parser_t *parser, - njs_lexer_token_t *token, njs_queue_link_t *current); + njs_lexer_token_t *token, njs_queue_link_t *current, + njs_token_type_t token_type); static njs_int_t njs_parser_for_var_in_statement(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_for_var_in_statement_after(njs_parser_t *parser, @@ -311,6 +314,8 @@ static njs_int_t njs_parser_switch_statement_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_switch_block(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); +static njs_int_t njs_parser_switch_block_after(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_switch_case(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_switch_case_wo_def(njs_parser_t *parser, @@ -362,6 +367,8 @@ static njs_int_t njs_parser_catch_after(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_catch_parenthesis(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); +static njs_int_t njs_parser_catch_statement_open_brace(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current); static njs_int_t njs_parser_catch_finally(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); @@ -506,8 +513,10 @@ njs_parser_reject(njs_parser_t *parser) njs_int_t njs_parser(njs_vm_t *vm, njs_parser_t *parser) { - njs_int_t ret; - njs_lexer_token_t *token; + njs_int_t ret; + njs_str_t str; + njs_lexer_token_t *token; + const njs_lexer_keyword_entry_t *keyword; parser->vm = vm; @@ -526,6 +535,16 @@ njs_parser(njs_vm_t *vm, njs_parser_t *parser) parser->ret = NJS_OK; } + /* Add this as first variable. */ + njs_string_get(&njs_string_undefined, &str); + + keyword = njs_lexer_keyword(str.start, str.length); + if (njs_slow_path(keyword == NULL)) { + return NJS_ERROR; + } + + parser->undefined_id = (uintptr_t) keyword->value; + njs_queue_init(&parser->stack); parser->target = NULL; @@ -645,7 +664,8 @@ njs_parser_scope_begin(njs_parser_t *parser, njs_scope_t type, return NJS_ERROR; } - var->index = njs_scope_index(type, 0, NJS_LEVEL_LOCAL); + var->index = njs_scope_index(type, 0, NJS_LEVEL_LOCAL, + NJS_VARIABLE_VAR); } } @@ -736,14 +756,6 @@ njs_parser_class_declaration(njs_parser_t *parser, njs_lexer_token_t *token, } -static njs_int_t -njs_parser_lexical_declaration(njs_parser_t *parser, njs_lexer_token_t *token, - njs_queue_link_t *current) -{ - return njs_parser_not_supported(parser, token); -} - - static njs_int_t njs_parser_function_or_generator(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) @@ -890,12 +902,14 @@ njs_parser_expression_parenthesis(njs_parser_t *parser, static njs_int_t -njs_parser_set_line_state(njs_parser_t *parser, +njs_parser_iteration_statement_for_end(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { parser->node->token_line = (uint32_t) (uintptr_t) parser->target; parser->target = NULL; + njs_parser_scope_end(parser); + return njs_parser_stack_pop(parser); } @@ -4534,7 +4548,7 @@ njs_parser_declaration(njs_parser_t *parser, njs_lexer_token_t *token, switch (token->type) { case NJS_TOKEN_CLASS: njs_parser_next(parser, njs_parser_class_declaration); - break; + return NJS_OK; case NJS_TOKEN_LET: case NJS_TOKEN_CONST: @@ -4570,7 +4584,8 @@ njs_parser_declaration(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_DECLINED; } - return NJS_OK; + return njs_parser_after(parser, current, parser->node, 1, + njs_parser_statement_after); } @@ -4732,6 +4747,24 @@ njs_parser_statement_list_item(njs_parser_t *parser, njs_lexer_token_t *token, } +/* + * 13.3.1 Let and Const Declarations + */ +static njs_int_t +njs_parser_lexical_declaration(njs_parser_t *parser, njs_lexer_token_t *token, + njs_queue_link_t *current) +{ + parser->var_type = (token->type == NJS_TOKEN_LET) ? NJS_VARIABLE_LET + : NJS_VARIABLE_CONST; + + njs_lexer_consume_token(parser->lexer, 1); + + njs_parser_next(parser, njs_parser_variable_declaration_list); + + return njs_parser_after(parser, current, NULL, 1, njs_parser_semicolon); +} + + /* * 13.3.2 Variable Statement */ @@ -4739,6 +4772,8 @@ static njs_int_t njs_parser_variable_statement(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { + parser->var_type = NJS_VARIABLE_VAR; + njs_parser_next(parser, njs_parser_variable_declaration_list); return njs_parser_after(parser, current, NULL, 1, njs_parser_semicolon); @@ -4789,6 +4824,7 @@ njs_parser_variable_declaration(njs_parser_t *parser, { njs_int_t ret; njs_variable_t *var; + njs_token_type_t type; njs_parser_node_t *name; ret = njs_parser_binding_pattern(parser, token, current); @@ -4807,14 +4843,14 @@ njs_parser_variable_declaration(njs_parser_t *parser, return NJS_DONE; } - name = njs_parser_variable_node(parser, token->unique_id, NJS_VARIABLE_VAR, + name = njs_parser_variable_node(parser, token->unique_id, parser->var_type, &var); if (name == NULL) { return NJS_ERROR; } if (var->self) { - var->type = NJS_VARIABLE_VAR; + var->type = parser->var_type; var->self = 0; } @@ -4829,7 +4865,21 @@ njs_parser_variable_declaration(njs_parser_t *parser, return NJS_ERROR; } - ret = njs_parser_initializer_assign(parser, NJS_TOKEN_VAR); + switch (parser->var_type) { + case NJS_VARIABLE_LET: + type = NJS_TOKEN_LET; + break; + + case NJS_VARIABLE_CONST: + type = NJS_TOKEN_CONST; + break; + + default: + type = NJS_TOKEN_VAR; + break; + } + + ret = njs_parser_initializer_assign(parser, type); if (ret != NJS_OK) { return ret; } @@ -4930,6 +4980,12 @@ njs_parser_expression_statement(njs_parser_t *parser, njs_lexer_token_t *token, return NJS_ERROR; } + if (token->type == NJS_TOKEN_NAME) { + njs_parser_syntax_error(parser, "let declaration cannot appear " + "in a single-statement context"); + return NJS_DONE; + } + if (token->type == NJS_TOKEN_OPEN_BRACKET) { return njs_parser_failed(parser); } @@ -5172,14 +5228,21 @@ static njs_int_t njs_parser_iteration_statement_for(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { + njs_int_t ret; + if (token->type == NJS_TOKEN_OPEN_PARENTHESIS) { njs_lexer_consume_token(parser->lexer, 1); + ret = njs_parser_scope_begin(parser, NJS_SCOPE_BLOCK, 0); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + njs_parser_next(parser, njs_parser_iteration_statement_for_map); return njs_parser_after(parser, current, (void *) (uintptr_t) parser->line, 1, - njs_parser_set_line_state); + njs_parser_iteration_statement_for_end); } if (token->type == NJS_TOKEN_AWAIT) { @@ -5194,8 +5257,9 @@ static njs_int_t njs_parser_iteration_statement_for_map(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - njs_int_t ret; - njs_str_t *text; + njs_int_t ret; + njs_str_t *text; + njs_token_type_t token_type; /* * "var" ";" ? ";" ? ")" @@ -5242,6 +5306,9 @@ njs_parser_iteration_statement_for_map(njs_parser_t *parser, return NJS_OK; case NJS_TOKEN_VAR: + case NJS_TOKEN_LET: + token_type = token->type; + token = njs_lexer_peek_token(parser->lexer, token, 0); if (token == NULL) { return NJS_ERROR; @@ -5249,7 +5316,8 @@ njs_parser_iteration_statement_for_map(njs_parser_t *parser, njs_lexer_consume_token(parser->lexer, 1); - ret = njs_parser_for_var_binding_or_var_list(parser, token, current); + ret = njs_parser_for_var_binding_or_var_list(parser, token, + current, token_type); if (ret != NJS_OK) { if (ret == NJS_DONE) { return NJS_OK; @@ -5260,7 +5328,6 @@ njs_parser_iteration_statement_for_map(njs_parser_t *parser, break; - case NJS_TOKEN_LET: case NJS_TOKEN_CONST: return njs_parser_not_supported(parser, token); @@ -5288,11 +5355,23 @@ njs_parser_iteration_statement_for_map(njs_parser_t *parser, static njs_int_t njs_parser_for_var_binding_or_var_list(njs_parser_t *parser, - njs_lexer_token_t *token, njs_queue_link_t *current) + njs_lexer_token_t *token, njs_queue_link_t *current, + njs_token_type_t token_type) { - njs_int_t ret; - njs_lexer_token_t *next; - njs_parser_node_t *node, *var; + njs_int_t ret; + njs_lexer_token_t *next; + njs_parser_node_t *node, *var, *node_type, *statement; + njs_variable_type_t type; + + switch (token_type) { + case NJS_TOKEN_LET: + type = NJS_VARIABLE_LET; + break; + + default: + type = NJS_VARIABLE_VAR; + break; + } switch (token->type) { /* BindingPattern */ @@ -5318,18 +5397,33 @@ njs_parser_for_var_binding_or_var_list(njs_parser_t *parser, } if (next->type != NJS_TOKEN_IN) { + parser->var_type = type; + njs_parser_next(parser, njs_parser_variable_declaration_list); return NJS_OK; } + statement = njs_parser_node_new(parser, NJS_TOKEN_STATEMENT); + if (njs_slow_path(statement == NULL)) { + return NJS_ERROR; + } + + node_type = njs_parser_node_new(parser, token_type); + if (njs_slow_path(node_type == NULL)) { + return NJS_ERROR; + } + var = njs_parser_variable_node(parser, token->unique_id, - NJS_VARIABLE_VAR, NULL); + type, NULL); if (var == NULL) { return NJS_ERROR; } + node_type->token_line = token->line; var->token_line = token->line; + statement->right = node_type; + node_type->left = var; parser->node = NULL; node = njs_parser_node_new(parser, NJS_TOKEN_IN); @@ -5338,7 +5432,7 @@ njs_parser_for_var_binding_or_var_list(njs_parser_t *parser, } node->token_line = next->line; - node->left = var; + node->left = statement; njs_parser_next(parser, njs_parser_expression); @@ -5829,6 +5923,8 @@ static njs_int_t njs_parser_switch_block(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { + njs_int_t ret; + if (token->type != NJS_TOKEN_OPEN_BRACE) { return njs_parser_failed(parser); } @@ -5837,9 +5933,24 @@ njs_parser_switch_block(njs_parser_t *parser, njs_lexer_token_t *token, parser->target->left = parser->node; + ret = njs_parser_scope_begin(parser, NJS_SCOPE_BLOCK, 0); + if (ret != NJS_OK) { + return NJS_ERROR; + } + njs_parser_next(parser, njs_parser_switch_case); - return NJS_OK; + return njs_parser_after(parser, current, NULL, 1, + njs_parser_switch_block_after); +} + +static njs_int_t +njs_parser_switch_block_after(njs_parser_t *parser, njs_lexer_token_t *token, + njs_queue_link_t *current) +{ + njs_parser_scope_end(parser); + + return njs_parser_stack_pop(parser); } @@ -6310,8 +6421,6 @@ njs_parser_catch_after(njs_parser_t *parser, njs_lexer_token_t *token, { njs_parser_node_t *node; - njs_parser_scope_end(parser); - parser->target->right->right = parser->node; if (token->type == NJS_TOKEN_FINALLY) { @@ -6355,13 +6464,49 @@ njs_parser_catch_parenthesis(njs_parser_t *parser, njs_lexer_token_t *token, parser->target->right->right = parser->node; parser->node = NULL; - njs_parser_next(parser, njs_parser_block_statement_open_brace); + njs_parser_next(parser, njs_parser_catch_statement_open_brace); return njs_parser_after(parser, current, parser->target, 1, njs_parser_catch_after); } +static njs_int_t +njs_parser_catch_statement_open_brace(njs_parser_t *parser, + njs_lexer_token_t *token, njs_queue_link_t *current) +{ + void *target; + + if (token->type != NJS_TOKEN_OPEN_BRACE) { + return njs_parser_failed(parser); + } + + parser->line = token->line; + + njs_lexer_consume_token(parser->lexer, 1); + + token = njs_lexer_token(parser->lexer, 0); + if (token == NULL) { + return NJS_ERROR; + } + + target = (void *) (uintptr_t) parser->line; + parser->node = NULL; + + if (token->type == NJS_TOKEN_CLOSE_BRACE) { + parser->target = target; + + njs_parser_next(parser, njs_parser_block_statement_close_brace); + return NJS_OK; + } + + njs_parser_next(parser, njs_parser_statement_list); + + return njs_parser_after(parser, current, target, 0, + njs_parser_block_statement_close_brace); +} + + static njs_int_t njs_parser_catch_finally(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) @@ -6569,7 +6714,7 @@ njs_parser_function_expression_after(njs_parser_t *parser, var = (njs_variable_t *) parser->target; var->index = njs_scope_index(var->scope->type, var->scope->items, - NJS_LEVEL_LOCAL); + NJS_LEVEL_LOCAL, NJS_VARIABLE_VAR); var->scope->items++; if (var->self) { @@ -6777,7 +6922,7 @@ njs_parser_arrow_function(njs_parser_t *parser, njs_lexer_token_t *token, arg->argument = 1; var->index = njs_scope_index(parser->scope->type, parser->scope->items, - NJS_LEVEL_LOCAL); + NJS_LEVEL_LOCAL, NJS_VARIABLE_VAR); parser->scope->items++; lambda->self = var->index; @@ -6815,7 +6960,7 @@ njs_parser_arrow_function_args_after(njs_parser_t *parser, *vv = NULL; var->index = njs_scope_index(var->scope->type, var->scope->items, - NJS_LEVEL_LOCAL); + NJS_LEVEL_LOCAL, NJS_VARIABLE_VAR); var->scope->items++; parser->target->u.value.data.u.lambda->self = var->index; @@ -7474,7 +7619,7 @@ njs_parser_module_lambda_after(njs_parser_t *parser, njs_lexer_token_t *token, *vv = NULL; var->index = njs_scope_index(var->scope->type, var->scope->items, - NJS_LEVEL_LOCAL); + NJS_LEVEL_LOCAL, NJS_VARIABLE_VAR); var->scope->items++; parser->node->u.value.data.u.lambda->self = var->index; @@ -7644,7 +7789,8 @@ njs_parser_reference(njs_parser_t *parser, njs_lexer_token_t *token) token->unique_id = (uintptr_t) keyword->value; } else if (!scope->arrow_function) { - index = njs_scope_index(scope->type, 0, NJS_LEVEL_LOCAL); + index = njs_scope_index(scope->type, 0, NJS_LEVEL_LOCAL, + NJS_VARIABLE_VAR); var = njs_variable_scope_add(parser, scope, scope, token->unique_id, NJS_VARIABLE_VAR, index); diff --git a/src/njs_parser.h b/src/njs_parser.h index 854ac9b6..e237d1fc 100644 --- a/src/njs_parser.h +++ b/src/njs_parser.h @@ -29,6 +29,7 @@ struct njs_parser_scope_s { njs_scope_t type:8; uint8_t module; uint8_t arrow_function; + uint8_t dest_disable; }; @@ -76,7 +77,9 @@ struct njs_parser_s { njs_parser_node_t *node; njs_parser_node_t *target; njs_parser_scope_t *scope; + njs_variable_type_t var_type; njs_int_t ret; + uintptr_t undefined_id; njs_bool_t strict_semicolon; uint32_t line; }; @@ -108,6 +111,8 @@ njs_int_t njs_parser(njs_vm_t *vm, njs_parser_t *parser); njs_int_t njs_parser_module_lambda(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current); +njs_bool_t njs_variable_closure_test(njs_parser_scope_t *root, + njs_parser_scope_t *scope); njs_variable_t *njs_variable_resolve(njs_vm_t *vm, njs_parser_node_t *node); njs_index_t njs_variable_index(njs_vm_t *vm, njs_parser_node_t *node); njs_bool_t njs_parser_has_side_effect(njs_parser_node_t *node); diff --git a/src/njs_scope.c b/src/njs_scope.c index 4d903cdd..29ab18ac 100644 --- a/src/njs_scope.c +++ b/src/njs_scope.c @@ -20,7 +20,8 @@ njs_scope_temp_index(njs_parser_scope_t *scope) return NJS_INDEX_ERROR; } - return njs_scope_index(NJS_SCOPE_GLOBAL, scope->temp++, NJS_LEVEL_TEMP); + return njs_scope_index(NJS_SCOPE_GLOBAL, scope->temp++, NJS_LEVEL_TEMP, + NJS_VARIABLE_VAR); } @@ -103,7 +104,8 @@ njs_scope_global_index(njs_vm_t *vm, const njs_value_t *src, njs_uint_t runtime) vm->levels[NJS_LEVEL_STATIC] = vm->scope_absolute->start; - *retval = njs_scope_index(NJS_SCOPE_GLOBAL, index, NJS_LEVEL_STATIC); + *retval = njs_scope_index(NJS_SCOPE_GLOBAL, index, NJS_LEVEL_STATIC, + NJS_VARIABLE_VAR); return *retval; } diff --git a/src/njs_scope.h b/src/njs_scope.h index 0303da47..6771e58b 100644 --- a/src/njs_scope.h +++ b/src/njs_scope.h @@ -8,10 +8,11 @@ #define _NJS_SCOPE_H_INCLUDED_ -#define NJS_SCOPE_TYPE_SIZE 4 -#define NJS_SCOPE_VALUE_OFFSET (NJS_SCOPE_TYPE_SIZE + 1) +#define NJS_SCOPE_VAR_SIZE 4 +#define NJS_SCOPE_TYPE_OFFSET (NJS_SCOPE_VAR_SIZE + 4) +#define NJS_SCOPE_VALUE_OFFSET (NJS_SCOPE_TYPE_OFFSET + 1) #define NJS_SCOPE_VALUE_MAX ((1 << (32 - NJS_SCOPE_VALUE_OFFSET)) - 1) -#define NJS_SCOPE_TYPE_MASK ((NJS_SCOPE_VALUE_MAX) << NJS_SCOPE_TYPE_SIZE) +#define NJS_SCOPE_TYPE_MASK ((NJS_SCOPE_VALUE_MAX) << NJS_SCOPE_VAR_SIZE) #define NJS_INDEX_NONE ((njs_index_t) 0) #define NJS_INDEX_ERROR ((njs_index_t) -1) @@ -25,7 +26,8 @@ njs_index_t njs_scope_global_index(njs_vm_t *vm, const njs_value_t *src, njs_inline njs_index_t -njs_scope_index(njs_scope_t scope, njs_index_t index, njs_level_type_t type) +njs_scope_index(njs_scope_t scope, njs_index_t index, njs_level_type_t type, + njs_variable_type_t var_type) { if (index > NJS_SCOPE_VALUE_MAX || type >= NJS_LEVEL_MAX || (scope != NJS_SCOPE_GLOBAL && scope != NJS_SCOPE_FUNCTION)) @@ -37,14 +39,23 @@ njs_scope_index(njs_scope_t scope, njs_index_t index, njs_level_type_t type) type = NJS_LEVEL_GLOBAL; } - return (index << NJS_SCOPE_VALUE_OFFSET) | type; + return (index << NJS_SCOPE_VALUE_OFFSET) | (type << NJS_SCOPE_VAR_SIZE) + | var_type; +} + + +njs_inline njs_variable_type_t +njs_scope_index_var(njs_index_t index) +{ + return (njs_variable_type_t) (index & ~NJS_SCOPE_TYPE_MASK); } njs_inline njs_level_type_t njs_scope_index_type(njs_index_t index) { - return (njs_level_type_t) (index & ~NJS_SCOPE_TYPE_MASK); + return (njs_level_type_t) ((index >> NJS_SCOPE_VAR_SIZE) + & ~NJS_SCOPE_TYPE_MASK); } @@ -71,6 +82,12 @@ njs_scope_valid_value(njs_vm_t *vm, njs_index_t index) value = njs_scope_value(vm, index); if (!njs_is_valid(value)) { + if (njs_scope_index_var(index) == NJS_VARIABLE_LET) { + njs_reference_error(vm, "cannot access to variable " + "before initialization"); + return NULL; + } + njs_set_undefined(value); } @@ -115,7 +132,8 @@ njs_scope_undefined_index(njs_vm_t *vm, njs_uint_t runtime) njs_inline njs_index_t njs_scope_global_this_index() { - return njs_scope_index(NJS_SCOPE_GLOBAL, 0, NJS_LEVEL_LOCAL); + return njs_scope_index(NJS_SCOPE_GLOBAL, 0, NJS_LEVEL_LOCAL, + NJS_VARIABLE_VAR); } diff --git a/src/njs_variable.c b/src/njs_variable.c index ee796cb0..5a532fb3 100644 --- a/src/njs_variable.c +++ b/src/njs_variable.c @@ -75,7 +75,8 @@ njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope, *declr = &var->value; - var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL); + var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL, + type); root->items++; } @@ -177,15 +178,42 @@ njs_variable_scope_find(njs_parser_t *parser, njs_parser_scope_t *scope, njs_parser_scope_t *root; const njs_lexer_entry_t *entry; - if (type != NJS_VARIABLE_VAR && type != NJS_VARIABLE_FUNCTION) { - return scope; - } - root = njs_variable_scope(scope, unique_id, &var, type); if (njs_slow_path(root == NULL)) { return NULL; } + switch (type) { + case NJS_VARIABLE_LET: + if (scope->type == NJS_SCOPE_GLOBAL + && parser->undefined_id == unique_id) + { + goto failed; + } + + if (root != scope) { + return scope; + } + + if (var != NULL && var->scope == root) { + if (var->self) { + var->function = 0; + return scope; + } + + goto failed; + } + + return scope; + + case NJS_VARIABLE_VAR: + case NJS_VARIABLE_FUNCTION: + break; + + default: + return scope; + } + if (type == NJS_VARIABLE_FUNCTION) { root = scope; } @@ -194,6 +222,10 @@ njs_variable_scope_find(njs_parser_t *parser, njs_parser_scope_t *scope, return root; } + if (var->type == NJS_VARIABLE_LET) { + goto failed; + } + if (var->original->type == NJS_SCOPE_BLOCK) { if (type == NJS_VARIABLE_FUNCTION || var->type == NJS_VARIABLE_FUNCTION) @@ -269,7 +301,8 @@ njs_variable_scope_add(njs_parser_t *parser, njs_parser_scope_t *scope, return NULL; } - var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL); + var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL, + type); root->items++; } @@ -348,7 +381,7 @@ njs_label_remove(njs_vm_t *vm, njs_parser_scope_t *scope, uintptr_t unique_id) } -static njs_bool_t +njs_bool_t njs_variable_closure_test(njs_parser_scope_t *root, njs_parser_scope_t *scope) { if (root == scope) { @@ -454,7 +487,7 @@ njs_variable_closure(njs_vm_t *vm, njs_variable_t *var, /* Create new closure for scope. */ index = njs_scope_index(root->type, root->closures->items, - NJS_LEVEL_CLOSURE); + NJS_LEVEL_CLOSURE, var->type); if (njs_slow_path(index == NJS_INDEX_ERROR)) { return NJS_INDEX_ERROR; } @@ -493,6 +526,7 @@ njs_variable_closure(njs_vm_t *vm, njs_variable_t *var, njs_variable_t * njs_variable_reference(njs_vm_t *vm, njs_parser_node_t *node) { + njs_bool_t closure; njs_rbtree_node_t *rb_node; njs_parser_scope_t *scope; njs_parser_rbtree_node_t *parse_node, ref_node; @@ -510,7 +544,7 @@ njs_variable_reference(njs_vm_t *vm, njs_parser_node_t *node) } } - ref->closure = njs_variable_closure_test(node->scope, ref->variable->scope); + closure = njs_variable_closure_test(node->scope, ref->variable->scope); ref->scope = node->scope; ref_node.key = ref->unique_id; @@ -528,12 +562,14 @@ njs_variable_reference(njs_vm_t *vm, njs_parser_node_t *node) return ref->variable; } - if (!ref->closure) { + if (!closure) { node->index = ref->variable->index; return ref->variable; } + ref->variable->closure = closure; + node->index = njs_variable_closure(vm, ref->variable, scope); if (njs_slow_path(node->index == NJS_INDEX_ERROR)) { return NULL; diff --git a/src/njs_variable.h b/src/njs_variable.h index 8759e04f..56d54e95 100644 --- a/src/njs_variable.h +++ b/src/njs_variable.h @@ -24,6 +24,8 @@ typedef struct { njs_bool_t argument; njs_bool_t arguments_object; njs_bool_t self; + njs_bool_t init; + njs_bool_t closure; njs_bool_t function; njs_parser_scope_t *scope; @@ -47,7 +49,6 @@ typedef struct { njs_variable_t *variable; njs_parser_scope_t *scope; njs_bool_t not_defined; - njs_bool_t closure; } njs_variable_reference_t; diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index 8eda0858..1a99fc73 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -64,7 +64,13 @@ static njs_jump_off_t njs_function_frame_create(njs_vm_t *vm, njs_bool_t ctor); -#define njs_vmcode_operand(vm, index) njs_scope_valid_value(vm, index) +#define njs_vmcode_operand(vm, index, _retval) \ + do { \ + _retval = njs_scope_valid_value(vm, index); \ + if (njs_slow_path(_retval == NULL)) { \ + goto error; \ + } \ + } while (0) njs_int_t @@ -87,6 +93,7 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc) njs_property_next_t *next; njs_vmcode_finally_t *finally; njs_vmcode_generic_t *vmcode; + njs_vmcode_variable_t *var; njs_vmcode_move_arg_t *move_arg; njs_vmcode_prop_get_t *get; njs_vmcode_prop_set_t *set; @@ -129,12 +136,12 @@ next: switch (vmcode->code.operands) { case NJS_VMCODE_3OPERANDS: - value2 = njs_vmcode_operand(vm, vmcode->operand3); + njs_vmcode_operand(vm, vmcode->operand3, value2); /* Fall through. */ case NJS_VMCODE_2OPERANDS: - value1 = njs_vmcode_operand(vm, vmcode->operand2); + njs_vmcode_operand(vm, vmcode->operand2, value1); } op = vmcode->code.operation; @@ -150,7 +157,7 @@ next: if (op > NJS_VMCODE_NORET) { if (op == NJS_VMCODE_MOVE) { - retval = njs_vmcode_operand(vm, vmcode->operand1); + njs_vmcode_operand(vm, vmcode->operand1, retval); *retval = *value1; pc += sizeof(njs_vmcode_move_t); @@ -159,7 +166,7 @@ next: if (op == NJS_VMCODE_PROPERTY_GET) { get = (njs_vmcode_prop_get_t *) pc; - retval = njs_vmcode_operand(vm, get->value); + njs_vmcode_operand(vm, get->value, retval); ret = njs_value_property(vm, value1, value2, retval); if (njs_slow_path(ret == NJS_ERROR)) { @@ -190,7 +197,7 @@ next: njs_set_number(value1, num + (1 - 2 * ((op - NJS_VMCODE_INCREMENT) >> 1))); - retval = njs_vmcode_operand(vm, vmcode->operand1); + njs_vmcode_operand(vm, vmcode->operand1, retval); if (op & 1) { njs_set_number(retval, num); @@ -204,7 +211,7 @@ next: case NJS_VMCODE_GLOBAL_GET: get = (njs_vmcode_prop_get_t *) pc; - retval = njs_vmcode_operand(vm, get->value); + njs_vmcode_operand(vm, get->value, retval); ret = njs_value_property(vm, value1, value2, retval); if (njs_slow_path(ret == NJS_ERROR)) { @@ -224,7 +231,7 @@ next: * njs_vmcode_finally(), and jumps to the nearest try_break block. */ case NJS_VMCODE_TRY_RETURN: - retval = njs_vmcode_operand(vm, vmcode->operand1); + njs_vmcode_operand(vm, vmcode->operand1, retval); *retval = *value1; try_return = (njs_vmcode_try_return_t *) pc; @@ -266,7 +273,7 @@ next: goto error; } - retval = njs_vmcode_operand(vm, vmcode->operand1); + njs_vmcode_operand(vm, vmcode->operand1, retval); if (op == NJS_VMCODE_ADDITION) { if (njs_fast_path(njs_is_numeric(value1) @@ -335,7 +342,7 @@ next: ret ^= op - NJS_VMCODE_EQUAL; - retval = njs_vmcode_operand(vm, vmcode->operand1); + njs_vmcode_operand(vm, vmcode->operand1, retval); njs_set_boolean(retval, ret); pc += sizeof(njs_vmcode_3addr_t); @@ -372,7 +379,7 @@ next: num = njs_number(value1); - retval = njs_vmcode_operand(vm, vmcode->operand1); + njs_vmcode_operand(vm, vmcode->operand1, retval); pc += sizeof(njs_vmcode_3addr_t); switch (op) { @@ -490,7 +497,7 @@ next: ret ^= op - NJS_VMCODE_STRICT_EQUAL; - retval = njs_vmcode_operand(vm, vmcode->operand1); + njs_vmcode_operand(vm, vmcode->operand1, retval); njs_set_boolean(retval, ret); pc += sizeof(njs_vmcode_3addr_t); @@ -515,7 +522,7 @@ next: ret = sizeof(njs_vmcode_3addr_t); } - retval = njs_vmcode_operand(vm, vmcode->operand1); + njs_vmcode_operand(vm, vmcode->operand1, retval); *retval = *value1; pc += ret; @@ -534,7 +541,7 @@ next: } num = njs_number(value1); - retval = njs_vmcode_operand(vm, vmcode->operand1); + njs_vmcode_operand(vm, vmcode->operand1, retval); switch (op) { case NJS_VMCODE_UNARY_NEGATION: @@ -553,7 +560,7 @@ next: goto next; case NJS_VMCODE_LOGICAL_NOT: - retval = njs_vmcode_operand(vm, vmcode->operand1); + njs_vmcode_operand(vm, vmcode->operand1, retval); njs_set_boolean(retval, !njs_is_true(value1)); pc += sizeof(njs_vmcode_2addr_t); @@ -605,7 +612,7 @@ next: break; } - retval = njs_vmcode_operand(vm, vmcode->operand1); + njs_vmcode_operand(vm, vmcode->operand1, retval); njs_release(vm, retval); *retval = vm->retval; @@ -619,7 +626,7 @@ next: hint = move_arg->dst; value1 = &native->arguments_offset[hint]; - value2 = njs_vmcode_operand(vm, move_arg->src); + njs_vmcode_operand(vm, move_arg->src, value2); *value1 = *value2; @@ -627,7 +634,7 @@ next: break; case NJS_VMCODE_STOP: - value2 = njs_vmcode_operand(vm, (njs_index_t) value2); + njs_vmcode_operand(vm, (njs_index_t) value2, value2); vm->retval = *value2; return NJS_OK; @@ -638,7 +645,7 @@ next: case NJS_VMCODE_PROPERTY_SET: set = (njs_vmcode_prop_set_t *) pc; - retval = njs_vmcode_operand(vm, set->value); + njs_vmcode_operand(vm, set->value, retval); ret = njs_value_property_set(vm, value1, value2, retval); if (njs_slow_path(ret == NJS_ERROR)) { @@ -650,7 +657,7 @@ next: case NJS_VMCODE_PROPERTY_ACCESSOR: accessor = (njs_vmcode_prop_accessor_t *) pc; - function = njs_vmcode_operand(vm, accessor->value); + njs_vmcode_operand(vm, accessor->value, function); ret = njs_value_to_key(vm, &name, value2); if (njs_slow_path(ret != NJS_OK)) { @@ -693,7 +700,7 @@ next: case NJS_VMCODE_PROPERTY_INIT: set = (njs_vmcode_prop_set_t *) pc; - retval = njs_vmcode_operand(vm, set->value); + njs_vmcode_operand(vm, set->value, retval); ret = njs_vmcode_property_init(vm, value1, value2, retval); if (njs_slow_path(ret == NJS_ERROR)) { goto error; @@ -702,7 +709,7 @@ next: break; case NJS_VMCODE_RETURN: - value2 = njs_vmcode_operand(vm, (njs_index_t) value2); + njs_vmcode_operand(vm, (njs_index_t) value2, value2); return njs_vmcode_return(vm, NULL, value2); case NJS_VMCODE_FUNCTION_COPY: @@ -763,7 +770,7 @@ next: case NJS_VMCODE_FUNCTION_CALL: vm->active_frame->native.pc = pc; - value2 = njs_vmcode_operand(vm, (njs_index_t) value2); + njs_vmcode_operand(vm, (njs_index_t) value2, value2); ret = njs_function_frame_invoke(vm, value2); if (njs_slow_path(ret == NJS_ERROR)) { @@ -775,7 +782,7 @@ next: case NJS_VMCODE_PROPERTY_NEXT: pnext = (njs_vmcode_prop_next_t *) pc; - retval = njs_vmcode_operand(vm, pnext->retval); + retval = njs_scope_value(vm, pnext->retval); next = value2->data.u.next; @@ -801,7 +808,7 @@ next: case NJS_VMCODE_PROTO_INIT: set = (njs_vmcode_prop_set_t *) pc; - retval = njs_vmcode_operand(vm, set->value); + njs_vmcode_operand(vm, set->value, retval); ret = njs_vmcode_proto_init(vm, value1, value2, retval); if (njs_slow_path(ret == NJS_ERROR)) { goto error; @@ -818,7 +825,7 @@ next: break; case NJS_VMCODE_THROW: - value2 = njs_vmcode_operand(vm, (njs_index_t) value2); + njs_vmcode_operand(vm, (njs_index_t) value2, value2); vm->retval = *value2; goto error; @@ -877,6 +884,56 @@ next: break; + case NJS_VMCODE_LET: + var = (njs_vmcode_variable_t *) pc; + value1 = njs_scope_value(vm, var->dst); + + if (njs_is_valid(value1)) { + value1 = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t)); + if (njs_slow_path(value1 == NULL)) { + return NJS_ERROR; + } + + njs_scope_value_set(vm, var->dst, value1); + } + + njs_set_undefined(value1); + + ret = sizeof(njs_vmcode_variable_t); + break; + + case NJS_VMCODE_LET_UPDATE: + var = (njs_vmcode_variable_t *) pc; + value2 = njs_scope_value(vm, var->dst); + + value1 = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t)); + if (njs_slow_path(value1 == NULL)) { + return NJS_ERROR; + } + + *value1 = *value2; + + njs_scope_value_set(vm, var->dst, value1); + + ret = sizeof(njs_vmcode_variable_t); + break; + + case NJS_VMCODE_INITIALIZATION_TEST: + var = (njs_vmcode_variable_t *) pc; + value1 = njs_scope_value(vm, var->dst); + + if (njs_is_valid(value1)) { + ret = sizeof(njs_vmcode_variable_t); + break; + } + + /* Fall through. */ + + case NJS_VMCODE_NOT_INITIALIZED: + njs_reference_error(vm, "cannot access to variable " + "before initialization"); + goto error; + case NJS_VMCODE_ERROR: njs_vmcode_error(vm, pc); goto error; @@ -1036,7 +1093,11 @@ njs_vmcode_arguments(njs_vm_t *vm, u_char *pc) code = (njs_vmcode_arguments_t *) pc; - value = njs_vmcode_operand(vm, code->dst); + value = njs_scope_valid_value(vm, code->dst); + if (njs_slow_path(value == NULL)) { + return NJS_ERROR; + } + njs_set_object(value, frame->native.arguments_object); return sizeof(njs_vmcode_arguments_t); @@ -1077,7 +1138,7 @@ njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *invld1, .u.native = njs_string_prototype_concat }; - value = njs_vmcode_operand(vm, (njs_index_t) retval); + value = njs_scope_valid_value(vm, (njs_index_t) retval); if (!njs_is_primitive(value)) { array = njs_array(value); diff --git a/src/njs_vmcode.h b/src/njs_vmcode.h index 76d13949..f7db9758 100644 --- a/src/njs_vmcode.h +++ b/src/njs_vmcode.h @@ -57,6 +57,12 @@ enum { NJS_VMCODE_TRY_END, NJS_VMCODE_CATCH, NJS_VMCODE_FINALLY, + + NJS_VMCODE_LET, + NJS_VMCODE_LET_UPDATE, + NJS_VMCODE_INITIALIZATION_TEST, + NJS_VMCODE_NOT_INITIALIZED, + NJS_VMCODE_ERROR, NJS_VMCODE_NORET = 127 @@ -408,6 +414,12 @@ typedef struct { } njs_vmcode_function_copy_t; +typedef struct { + njs_vmcode_t code; + njs_index_t dst; +} njs_vmcode_variable_t; + + njs_int_t njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc); njs_object_t *njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor); diff --git a/src/test/njs_benchmark.c b/src/test/njs_benchmark.c index c4385e36..bbe052d5 100644 --- a/src/test/njs_benchmark.c +++ b/src/test/njs_benchmark.c @@ -213,6 +213,17 @@ static njs_benchmark_test_t njs_test[] = njs_str("100000000"), 1 }, + { "for let loop 100M", + njs_str("let i; for (i = 0; i < 100000000; i++); i"), + njs_str("100000000"), + 1 }, + + { "for let closures 1M", + njs_str("let a = []; for (let i = 0; i < 1000000; i++) { a.push(() => i); }" + "a[5]()"), + njs_str("5"), + 1 }, + { "while loop 100M", njs_str("var i = 0; while (i < 100000000) { i++ }; i"), njs_str("100000000"), diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 5d1e3ea3..f473dc4a 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -19772,6 +19772,243 @@ static njs_unit_test_t njs_test[] = { njs_str("var buffer = require('buffer');" "typeof buffer.constants.MAX_STRING_LENGTH === 'number' "), njs_str("true") }, + + /* let */ + + { njs_str("let x"), + njs_str("undefined") }, + + { njs_str("let x = 123; x"), + njs_str("123") }, + + { njs_str("let x = [123]; x"), + njs_str("123") }, + + { njs_str("let x = () => x; x()"), + njs_str("[object Function]") }, + + { njs_str("let x = (() => x)()"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("x; let x"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("x; let x = 123"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("let x = x + 123"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("let x = (x, 1)"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("let x = x"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("let x; var x"), + njs_str("SyntaxError: \"x\" has already been declared in 1") }, + + { njs_str("var x; let x"), + njs_str("SyntaxError: \"x\" has already been declared in 1") }, + + { njs_str("let x; function x() {}"), + njs_str("SyntaxError: \"x\" has already been declared in 1") }, + + { njs_str("function x() {} let x"), + njs_str("SyntaxError: \"x\" has already been declared in 1") }, + + { njs_str("function x() {let x; var x}"), + njs_str("SyntaxError: \"x\" has already been declared in 1") }, + + { njs_str("function x() {var x; let x}"), + njs_str("SyntaxError: \"x\" has already been declared in 1") }, + + { njs_str("var x = function f() {let f}"), + njs_str("undefined") }, + + { njs_str("let a; let x = 1;" + "{let x = 2; a = x}" + "[x, a]"), + njs_str("1,2") }, + + { njs_str("let a; let x = 1;" + "if (true) {let x = 2; a = x}" + "[x, a]"), + njs_str("1,2") }, + + { njs_str("var a = 5, b = 10, arr = [];" + "{let a = 4; var b = 1; arr.push(a); arr.push(b)}" + "arr.push(a); arr.push(b); arr"), + njs_str("4,1,5,1") }, + + { njs_str("function func() {return x}" + "let x = 123;" + "func()"), + njs_str("123") }, + + { njs_str("function func() {return x}" + "func();" + "let x = 123"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("function func() {return () => x}" + "let x = 123;" + "func()()"), + njs_str("123") }, + + { njs_str("function func() {x = x + 1; let x}"), + njs_str("undefined") }, + + { njs_str("function func() {return () => x}" + "func()();" + "let x = 123;"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("var arr = [];" + "" + "for (var i = 0; i < 10; i++) {" + " let x = i;" + "" + " arr.push( (n) => {x += n; return x} );" + "}" + "" + "[" + " arr[0](2), arr[1](1), arr[2](4), arr[3](7), arr[4](0)," + " arr[5](1), arr[6](2), arr[7](5), arr[8](8), arr[9](10)" + "]"), + njs_str("2,2,6,10,4,6,8,12,16,19") }, + + { njs_str("var arr = [];" + "" + "for (let i = 0; i < 10; i++) {" + " arr.push( (n) => {i += n; return i} );" + "}" + "" + "[" + " arr[0](2), arr[1](1), arr[2](4), arr[3](7), arr[4](0)," + " arr[5](1), arr[6](2), arr[7](5), arr[8](8), arr[9](10)" + "]"), + njs_str("2,2,6,10,4,6,8,12,16,19") }, + + { njs_str("for (let i = 0; i < 1; i++) {" + " let i = i + 2;" + "}"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("let arr = [], res = [];" + "for (let i = 0, f = function() { return i }; i < 5; i++) {" + " arr.push(f);" + "}" + "for (let i = 0; i < 5; i++) {" + " res.push(arr[i]());" + "} res"), + njs_str("0,0,0,0,0") }, + + { njs_str("let arr = [], res = [];" + "for (let i = 0; arr.push(() => i), i < 10; i++) {}" + "for (let k = 0; k < 10; k++) {res.push(arr[k]())}" + "res"), + njs_str("0,1,2,3,4,5,6,7,8,9") }, + + { njs_str("let res = [];" + "for (let n in [1,2,3]) {res.push(n)}" + "res"), + njs_str("0,1,2") }, + + { njs_str("let arr = [], res = [];" + "" + "for (let n in [1,2,3]) {" + " arr.push(() => n);" + "}" + "" + "for (let n in arr) {" + " res.push(arr[n]());" + "}" + "res"), + njs_str("0,1,2") }, + + { njs_str("let arr = [];" + "" + "for (let n in [1,2,3]) {" + " let n = 1;" + " arr.push(n);" + "}" + "arr"), + njs_str("1,1,1") }, + + { njs_str("for (let n in [1,2,3]) {" + " let n = n + 1;" + "}"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("for (let n in [1,2,3]) {}" + "n"), + njs_str("ReferenceError: \"n\" is not defined") }, + + { njs_str("for (let n in [1,n,3]) {}"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("(function() {" + "function f() {return x + 1}" + "function abc() {f()};" + "abc();" + "let x;" + "}())"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("function func() {var x = 1; {let x = x + 1} } func()"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("if (false) let x = 1"), + njs_str("SyntaxError: let declaration cannot appear in a single-statement context in 1") }, + + { njs_str("while (false) let x = 1"), + njs_str("SyntaxError: let declaration cannot appear in a single-statement context in 1") }, + + { njs_str("for (;;) let x = 1"), + njs_str("SyntaxError: let declaration cannot appear in a single-statement context in 1") }, + + { njs_str("try {} catch (e) {let e}"), + njs_str("SyntaxError: \"e\" has already been declared in 1") }, + + { njs_str("let arr = [], x = 2;" + "switch(true) {default: let x = 1; arr.push(x)}" + "arr.push(x); arr"), + njs_str("1,2") }, + + { njs_str("switch(true) {case false: let x = 1; default: x = 2}"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("let res;" + "switch(true) {case true: let x = 1; default: x = 2; res = x} res"), + njs_str("2") }, + + { njs_str("let null"), + njs_str("SyntaxError: Unexpected token \"null\" in 1") }, + + { njs_str("let continue"), + njs_str("SyntaxError: Unexpected token \"continue\" in 1") }, + + { njs_str("let undefined"), + njs_str("SyntaxError: \"undefined\" has already been declared in 1") }, + + { njs_str("let a = 1; globalThis.a"), + njs_str("undefined") }, + + { njs_str("if (false) {x = 2} else {x = 1} let x;"), + njs_str("ReferenceError: cannot access to variable before initialization") }, + + { njs_str("let let"), + njs_str("SyntaxError: Unexpected token \"let\" in 1") }, + + { njs_str("let null"), + njs_str("SyntaxError: Unexpected token \"null\" in 1") }, + + { njs_str("function let() {}"), + njs_str("SyntaxError: Unexpected token \"let\" in 1") }, + + { njs_str("function static() {}"), + njs_str("SyntaxError: Unexpected token \"static\" in 1") }, }; -- 2.47.3