From: Dmitry Volyntsev Date: Mon, 25 Feb 2019 16:00:55 +0000 (+0300) Subject: Added labels support. X-Git-Tag: 0.2.8~3 X-Git-Url: http://git.kaiwu.me/postgresql/log/contrib/postgres_fdw/static/gitweb.js?a=commitdiff_plain;h=1df63480d9bdaec0454a1d57d13d85939bd02066;p=njs.git Added labels support. --- diff --git a/njs/njs_generator.c b/njs/njs_generator.c index a5496e9c..ed74e675 100644 --- a/njs/njs_generator.c +++ b/njs/njs_generator.c @@ -20,29 +20,35 @@ struct njs_generator_patch_s { */ njs_ret_t jump_offset; njs_generator_patch_t *next; + + nxt_str_t label; }; typedef enum { - NJS_GENERATOR_BLOCK = 1, - NJS_GENERATOR_LOOP = 2, - NJS_GENERATOR_SWITCH = 4, + NJS_GENERATOR_LOOP = 1, + NJS_GENERATOR_SWITCH = 2, + NJS_GENERATOR_BLOCK = 4, NJS_GENERATOR_TRY = 8, - -#define NJS_GENERATOR_ALL (NJS_GENERATOR_BLOCK \ - | NJS_GENERATOR_LOOP \ - | NJS_GENERATOR_SWITCH \ - | NJS_GENERATOR_TRY) +#define NJS_GENERATOR_ALL (NJS_GENERATOR_LOOP | NJS_GENERATOR_SWITCH) } njs_generator_block_type_t; struct njs_generator_block_s { - njs_generator_block_type_t type; /* 2 bits */ + njs_generator_block_type_t type; /* 4 bits */ nxt_str_t label; + + /* list of "continue" instruction offsets to be patched. */ njs_generator_patch_t *continuation; + /* + * list of "return" from try-catch block and "break" + * instruction offsets to be patched. + */ njs_generator_patch_t *exit; + njs_generator_block_t *next; + /* exit value index, used only for NJS_GENERATOR_TRY blocks. */ njs_index_t index; }; @@ -78,16 +84,21 @@ static nxt_int_t njs_generate_for_in_statement(njs_vm_t *vm, static nxt_noinline nxt_int_t njs_generate_start_block(njs_vm_t *vm, njs_generator_t *generator, njs_generator_block_type_t type, const nxt_str_t *label); +static njs_generator_block_t *njs_generate_lookup_block( + njs_generator_block_t *block, uint32_t mask, const nxt_str_t *label); static njs_generator_block_t *njs_generate_find_block( - njs_generator_block_t *block, uint32_t mask); -static nxt_int_t njs_generate_make_continuation_patch(njs_vm_t *vm, - njs_generator_t *generator, njs_generator_block_t *block, njs_ret_t offset); + njs_generator_block_t *block, uint32_t mask, const nxt_str_t *label); +static njs_generator_patch_t *njs_generate_make_continuation_patch(njs_vm_t *vm, + njs_generator_block_t *block, const nxt_str_t *label, njs_ret_t offset); static nxt_noinline void njs_generate_patch_block(njs_vm_t *vm, njs_generator_t *generator, njs_generator_patch_t *list); -static nxt_int_t njs_generate_make_exit_patch(njs_vm_t *vm, - njs_generator_t *generator, njs_generator_block_t *block, njs_ret_t offset); +static njs_generator_patch_t *njs_generate_make_exit_patch(njs_vm_t *vm, + njs_generator_block_t *block, const nxt_str_t *label, njs_ret_t offset); static nxt_noinline void njs_generate_patch_block_exit(njs_vm_t *vm, njs_generator_t *generator); +static const nxt_str_t *njs_generate_jump_destination(njs_vm_t *vm, + njs_generator_block_t *block, const char *inst_type, uint32_t mask, + const nxt_str_t *label1, const nxt_str_t *label2); static nxt_int_t njs_generate_continue_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node); static nxt_int_t njs_generate_break_statement(njs_vm_t *vm, @@ -208,7 +219,10 @@ static nxt_int_t njs_generate_function_debug(njs_vm_t *vm, nxt_str_t *name, njs_parser_node_error(vm, node, NJS_OBJECT_SYNTAX_ERROR, fmt, ##__VA_ARGS__) -static const nxt_str_t no_label = { 0, NULL }; +static const nxt_str_t no_label = nxt_string(""); +static const nxt_str_t return_label = nxt_string("@return"); +/* GCC and Clang complain about NULL argument passed to memcmp(). */ +static const nxt_str_t undef_label = { 0xffffffff, (u_char *) "" }; static nxt_int_t @@ -432,7 +446,7 @@ njs_generator(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) default: nxt_thread_log_debug("unknown token: %d", node->token); - njs_syntax_error(vm, "unknown token"); + njs_internal_error(vm, "Generator failed: unknown token"); return NXT_ERROR; } @@ -845,7 +859,7 @@ njs_generate_switch_statement(njs_vm_t *vm, njs_generator_t *generator, } ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_SWITCH, - &no_label); + &swtch->label); if (nxt_slow_path(ret != NXT_OK)) { return ret; } @@ -886,6 +900,7 @@ njs_generate_switch_statement(njs_vm_t *vm, njs_generator_t *generator, patch->jump_offset = njs_code_offset(generator, equal) + offsetof(njs_vmcode_equal_jump_t, offset); + patch->label = no_label; *last = patch; last = &patch->next; @@ -970,7 +985,7 @@ njs_generate_while_statement(njs_vm_t *vm, njs_generator_t *generator, /* The loop body. */ ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP, - &no_label); + &node->label); if (nxt_slow_path(ret != NXT_OK)) { return ret; } @@ -1020,7 +1035,7 @@ njs_generate_do_while_statement(njs_vm_t *vm, njs_generator_t *generator, /* The loop body. */ ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP, - &no_label); + &node->label); if (nxt_slow_path(ret != NXT_OK)) { return ret; } @@ -1067,7 +1082,7 @@ njs_generate_for_statement(njs_vm_t *vm, njs_generator_t *generator, njs_vmcode_cond_jump_t *cond_jump; ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP, - &no_label); + &node->label); if (nxt_slow_path(ret != NXT_OK)) { return ret; } @@ -1178,7 +1193,7 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator, njs_vmcode_prop_foreach_t *prop_foreach; ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_LOOP, - &no_label); + &node->label); if (nxt_slow_path(ret != NXT_OK)) { return ret; } @@ -1278,10 +1293,18 @@ njs_generate_start_block(njs_vm_t *vm, njs_generator_t *generator, static njs_generator_block_t * -njs_generate_find_block(njs_generator_block_t *block, uint32_t mask) +njs_generate_lookup_block(njs_generator_block_t *block, uint32_t mask, + const nxt_str_t *label) { + if (nxt_strstr_eq(label, &return_label)) { + mask = NJS_GENERATOR_TRY; + label = &no_label; + } + while (block != NULL) { - if (block->type & mask) { + if ((block->type & mask) != 0 + && (label->length == 0 || nxt_strstr_eq(&block->label, label))) + { return block; } @@ -1292,16 +1315,59 @@ njs_generate_find_block(njs_generator_block_t *block, uint32_t mask) } -static nxt_int_t -njs_generate_make_continuation_patch(njs_vm_t *vm, njs_generator_t *generator, - njs_generator_block_t *block, njs_ret_t offset) +static njs_generator_block_t * +njs_generate_find_block(njs_generator_block_t *block, uint32_t mask, + const nxt_str_t *label) +{ + njs_generator_block_t *dest_block; + + /* + * ES5.1: 12.8 The break Statement + * "break" without a label is valid only from within + * loop or switch statement. + */ + if ((mask & NJS_GENERATOR_ALL) == NJS_GENERATOR_ALL + && !nxt_strstr_eq(label, &no_label)) + { + mask |= NJS_GENERATOR_BLOCK; + } + + dest_block = njs_generate_lookup_block(block, mask, label); + + if (dest_block != NULL) { + + /* + * Looking for intermediate try-catch blocks. Before jumping to + * the destination finally blocks have to be executed. + */ + + while (block != NULL) { + if (block->type & NJS_GENERATOR_TRY) { + return block; + } + + if (block == dest_block) { + return block; + } + + block = block->next; + } + } + + return dest_block; +} + + +static njs_generator_patch_t * +njs_generate_make_continuation_patch(njs_vm_t *vm, njs_generator_block_t *block, + const nxt_str_t *label, njs_ret_t offset) { njs_generator_patch_t *patch; patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t)); if (nxt_slow_path(patch == NULL)) { njs_memory_error(vm); - return NXT_ERROR; + return NULL; } patch->next = block->continuation; @@ -1309,7 +1375,9 @@ njs_generate_make_continuation_patch(njs_vm_t *vm, njs_generator_t *generator, patch->jump_offset = offset; - return NXT_OK; + patch->label = *label; + + return patch; } @@ -1328,16 +1396,16 @@ njs_generate_patch_block(njs_vm_t *vm, njs_generator_t *generator, } -static nxt_int_t -njs_generate_make_exit_patch(njs_vm_t *vm, njs_generator_t *generator, - njs_generator_block_t *block, njs_ret_t offset) +static njs_generator_patch_t * +njs_generate_make_exit_patch(njs_vm_t *vm, njs_generator_block_t *block, + const nxt_str_t *label, njs_ret_t offset) { njs_generator_patch_t *patch; patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t)); if (nxt_slow_path(patch == NULL)) { njs_memory_error(vm); - return NXT_ERROR; + return NULL; } patch->next = block->exit; @@ -1345,7 +1413,9 @@ njs_generate_make_exit_patch(njs_vm_t *vm, njs_generator_t *generator, patch->jump_offset = offset; - return NXT_OK; + patch->label = *label; + + return patch; } @@ -1363,43 +1433,79 @@ njs_generate_patch_block_exit(njs_vm_t *vm, njs_generator_t *generator) } +/* + * TODO: support multiple destination points from within try-catch block. + */ +static const nxt_str_t * +njs_generate_jump_destination(njs_vm_t *vm, njs_generator_block_t *block, + const char *inst_type, uint32_t mask, const nxt_str_t *label1, + const nxt_str_t *label2) +{ + njs_generator_block_t *block1, *block2; + + if (label1->length == undef_label.length) { + return label2; + } + + if (label2->length == undef_label.length) { + return label1; + } + + block1 = njs_generate_lookup_block(block, mask, label1); + block2 = njs_generate_lookup_block(block, mask, label2); + + if (block1 != block2) { + njs_internal_error(vm, "%s instructions with different labels " + "(\"%V\" vs \"%V\") " + "from try-catch block are not supported", inst_type, + label1, label2); + + return NULL; + } + + return label1; +} + + static nxt_int_t njs_generate_continue_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { + const nxt_str_t *label, *dest; njs_vmcode_jump_t *jump; njs_generator_patch_t *patch; njs_generator_block_t *block; - block = njs_generate_find_block(generator->block, - NJS_GENERATOR_LOOP | NJS_GENERATOR_TRY); + label = &node->label; + + block = njs_generate_find_block(generator->block, NJS_GENERATOR_LOOP, + label); if (nxt_slow_path(block == NULL)) { goto syntax_error; } - if (block->type == NJS_GENERATOR_TRY - && njs_generate_find_block(block->next, NJS_GENERATOR_LOOP) == NULL) - { - goto syntax_error; + if (block->type == NJS_GENERATOR_TRY && block->continuation != NULL) { + dest = njs_generate_jump_destination(vm, block->next, "continue", + NJS_GENERATOR_LOOP, + &block->continuation->label, + label); + if (nxt_slow_path(dest == NULL)) { + return NXT_ERROR; + } } - /* TODO: LABEL */ - - patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t)); - - if (nxt_fast_path(patch != NULL)) { - patch->next = block->continuation; - block->continuation = patch; - - njs_generate_code(generator, njs_vmcode_jump_t, jump); - jump->code.operation = njs_vmcode_jump; - jump->code.operands = NJS_VMCODE_NO_OPERAND; - jump->code.retval = NJS_VMCODE_NO_RETVAL; - jump->offset = offsetof(njs_vmcode_jump_t, offset); + njs_generate_code(generator, njs_vmcode_jump_t, jump); + jump->code.operation = njs_vmcode_jump; + jump->code.operands = NJS_VMCODE_NO_OPERAND; + jump->code.retval = NJS_VMCODE_NO_RETVAL; + jump->offset = offsetof(njs_vmcode_jump_t, offset); - patch->jump_offset = njs_code_offset(generator, jump) - + offsetof(njs_vmcode_jump_t, offset); + patch = njs_generate_make_continuation_patch(vm, block, label, + njs_code_offset(generator, jump) + + offsetof(njs_vmcode_jump_t, offset)); + if (nxt_slow_path(patch == NULL)) { + return NXT_ERROR; } return NXT_OK; @@ -1416,38 +1522,38 @@ static nxt_int_t njs_generate_break_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { + const nxt_str_t *label, *dest; njs_vmcode_jump_t *jump; njs_generator_patch_t *patch; njs_generator_block_t *block; - block = njs_generate_find_block(generator->block, NJS_GENERATOR_ALL); + label = &node->label; + block = njs_generate_find_block(generator->block, NJS_GENERATOR_ALL, label); if (nxt_slow_path(block == NULL)) { goto syntax_error; } - if (block->type == NJS_GENERATOR_TRY - && njs_generate_find_block(block->next, NJS_GENERATOR_ALL) == NULL) - { - goto syntax_error; + if (block->type == NJS_GENERATOR_TRY && block->exit != NULL) { + dest = njs_generate_jump_destination(vm, block->next, "break/return", + NJS_GENERATOR_ALL, + &block->exit->label, label); + if (nxt_slow_path(dest == NULL)) { + return NXT_ERROR; + } } - /* TODO: LABEL: loop and switch may have label, block must have label. */ - - patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t)); - - if (nxt_fast_path(patch != NULL)) { - patch->next = block->exit; - block->exit = patch; - - njs_generate_code(generator, njs_vmcode_jump_t, jump); - jump->code.operation = njs_vmcode_jump; - jump->code.operands = NJS_VMCODE_NO_OPERAND; - jump->code.retval = NJS_VMCODE_NO_RETVAL; - jump->offset = offsetof(njs_vmcode_jump_t, offset); + njs_generate_code(generator, njs_vmcode_jump_t, jump); + jump->code.operation = njs_vmcode_jump; + jump->code.operands = NJS_VMCODE_NO_OPERAND; + jump->code.retval = NJS_VMCODE_NO_RETVAL; + jump->offset = offsetof(njs_vmcode_jump_t, offset); - patch->jump_offset = njs_code_offset(generator, jump) - + offsetof(njs_vmcode_jump_t, offset); + patch = njs_generate_make_exit_patch(vm, block, label, + njs_code_offset(generator, jump) + + offsetof(njs_vmcode_jump_t, offset)); + if (nxt_slow_path(patch == NULL)) { + return NXT_ERROR; } return NXT_OK; @@ -1483,7 +1589,7 @@ njs_generate_block_statement(njs_vm_t *vm, njs_generator_t *generator, nxt_int_t ret; ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_BLOCK, - &no_label); + &node->label); if (nxt_slow_path(ret != NXT_OK)) { return ret; } @@ -2437,9 +2543,10 @@ njs_generate_return_statement(njs_vm_t *vm, njs_generator_t *generator, { nxt_int_t ret; njs_index_t index; + const nxt_str_t *dest; njs_vmcode_return_t *code; njs_generator_patch_t *patch; - njs_generator_block_t *block; + njs_generator_block_t *block, *immediate, *top; njs_vmcode_try_return_t *try_return; ret = njs_generator(vm, generator, node->right); @@ -2452,13 +2559,13 @@ njs_generate_return_statement(njs_vm_t *vm, njs_generator_t *generator, index = node->right->index; } else { - index = njs_value_index(vm, &njs_value_void, - generator->runtime); + index = njs_value_index(vm, &njs_value_void, generator->runtime); } - block = njs_generate_find_block(generator->block, NJS_GENERATOR_TRY); + immediate = njs_generate_lookup_block(generator->block, NJS_GENERATOR_TRY, + &no_label); - if (nxt_fast_path(block == NULL)) { + if (nxt_fast_path(immediate == NULL)) { njs_generate_code(generator, njs_vmcode_return_t, code); code->code.operation = njs_vmcode_return; code->code.operands = NJS_VMCODE_1OPERAND; @@ -2470,24 +2577,43 @@ njs_generate_return_statement(njs_vm_t *vm, njs_generator_t *generator, return NXT_OK; } - patch = nxt_mp_alloc(vm->mem_pool, sizeof(njs_generator_patch_t)); - if (nxt_slow_path(patch == NULL)) { - return NXT_ERROR; + if (immediate->type == NJS_GENERATOR_TRY && immediate->exit != NULL) { + dest = njs_generate_jump_destination(vm, immediate->next, + "break/return", + NJS_GENERATOR_ALL, + &immediate->exit->label, + &return_label); + if (nxt_slow_path(dest == NULL)) { + return NXT_ERROR; + } } - patch->next = block->exit; - block->exit = patch; + top = immediate; + block = immediate->next; + + while (block != NULL) { + if (block->type & NJS_GENERATOR_TRY) { + top = block; + } + + block = block->next; + } njs_generate_code(generator, njs_vmcode_try_return_t, try_return); try_return->code.operation = njs_vmcode_try_return; try_return->code.operands = NJS_VMCODE_2OPERANDS; try_return->code.retval = NJS_VMCODE_RETVAL; try_return->retval = index; - try_return->save = block->index; - + try_return->save = top->index; try_return->offset = offsetof(njs_vmcode_try_return_t, offset); - patch->jump_offset = njs_code_offset(generator, try_return) - + offsetof(njs_vmcode_try_return_t, offset); + + patch = njs_generate_make_exit_patch(vm, immediate, &return_label, + njs_code_offset(generator, try_return) + + offsetof(njs_vmcode_try_return_t, + offset)); + if (nxt_slow_path(patch == NULL)) { + return NXT_ERROR; + } return NXT_OK; } @@ -2648,9 +2774,13 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, catch_end_offset; nxt_int_t ret; njs_index_t exception_index, exit_index, catch_index; + nxt_str_t try_cont_label, try_exit_label, + catch_cont_label, catch_exit_label; + const nxt_str_t *dest_label; njs_vmcode_catch_t *catch; njs_vmcode_finally_t *finally; njs_vmcode_try_end_t *try_end, *catch_end; + njs_generator_patch_t *patch; njs_generator_block_t *block, *try_block, *catch_block; njs_vmcode_try_start_t *try_start; njs_vmcode_try_trampoline_t *try_break, *try_continue; @@ -2694,6 +2824,9 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, return ret; } + try_exit_label = undef_label; + try_cont_label = undef_label; + njs_generate_code(generator, njs_vmcode_try_end_t, try_end); try_end_offset = njs_code_offset(generator, try_end); try_end->code.operation = njs_vmcode_try_end; @@ -2701,6 +2834,8 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, try_end->code.retval = NJS_VMCODE_NO_RETVAL; if (try_block->exit != NULL) { + try_exit_label = try_block->exit->label; + njs_generate_patch_block(vm, generator, try_block->exit); njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_break); @@ -2717,6 +2852,8 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, } if (try_block->continuation != NULL) { + try_cont_label = try_block->continuation->label; + njs_generate_patch_block(vm, generator, try_block->continuation); njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_continue); @@ -2740,6 +2877,9 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, node = node->right; + catch_exit_label = undef_label; + catch_cont_label = undef_label; + if (node->token == NJS_TOKEN_CATCH) { /* A "try/catch" case. */ @@ -2780,21 +2920,31 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, * by njs_generate_continue_statement() */ block = njs_generate_find_block(generator->block, - NJS_GENERATOR_LOOP); + NJS_GENERATOR_LOOP, + &try_cont_label); - njs_generate_make_continuation_patch(vm, generator, block, + patch = njs_generate_make_continuation_patch(vm, block, + &try_cont_label, njs_code_offset(generator, finally) + offsetof(njs_vmcode_finally_t, continue_offset)); + if (nxt_slow_path(patch == NULL)) { + return NXT_ERROR; + } } if (try_block->exit != NULL) { block = njs_generate_find_block(generator->block, - NJS_GENERATOR_ALL); + NJS_GENERATOR_ALL, + &try_exit_label); if (block != NULL) { - njs_generate_make_exit_patch(vm, generator, block, + patch = njs_generate_make_exit_patch(vm, block, + &try_exit_label, njs_code_offset(generator, finally) + offsetof(njs_vmcode_finally_t, break_offset)); + if (nxt_slow_path(patch == NULL)) { + return NXT_ERROR; + } } } } @@ -2838,6 +2988,8 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, catch_end->code.retval = NJS_VMCODE_NO_RETVAL; if (catch_block->exit != NULL) { + catch_exit_label = catch_block->exit->label; + njs_generate_patch_block(vm, generator, catch_block->exit); njs_generate_code(generator, njs_vmcode_try_trampoline_t, @@ -2855,6 +3007,8 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, } if (catch_block->continuation != NULL) { + catch_cont_label = catch_block->continuation->label; + njs_generate_patch_block(vm, generator, catch_block->continuation); @@ -2923,27 +3077,57 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, if (try_block->continuation != NULL || (catch_block && catch_block->continuation != NULL)) { + dest_label = njs_generate_jump_destination(vm, generator->block, + "try continue", + NJS_GENERATOR_LOOP, + &try_cont_label, + &catch_cont_label); + if (nxt_slow_path(dest_label == NULL)) { + return NXT_ERROR; + } + /* * block != NULL is checked * by njs_generate_continue_statement() */ block = njs_generate_find_block(generator->block, - NJS_GENERATOR_LOOP); + NJS_GENERATOR_LOOP, dest_label); - njs_generate_make_continuation_patch(vm, generator, block, - njs_code_offset(generator, finally) - + offsetof(njs_vmcode_finally_t, continue_offset)); + patch = njs_generate_make_continuation_patch(vm, block, dest_label, + njs_code_offset(generator, finally) + + offsetof(njs_vmcode_finally_t, continue_offset)); + if (nxt_slow_path(patch == NULL)) { + return NXT_ERROR; + } } if (try_block->exit != NULL || (catch_block != NULL && catch_block->exit != NULL)) { + dest_label = njs_generate_jump_destination(vm, generator->block, + "try break/return", + NJS_GENERATOR_ALL + | NJS_GENERATOR_TRY, + &try_exit_label, + &catch_exit_label); + if (nxt_slow_path(dest_label == NULL)) { + return NXT_ERROR; + } + + /* + * block can be NULL for "return" instruction in + * outermost try-catch block. + */ block = njs_generate_find_block(generator->block, - NJS_GENERATOR_ALL); + NJS_GENERATOR_ALL + | NJS_GENERATOR_TRY, dest_label); if (block != NULL) { - njs_generate_make_exit_patch(vm, generator, block, + patch = njs_generate_make_exit_patch(vm, block, dest_label, njs_code_offset(generator, finally) + offsetof(njs_vmcode_finally_t, break_offset)); + if (nxt_slow_path(patch == NULL)) { + return NXT_ERROR; + } } } } diff --git a/njs/njs_lexer.c b/njs/njs_lexer.c index 38853515..338304d0 100644 --- a/njs/njs_lexer.c +++ b/njs/njs_lexer.c @@ -292,6 +292,32 @@ void njs_lexer_rollback(njs_lexer_t *lexer) { lexer->start = lexer->prev_start; + lexer->token = lexer->prev_token; +} + + +njs_token_t +njs_lexer_peek_token(njs_lexer_t *lexer) +{ + u_char *start; + njs_token_t token; + + start = lexer->start; + + while (start < lexer->end) { + token = njs_tokens[*start++]; + + switch (token) { + case NJS_TOKEN_SPACE: + case NJS_TOKEN_LINE_END: + continue; + + default: + return token; + } + } + + return NJS_TOKEN_END; } diff --git a/njs/njs_parser.c b/njs/njs_parser.c index 8f6a8ee6..25080c4c 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -21,6 +21,8 @@ static njs_token_t njs_parser_block_statement(njs_vm_t *vm, njs_parser_t *parser); static njs_token_t njs_parser_block(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token); +static njs_token_t njs_parser_labelled_statement(njs_vm_t *vm, + njs_parser_t *parser); static njs_token_t njs_parser_function_declaration(njs_vm_t *vm, njs_parser_t *parser); static njs_token_t njs_parser_function_lambda(njs_vm_t *vm, @@ -42,10 +44,8 @@ static njs_token_t njs_parser_for_var_in_statement(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *name); static njs_token_t njs_parser_for_in_statement(njs_vm_t *vm, njs_parser_t *parser, nxt_str_t *name, njs_token_t token); -static njs_token_t njs_parser_continue_statement(njs_vm_t *vm, - njs_parser_t *parser); -static njs_token_t njs_parser_break_statement(njs_vm_t *vm, - njs_parser_t *parser); +static njs_token_t njs_parser_brk_statement(njs_vm_t *vm, + njs_parser_t *parser, njs_token_t token); static njs_token_t njs_parser_try_statement(njs_vm_t *vm, njs_parser_t *parser); static njs_token_t njs_parser_try_block(njs_vm_t *vm, njs_parser_t *parser); static njs_token_t njs_parser_throw_statement(njs_vm_t *vm, @@ -216,6 +216,7 @@ njs_parser_scope_begin(njs_vm_t *vm, njs_parser_t *parser, njs_scope_t type) scope->argument_closures = 0; nxt_queue_init(&scope->nested); + nxt_lvlhsh_init(&scope->labels); nxt_lvlhsh_init(&scope->variables); nxt_lvlhsh_init(&scope->references); @@ -354,19 +355,17 @@ njs_parser_statement(njs_vm_t *vm, njs_parser_t *parser, default: + if (token == NJS_TOKEN_NAME + && njs_lexer_peek_token(parser->lexer) == NJS_TOKEN_COLON) + { + return njs_parser_labelled_statement(vm, parser); + } + switch (token) { case NJS_TOKEN_VAR: token = njs_parser_var_statement(vm, parser); break; - case NJS_TOKEN_CONTINUE: - token = njs_parser_continue_statement(vm, parser); - break; - - case NJS_TOKEN_BREAK: - token = njs_parser_break_statement(vm, parser); - break; - case NJS_TOKEN_RETURN: token = njs_parser_return_statement(vm, parser); break; @@ -375,6 +374,11 @@ njs_parser_statement(njs_vm_t *vm, njs_parser_t *parser, token = njs_parser_throw_statement(vm, parser); break; + case NJS_TOKEN_CONTINUE: + case NJS_TOKEN_BREAK: + token = njs_parser_brk_statement(vm, parser, token); + break; + default: token = njs_parser_expression(vm, parser, token); break; @@ -505,6 +509,63 @@ njs_parser_variable_reference(njs_vm_t *vm, njs_parser_t *parser, } +static njs_token_t +njs_parser_labelled_statement(njs_vm_t *vm, njs_parser_t *parser) +{ + uint32_t hash; + njs_ret_t ret; + nxt_str_t name; + njs_token_t token; + njs_variable_t *label; + + name = parser->lexer->text; + hash = parser->lexer->key_hash; + + label = njs_label_find(vm, parser->scope, &name, hash); + if (nxt_slow_path(label != NULL)) { + njs_parser_syntax_error(vm, parser, "Label \"%V\" " + "has already been declared", &name); + return NJS_TOKEN_ILLEGAL; + } + + label = njs_label_add(vm, parser->scope, &name, hash); + if (nxt_slow_path(label == NULL)) { + return NJS_TOKEN_ERROR; + } + + token = njs_parser_token(parser); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return token; + } + + token = njs_parser_match(vm, parser, token, NJS_TOKEN_COLON); + if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return token; + } + + token = njs_parser_statement(vm, parser, token); + + if (nxt_fast_path(token > NJS_TOKEN_ILLEGAL)) { + + if (parser->node != NULL) { + /* The statement is not empty block or just semicolon. */ + + ret = njs_name_copy(vm, &parser->node->label, &name); + if (nxt_slow_path(ret != NXT_OK)) { + return NJS_TOKEN_ERROR; + } + + ret = njs_label_remove(vm, parser->scope, &name, hash); + if (nxt_slow_path(ret != NXT_OK)) { + return NJS_TOKEN_ERROR; + } + } + } + + return token; +} + + static njs_token_t njs_parser_function_declaration(njs_vm_t *vm, njs_parser_t *parser) { @@ -1483,12 +1544,15 @@ njs_parser_for_in_statement(njs_vm_t *vm, njs_parser_t *parser, nxt_str_t *name, static njs_token_t -njs_parser_continue_statement(njs_vm_t *vm, njs_parser_t *parser) +njs_parser_brk_statement(njs_vm_t *vm, njs_parser_t *parser, + njs_token_t token) { - njs_token_t token; + uint32_t hash; + njs_ret_t ret; + nxt_str_t name; njs_parser_node_t *node; - node = njs_parser_node_new(vm, parser, NJS_TOKEN_CONTINUE); + node = njs_parser_node_new(vm, parser, token); if (nxt_slow_path(node == NULL)) { return NJS_TOKEN_ERROR; } @@ -1503,37 +1567,21 @@ njs_parser_continue_statement(njs_vm_t *vm, njs_parser_t *parser) case NJS_TOKEN_LINE_END: return njs_parser_token(parser); - case NJS_TOKEN_SEMICOLON: - case NJS_TOKEN_CLOSE_BRACE: - case NJS_TOKEN_END: - return token; - - default: - /* TODO: LABEL */ - return NJS_TOKEN_ILLEGAL; - } -} - - -static njs_token_t -njs_parser_break_statement(njs_vm_t *vm, njs_parser_t *parser) -{ - njs_token_t token; - njs_parser_node_t *node; - - node = njs_parser_node_new(vm, parser, NJS_TOKEN_BREAK); - if (nxt_slow_path(node == NULL)) { - return NJS_TOKEN_ERROR; - } - - node->token_line = parser->lexer->token_line; - parser->node = node; + case NJS_TOKEN_NAME: + name = parser->lexer->text; + hash = parser->lexer->key_hash; - token = njs_lexer_token(parser->lexer); + if (njs_label_find(vm, parser->scope, &name, hash) == NULL) { + njs_parser_syntax_error(vm, parser, "Undefined label \"%V\"", + &name); + return NJS_TOKEN_ILLEGAL; + } - switch (token) { + ret = njs_name_copy(vm, &parser->node->label, &parser->lexer->text); + if (nxt_slow_path(ret != NXT_OK)) { + return NJS_TOKEN_ERROR; + } - case NJS_TOKEN_LINE_END: return njs_parser_token(parser); case NJS_TOKEN_SEMICOLON: diff --git a/njs/njs_parser.h b/njs/njs_parser.h index 4b8f68bd..501e806e 100644 --- a/njs/njs_parser.h +++ b/njs/njs_parser.h @@ -243,6 +243,7 @@ struct njs_parser_scope_s { nxt_queue_t nested; njs_parser_scope_t *parent; + nxt_lvlhsh_t labels; nxt_lvlhsh_t variables; nxt_lvlhsh_t references; @@ -274,6 +275,8 @@ struct njs_parser_node_s { njs_parser_node_t *object; } u; + nxt_str_t label; + njs_index_t index; /* @@ -306,6 +309,7 @@ typedef struct { njs_token_t njs_lexer_token(njs_lexer_t *lexer); void njs_lexer_rollback(njs_lexer_t *lexer); +njs_token_t njs_lexer_peek_token(njs_lexer_t *lexer); nxt_int_t njs_lexer_keywords_init(nxt_mp_t *mcp, nxt_lvlhsh_t *hash); njs_token_t njs_lexer_keyword(njs_lexer_t *lexer); diff --git a/njs/njs_variable.c b/njs/njs_variable.c index d3b8e2b4..f543552d 100644 --- a/njs/njs_variable.c +++ b/njs/njs_variable.c @@ -92,6 +92,74 @@ njs_variable_add(njs_vm_t *vm, njs_parser_scope_t *scope, nxt_str_t *name, } +njs_variable_t * +njs_label_add(njs_vm_t *vm, njs_parser_scope_t *scope, nxt_str_t *name, + uint32_t hash) +{ + nxt_int_t ret; + njs_variable_t *label; + nxt_lvlhsh_query_t lhq; + + lhq.key_hash = hash; + lhq.key = *name; + lhq.proto = &njs_variables_hash_proto; + + if (nxt_lvlhsh_find(&scope->labels, &lhq) == NXT_OK) { + return lhq.value; + } + + label = njs_variable_alloc(vm, &lhq.key, NJS_VARIABLE_CONST); + if (nxt_slow_path(label == NULL)) { + return label; + } + + lhq.replace = 0; + lhq.value = label; + lhq.pool = vm->mem_pool; + + ret = nxt_lvlhsh_insert(&scope->labels, &lhq); + + if (nxt_fast_path(ret == NXT_OK)) { + return label; + } + + nxt_mp_free(vm->mem_pool, label->name.start); + nxt_mp_free(vm->mem_pool, label); + + njs_internal_error(vm, "lvlhsh insert failed"); + + return NULL; +} + + +njs_ret_t +njs_label_remove(njs_vm_t *vm, njs_parser_scope_t *scope, nxt_str_t *name, + uint32_t hash) +{ + nxt_int_t ret; + njs_variable_t *label; + nxt_lvlhsh_query_t lhq; + + lhq.key_hash = hash; + lhq.key = *name; + lhq.proto = &njs_variables_hash_proto; + lhq.pool = vm->mem_pool; + + ret = nxt_lvlhsh_delete(&scope->labels, &lhq); + + if (nxt_fast_path(ret == NXT_OK)) { + label = lhq.value; + nxt_mp_free(vm->mem_pool, label->name.start); + nxt_mp_free(vm->mem_pool, label); + + } else { + njs_internal_error(vm, "lvlhsh delete failed"); + } + + return ret; +} + + static nxt_int_t njs_reference_hash_test(nxt_lvlhsh_query_t *lhq, void *data) { @@ -351,6 +419,30 @@ not_found: } +njs_variable_t * +njs_label_find(njs_vm_t *vm, njs_parser_scope_t *scope, nxt_str_t *name, + uint32_t hash) +{ + nxt_lvlhsh_query_t lhq; + + lhq.key_hash = hash; + lhq.key = *name; + lhq.proto = &njs_variables_hash_proto; + + for ( ;; ) { + if (nxt_lvlhsh_find(&scope->labels, &lhq) == NXT_OK) { + return lhq.value; + } + + scope = scope->parent; + + if (scope == NULL) { + return NULL; + } + } +} + + static njs_ret_t njs_variable_reference_resolve(njs_vm_t *vm, njs_variable_reference_t *vr, njs_parser_scope_t *node_scope) diff --git a/njs/njs_variable.h b/njs/njs_variable.h index a812b9d5..3af51872 100644 --- a/njs/njs_variable.h +++ b/njs/njs_variable.h @@ -54,6 +54,12 @@ typedef struct { njs_variable_t *njs_variable_add(njs_vm_t *vm, njs_parser_scope_t *scope, nxt_str_t *name, uint32_t hash, njs_variable_type_t type); +njs_variable_t * njs_label_add(njs_vm_t *vm, njs_parser_scope_t *scope, + nxt_str_t *name, uint32_t hash); +njs_variable_t *njs_label_find(njs_vm_t *vm, njs_parser_scope_t *scope, + nxt_str_t *name, uint32_t hash); +njs_ret_t njs_label_remove(njs_vm_t *vm, njs_parser_scope_t *scope, + nxt_str_t *name, uint32_t hash); njs_ret_t njs_variable_reference(njs_vm_t *vm, njs_parser_scope_t *scope, njs_parser_node_t *node, nxt_str_t *name, uint32_t hash, njs_reference_type_t type); diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index cc733d59..c31ef3e4 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -2434,6 +2434,9 @@ static njs_unit_test_t njs_test[] = { nxt_string("break"), nxt_string("SyntaxError: Illegal break statement in 1") }, + { nxt_string("{break}"), + nxt_string("SyntaxError: Illegal break statement in 1") }, + { nxt_string("\nbreak"), nxt_string("SyntaxError: Illegal break statement in 2") }, @@ -2494,6 +2497,268 @@ static njs_unit_test_t njs_test[] = "for (i in a) if (a[i] > 4) break; s += a[i]; s"), nxt_string("5") }, + /* Labels. */ + + { nxt_string("var n = 0; a:{n++}; a:{n++}; n"), + nxt_string("2") }, + + { nxt_string("a: throw 'a'"), + nxt_string("a") }, + + { nxt_string("a : var n = 0; b :++n"), + nxt_string("1") }, + + { nxt_string("a:{a:1}"), + nxt_string("SyntaxError: Label \"a\" has already been declared in 1") }, + + { nxt_string("for (var i in [1]) {break b}"), + nxt_string("SyntaxError: Undefined label \"b\" in 1") }, + + { nxt_string("for (var i in [1]) {continue b}"), + nxt_string("SyntaxError: Undefined label \"b\" in 1") }, + + { nxt_string("a:{break b}"), + nxt_string("SyntaxError: Undefined label \"b\" in 1") }, + + { nxt_string("a:{continue b}"), + nxt_string("SyntaxError: Undefined label \"b\" in 1") }, + +#if 0 /* TODO */ + { nxt_string("a:{1; break a}"), + nxt_string("1") }, +#endif + + { nxt_string("var a = 0; a:{a++}; a"), + nxt_string("1") }, + + { nxt_string("var a = 0; a:{break a; a++}; a"), + nxt_string("0") }, + + { nxt_string("var r = 0; " + "out: for (var i in [1,2,3]) { if (i == 2) {break out;}; r++}; r"), + nxt_string("2") }, + + { nxt_string("var r = 0; " + "out: for (var i = 0; i < 5; i++) { if (i == 2) {break out;}; r++}; r"), + nxt_string("2") }, + + { nxt_string("var l1 = 0, l2 = 0; " + "out: " + "for (var i in [1,2,3]) { " + " for (var j in [1,2,3]) { " + " if (i == 1 && j == 1) {break;}" + " l2++;" + " }" + " l1++;" + "}; [l1, l2]"), + nxt_string("3,7") }, + + { nxt_string("var l1 = 0, l2 = 0; " + "out: " + "for (var i in [1,2,3]) { " + " for (var j in [1,2,3]) { " + " if (i == 1 && j == 1) {break out;}" + " l2++;" + " }" + " l1++;" + "}; [l1, l2]"), + nxt_string("1,4") }, + + { nxt_string("var l1 = 0, l2 = 0; " + "out: " + "for (var i in [1,2,3]) { " + " for (var j in [1,2,3]) { " + " if (i == 1 && j == 1) {continue out;}" + " l2++;" + " }" + " l1++;" + "}; [l1, l2]"), + nxt_string("2,7") }, + + { nxt_string("var l1 = 0, l2 = 0; " + "out: " + "for (var i in [1,2,3]) { " + " l1++;" + " switch (i) { " + " case '1':" + " break out;" + " default:" + " }" + " l2++;" + "}; [l1, l2]"), + nxt_string("2,1") }, + + { nxt_string("var l1 = 0, l2 = 0; " + "out: " + "for (var i in [1,2,3]) { " + " l1++;" + " switch (i) { " + " case '1':" + " continue out;" + " default:" + " }" + " l2++;" + "}; [l1, l2]"), + nxt_string("3,2") }, + + { nxt_string("var l1 = 0, l2 = 0, i = 0, j; " + "out: " + "while (i < 3) { " + " j = 0;" + " while (j < 3) { " + " if (i == 1 && j == 1) {break out;}" + " l2++;" + " j++;" + " }" + " l1++;" + " i++;" + "}; [l1, l2]"), + nxt_string("1,4") }, + + { nxt_string("var l1 = 0, l2 = 0, i = 0, j; " + "out: " + "while (i < 3) { " + " j = 0;" + " while (j < 3) { " + " if (i == 1 && j == 1) {i++; continue out;}" + " l2++;" + " j++;" + " }" + " l1++;" + " i++;" + "}; [l1, l2]"), + nxt_string("2,7") }, + + { nxt_string("var l1 = 0, l2 = 0, i = 0, j; " + "out: " + "do { " + " j = 0;" + " do { " + " if (i == 1 && j == 1) {break out;}" + " l2++;" + " j++;" + " } while (j < 3)" + " l1++;" + " i++;" + "} while (i < 3); [l1, l2]"), + nxt_string("1,4") }, + + { nxt_string("var l1 = 0, l2 = 0, i = 0, j; " + "out: " + "do { " + " j = 0;" + " do { " + " if (i == 1 && j == 1) {i++; continue out;}" + " l2++;" + " j++;" + " } while (j < 3)" + " l1++;" + " i++;" + "} while (i < 3); [l1, l2]"), + nxt_string("2,7") }, + + { nxt_string("out1: while (1) { out2: while (1) { " + " try { break out1; break out2; } catch (e) {}" + "}}"), + nxt_string("InternalError: break/return instructions with different labels " + "(\"out1\" vs \"out2\") from try-catch block are not supported") }, + + { nxt_string("out1: while (1) { out2: while (1) { " + " try { } catch (e) {break out1; break out2;} finally {}" + "}}"), + nxt_string("InternalError: break/return instructions with different labels " + "(\"out1\" vs \"out2\") from try-catch block are not supported") }, + + { nxt_string("out1: while (1) { out2: while (1) { " + " try { break out1; } catch (e) {break out2;} finally {}" + "}}"), + nxt_string("InternalError: try break/return instructions with different labels " + "(\"out1\" vs \"out2\") from try-catch block are not supported") }, + + { nxt_string("out1: while (1) { out2: while (1) { " + " try { break out1; break out2; } finally {}" + "}}"), + nxt_string("InternalError: break/return instructions with different labels " + "(\"out1\" vs \"out2\") from try-catch block are not supported") }, + + { nxt_string("out1: while (1) { out2: while (1) { " + " try { continue out1; continue out2; } catch (e) {}" + "}}"), + nxt_string("InternalError: continue instructions with different labels " + "(\"out1\" vs \"out2\") from try-catch block are not supported") }, + + { nxt_string("out1: while (1) { out2: while (1) { " + " try { continue out1; } catch (e) {continue out2;} finally {}" + "}}"), + nxt_string("InternalError: try continue instructions with different labels " + "(\"out1\" vs \"out2\") from try-catch block are not supported") }, + + { nxt_string("function f() {" + " a:{ try { try { return 'a'; } catch (e) {break a;} finally {} } " + " catch (e) {} finally {}; }" + "}"), + nxt_string("InternalError: try break/return instructions with different labels " + "(\"@return\" vs \"a\") from try-catch block are not supported") }, + + { nxt_string("a:{ try { try { continue a; } catch (e) {} finally {} } " + " catch (e) {} finally {}; " + "}"), + nxt_string("SyntaxError: Illegal continue statement in 1") }, + + { nxt_string("var i = 0, j = 0, r = 0;" + "out1: while (i < 3) " + "{ " + " i++;" + " out2: while (j < 3) { " + " j++; try { break out1; } catch (e) {} finally {r++}" + " }" + "}; [i, j, r]"), + nxt_string("1,1,1") }, + + { nxt_string("var i = 0, j = 0, r = 0;" + "out1: while (i < 3) " + "{ " + " i++;" + " out2: while (j < 3) { " + " j++; try { continue out1; } catch (e) {} finally {r++}" + " }" + "}; [i, j, r]"), + nxt_string("3,3,3") }, + + { nxt_string("var c=0,fin=0;" + "try {" + " while (c < 2) {" + " try { c += 1; throw 'e';}" + " finally { fin = 1; break;}" + " fin = -1;" + " c += 2;" + " }" + "} catch(e) {c = 10;}; [c, fin]"), + nxt_string("1,1") }, + + /* jumping out of a nested try-catch block. */ + + { nxt_string("var r = 0; " + "function f () { try { try {return 'a';} finally { r++; }} " + " finally { r++; } }; " + "[f(), r]"), + nxt_string("a,2") }, + + { nxt_string("function f(n) { " + " var r1 = 0, r2 = 0, r3 = 0;" + " a:{ try { try { " + " if (n == 0) { break a; } " + " if (n == 1) { throw 'a'; } " + " } " + " catch (e) { break a; } finally { r1++; } } " + " catch (e) {} " + " finally { r2++; } " + " r3++; " + " }; " + "return [r1, r2, r3]" + "}; njs.dump([f(0), f(1), f(3)])"), + nxt_string("[[1,1,0],[1,1,0],[1,1,1]]") }, + /**/ { nxt_string("var i; for (i = 0; i < 10; i++) { i += 1 } i"),