From: Dmitry Volyntsev Date: Wed, 26 Dec 2018 10:23:16 +0000 (+0300) Subject: Fixed exit instructions inside try blocks. X-Git-Tag: 0.2.8~106 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=0f2eadea5236ba9be728350fb494566387aa7f01;p=njs.git Fixed exit instructions inside try blocks. Fixed instructions: continue, break, return. --- diff --git a/njs/njs_disassembler.c b/njs/njs_disassembler.c index 2097bdd9..da16443a 100644 --- a/njs/njs_disassembler.c +++ b/njs/njs_disassembler.c @@ -125,8 +125,6 @@ static njs_code_name_t code_names[] = { { njs_vmcode_throw, sizeof(njs_vmcode_throw_t), nxt_string("THROW ") }, - { njs_vmcode_finally, sizeof(njs_vmcode_finally_t), - nxt_string("FINALLY ") }, }; @@ -162,15 +160,18 @@ njs_disassemble(u_char *start, u_char *end) njs_vmcode_3addr_t *code3; njs_vmcode_array_t *array; njs_vmcode_catch_t *catch; + njs_vmcode_finally_t *finally; njs_vmcode_try_end_t *try_end; njs_vmcode_try_start_t *try_start; njs_vmcode_operation_t operation; njs_vmcode_cond_jump_t *cond_jump; njs_vmcode_test_jump_t *test_jump; njs_vmcode_prop_next_t *prop_next; + njs_vmcode_try_return_t *try_return; njs_vmcode_equal_jump_t *equal; njs_vmcode_prop_foreach_t *prop_foreach; njs_vmcode_method_frame_t *method; + njs_vmcode_try_trampoline_t *try_tramp; njs_vmcode_function_frame_t *function; p = start; @@ -323,8 +324,9 @@ njs_disassemble(u_char *start, u_char *end) if (operation == njs_vmcode_try_start) { try_start = (njs_vmcode_try_start_t *) p; - printf("%05zd TRY START %04zX +%zd\n", - p - start, (size_t) try_start->value, + printf("%05zd TRY START %04zX %04zX +%zd\n", + p - start, (size_t) try_start->exception_value, + (size_t) try_start->exit_value, (size_t) try_start->offset); p += sizeof(njs_vmcode_try_start_t); @@ -332,6 +334,43 @@ njs_disassemble(u_char *start, u_char *end) continue; } + if (operation == njs_vmcode_try_break) { + try_tramp = (njs_vmcode_try_trampoline_t *) p; + + printf("%05zd TRY BREAK %04zX %zd\n", + p - start, (size_t) try_tramp->exit_value, + (size_t) try_tramp->offset); + + p += sizeof(njs_vmcode_try_trampoline_t); + + continue; + } + + if (operation == njs_vmcode_try_continue) { + try_tramp = (njs_vmcode_try_trampoline_t *) p; + + printf("%05zd TRY CONTINUE %04zX %zd\n", + p - start, (size_t) try_tramp->exit_value, + (size_t) try_tramp->offset); + + p += sizeof(njs_vmcode_try_trampoline_t); + + continue; + } + + if (operation == njs_vmcode_try_return) { + try_return = (njs_vmcode_try_return_t *) p; + + printf("%05zd TRY RETURN %04zX %04zX +%zd\n", + p - start, (size_t) try_return->save, + (size_t) try_return->retval, + (size_t) try_return->offset); + + p += sizeof(njs_vmcode_try_return_t); + + continue; + } + if (operation == njs_vmcode_catch) { catch = (njs_vmcode_catch_t *) p; @@ -355,6 +394,20 @@ njs_disassemble(u_char *start, u_char *end) continue; } + if (operation == njs_vmcode_finally) { + finally = (njs_vmcode_finally_t *) p; + + printf("%05zd TRY FINALLY %04zX %04zX +%zd +%zd\n", + p - start, (size_t) finally->retval, + (size_t) finally->exit_value, + (size_t) finally->continue_offset, + (size_t) finally->break_offset); + + p += sizeof(njs_vmcode_finally_t); + + continue; + } + code_name = code_names; n = nxt_nitems(code_names); diff --git a/njs/njs_generator.c b/njs/njs_generator.c index f2678f24..6ca1e116 100644 --- a/njs/njs_generator.c +++ b/njs/njs_generator.c @@ -20,13 +20,24 @@ struct njs_generator_patch_s { */ njs_ret_t jump_offset; njs_generator_patch_t *next; + /* + * index_offset is used for patching vmcode_try_return instruction + * inside try blocks. + */ + njs_index_t index_offset; }; typedef enum { - NJS_GENERATOR_BLOCK = 0, - NJS_GENERATOR_LOOP, - NJS_GENERATOR_SWITCH, + NJS_GENERATOR_BLOCK = 1, + NJS_GENERATOR_LOOP = 2, + NJS_GENERATOR_SWITCH = 4, + NJS_GENERATOR_TRY = 8, + +#define NJS_GENERATOR_ALL (NJS_GENERATOR_BLOCK \ + | NJS_GENERATOR_LOOP \ + | NJS_GENERATOR_SWITCH \ + | NJS_GENERATOR_TRY) } njs_generator_block_type_t; @@ -70,8 +81,16 @@ 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 nxt_noinline void njs_generate_patch_loop_continuation(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); +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 nxt_noinline void njs_generate_patch_block(njs_vm_t *vm, + njs_generator_t *generator, njs_generator_patch_t *list); +static nxt_noinline void njs_generate_patch_try_exit_block(njs_vm_t *vm, + njs_generator_t *generator, njs_generator_patch_t *list, njs_index_t dest); +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 nxt_noinline void njs_generate_patch_block_exit(njs_vm_t *vm, njs_generator_t *generator); static nxt_int_t njs_generate_continue_statement(njs_vm_t *vm, @@ -866,6 +885,7 @@ njs_generate_switch_statement(njs_vm_t *vm, njs_generator_t *generator, return NXT_ERROR; } + patch->index_offset = 0; patch->jump_offset = njs_code_offset(generator, equal) + offsetof(njs_vmcode_equal_jump_t, offset); @@ -966,7 +986,7 @@ njs_generate_while_statement(njs_vm_t *vm, njs_generator_t *generator, /* The loop condition. */ - njs_generate_patch_loop_continuation(vm, generator); + njs_generate_patch_block(vm, generator, generator->block->continuation); njs_code_set_jump_offset(generator, njs_vmcode_jump_t, jump_offset); @@ -1016,7 +1036,7 @@ njs_generate_do_while_statement(njs_vm_t *vm, njs_generator_t *generator, /* The loop condition. */ - njs_generate_patch_loop_continuation(vm, generator); + njs_generate_patch_block(vm, generator, generator->block->continuation); condition = node->right; @@ -1100,7 +1120,7 @@ njs_generate_for_statement(njs_vm_t *vm, njs_generator_t *generator, /* The loop update. */ - njs_generate_patch_loop_continuation(vm, generator); + njs_generate_patch_block(vm, generator, generator->block->continuation); update = node->right; @@ -1199,7 +1219,7 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator, /* The loop iterator. */ - njs_generate_patch_loop_continuation(vm, generator); + njs_generate_patch_block(vm, generator, generator->block->continuation); njs_code_set_jump_offset(generator, njs_vmcode_prop_foreach_t, prop_offset); @@ -1258,15 +1278,51 @@ 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) +{ + while (block != NULL) { + if (block->type & mask) { + return block; + } + + block = block->next; + } + + return NULL; +} + + +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_patch_t *patch; + + patch = nxt_mem_cache_alloc(vm->mem_cache_pool, + sizeof(njs_generator_patch_t)); + if (nxt_slow_path(patch == NULL)) { + njs_memory_error(vm); + return NXT_ERROR; + } + + patch->next = block->continuation; + block->continuation = patch; + + patch->index_offset = 0; + patch->jump_offset = offset; + + return NXT_OK; +} + + static nxt_noinline void -njs_generate_patch_loop_continuation(njs_vm_t *vm, njs_generator_t *generator) +njs_generate_patch_block(njs_vm_t *vm, njs_generator_t *generator, + njs_generator_patch_t *list) { - njs_generator_block_t *block; njs_generator_patch_t *patch, *next; - block = generator->block; - - for (patch = block->continuation; patch != NULL; patch = next) { + for (patch = list; patch != NULL; patch = next) { njs_code_update_offset(generator, patch); next = patch->next; @@ -1276,20 +1332,56 @@ njs_generate_patch_loop_continuation(njs_vm_t *vm, njs_generator_t *generator) static nxt_noinline void -njs_generate_patch_block_exit(njs_vm_t *vm, njs_generator_t *generator) +njs_generate_patch_try_exit_block(njs_vm_t *vm, njs_generator_t *generator, + njs_generator_patch_t *list, njs_index_t dest) { - njs_generator_block_t *block; njs_generator_patch_t *patch, *next; - block = generator->block; - generator->block = block->next; - - for (patch = block->exit; patch != NULL; patch = next) { + for (patch = list; patch != NULL; patch = next) { njs_code_update_offset(generator, patch); next = patch->next; + if (patch->index_offset != 0) { + *(njs_code_ptr(generator, njs_index_t, patch->index_offset)) = dest; + } + nxt_mem_cache_free(vm->mem_cache_pool, patch); } +} + + +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) +{ + njs_generator_patch_t *patch; + + patch = nxt_mem_cache_alloc(vm->mem_cache_pool, + sizeof(njs_generator_patch_t)); + if (nxt_slow_path(patch == NULL)) { + njs_memory_error(vm); + return NXT_ERROR; + } + + patch->next = block->exit; + block->exit = patch; + + patch->index_offset = 0; + patch->jump_offset = offset; + + return NXT_OK; +} + + +static nxt_noinline void +njs_generate_patch_block_exit(njs_vm_t *vm, njs_generator_t *generator) +{ + njs_generator_block_t *block; + + block = generator->block; + generator->block = block->next; + + njs_generate_patch_block(vm, generator, block->exit); nxt_mem_cache_free(vm->mem_cache_pool, block); } @@ -1299,22 +1391,22 @@ static nxt_int_t njs_generate_continue_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_vmcode_jump_t *jump; + njs_vmcode_jump_t *jump; njs_generator_patch_t *patch; njs_generator_block_t *block; - for (block = generator->block; block != NULL; block = block->next) { - if (block->type == NJS_GENERATOR_LOOP) { - goto found; - } - } - - njs_generate_syntax_error(vm, node->token_line, - "Illegal continue statement"); + block = njs_generate_find_block(generator->block, + NJS_GENERATOR_LOOP | NJS_GENERATOR_TRY); - return NXT_ERROR; + if (nxt_slow_path(block == NULL)) { + goto syntax_error; + } -found: + if (block->type == NJS_GENERATOR_TRY + && njs_generate_find_block(block->next, NJS_GENERATOR_LOOP) == NULL) + { + goto syntax_error; + } /* TODO: LABEL */ @@ -1331,11 +1423,19 @@ found: jump->code.retval = NJS_VMCODE_NO_RETVAL; jump->offset = offsetof(njs_vmcode_jump_t, offset); + patch->index_offset = 0; patch->jump_offset = njs_code_offset(generator, jump) + offsetof(njs_vmcode_jump_t, offset); } return NXT_OK; + +syntax_error: + + njs_generate_syntax_error(vm, node->token_line, + "Illegal continue statement"); + + return NXT_ERROR; } @@ -1343,23 +1443,21 @@ static nxt_int_t njs_generate_break_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_vmcode_jump_t *jump; + njs_vmcode_jump_t *jump; njs_generator_patch_t *patch; njs_generator_block_t *block; - for (block = generator->block; block != NULL; block = block->next) { - if (block->type == NJS_GENERATOR_LOOP - || block->type == NJS_GENERATOR_SWITCH) - { - goto found; - } - } - - njs_generate_syntax_error(vm, node->token_line, "Illegal break statement"); + block = njs_generate_find_block(generator->block, NJS_GENERATOR_ALL); - return NXT_ERROR; + if (nxt_slow_path(block == NULL)) { + goto syntax_error; + } -found: + if (block->type == NJS_GENERATOR_TRY + && njs_generate_find_block(block->next, NJS_GENERATOR_ALL) == NULL) + { + goto syntax_error; + } /* TODO: LABEL: loop and switch may have label, block must have label. */ @@ -1376,11 +1474,18 @@ found: jump->code.retval = NJS_VMCODE_NO_RETVAL; jump->offset = offsetof(njs_vmcode_jump_t, offset); + patch->index_offset = 0; patch->jump_offset = njs_code_offset(generator, jump) + offsetof(njs_vmcode_jump_t, offset); } return NXT_OK; + +syntax_error: + + njs_generate_syntax_error(vm, node->token_line, "Illegal break statement"); + + return NXT_ERROR; } @@ -2356,30 +2461,65 @@ static nxt_int_t njs_generate_return_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - nxt_int_t ret; - njs_index_t index; - njs_vmcode_return_t *code; + nxt_int_t ret; + njs_index_t index; + njs_vmcode_return_t *code; + njs_generator_patch_t *patch; + njs_generator_block_t *block; + njs_vmcode_try_return_t *try_return; ret = njs_generator(vm, generator, node->right); - if (nxt_fast_path(ret == NXT_OK)) { + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + + if (node->right != NULL) { + index = node->right->index; + + } else { + index = njs_value_index(vm, &njs_value_void, + generator->runtime); + } + + block = njs_generate_find_block(generator->block, NJS_GENERATOR_TRY); + + if (nxt_fast_path(block == NULL)) { njs_generate_code(generator, njs_vmcode_return_t, code); code->code.operation = njs_vmcode_return; code->code.operands = NJS_VMCODE_1OPERAND; code->code.retval = NJS_VMCODE_NO_RETVAL; - if (node->right != NULL) { - index = node->right->index; - - } else { - index = njs_value_index(vm, &njs_value_void, generator->runtime); - } - code->retval = index; node->index = index; + + return NXT_OK; } - return ret; + patch = nxt_mem_cache_alloc(vm->mem_cache_pool, + sizeof(njs_generator_patch_t)); + if (nxt_slow_path(patch == NULL)) { + return NXT_ERROR; + } + + patch->next = block->exit; + block->exit = patch; + + 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 = index; + patch->index_offset = njs_code_offset(generator, try_return) + + offsetof(njs_vmcode_try_return_t, save); + + 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); + + return NXT_OK; } @@ -2534,13 +2674,16 @@ static nxt_int_t njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_ret_t try_offset, catch_offset; - nxt_int_t ret; - njs_index_t index, catch_index; - njs_vmcode_catch_t *catch; - njs_vmcode_finally_t *finally; - njs_vmcode_try_end_t *try_end, *catch_end; - njs_vmcode_try_start_t *try_start; + njs_ret_t try_offset, try_end_offset, catch_offset, + catch_end_offset; + nxt_int_t ret; + njs_index_t exception_index, exit_index, catch_index; + njs_vmcode_catch_t *catch; + njs_vmcode_finally_t *finally; + njs_vmcode_try_end_t *try_end, *catch_end; + 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; njs_generate_code(generator, njs_vmcode_try_start_t, try_start); try_offset = njs_code_offset(generator, try_start); @@ -2548,25 +2691,82 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, try_start->code.operands = NJS_VMCODE_2OPERANDS; try_start->code.retval = NJS_VMCODE_NO_RETVAL; - index = njs_generate_temp_index_get(vm, generator, node); - if (nxt_slow_path(index == NJS_INDEX_ERROR)) { + exception_index = njs_generate_temp_index_get(vm, generator, node); + if (nxt_slow_path(exception_index == NJS_INDEX_ERROR)) { return NXT_ERROR; } - try_start->value = index; + try_start->exception_value = exception_index; + + /* + * exit_value is used in njs_vmcode_finally to make a decision + * which way to go after "break", "continue" and "return" instruction + * inside "try" or "catch" blocks. + */ + + exit_index = njs_generate_temp_index_get(vm, generator, node); + if (nxt_slow_path(exit_index == NJS_INDEX_ERROR)) { + return NXT_ERROR; + } + + try_start->exit_value = exit_index; + + ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_TRY, &no_label); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } ret = njs_generator(vm, generator, node->left); if (nxt_slow_path(ret != NXT_OK)) { return ret; } + try_block = generator->block; + 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; try_end->code.operands = NJS_VMCODE_NO_OPERAND; try_end->code.retval = NJS_VMCODE_NO_RETVAL; + if (try_block->exit != NULL) { + njs_generate_patch_try_exit_block(vm, generator, try_block->exit, + exit_index); + + njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_break); + try_break->code.operation = njs_vmcode_try_break; + try_break->code.operands = NJS_VMCODE_2OPERANDS; + try_break->code.retval = NJS_VMCODE_NO_RETVAL; + + try_break->exit_value = exit_index; + + try_break->offset = -sizeof(njs_vmcode_try_end_t); + + } else { + try_break = NULL; + } + + if (try_block->continuation != NULL) { + njs_generate_patch_block(vm, generator, try_block->continuation); + + njs_generate_code(generator, njs_vmcode_try_trampoline_t, try_continue); + try_continue->code.operation = njs_vmcode_try_continue; + try_continue->code.operands = NJS_VMCODE_2OPERANDS; + try_continue->code.retval = NJS_VMCODE_NO_RETVAL; + + try_continue->exit_value = exit_index; + + try_continue->offset = -sizeof(njs_vmcode_try_end_t); + + if (try_break != NULL) { + try_continue->offset -= sizeof(njs_vmcode_try_trampoline_t); + } + } + + generator->block = try_block->next; + njs_code_set_jump_offset(generator, njs_vmcode_try_start_t, try_offset); - try_offset = njs_code_offset(generator, try_end); + try_offset = try_end_offset; node = node->right; @@ -2592,6 +2792,43 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, njs_code_set_jump_offset(generator, njs_vmcode_try_end_t, try_offset); + if (try_block->continuation != NULL || try_block->exit != NULL) { + njs_generate_code(generator, njs_vmcode_finally_t, finally); + finally->code.operation = njs_vmcode_finally; + finally->code.operands = NJS_VMCODE_2OPERANDS; + finally->code.retval = NJS_VMCODE_NO_RETVAL; + finally->retval = exception_index; + finally->exit_value = exit_index; + finally->continue_offset = offsetof(njs_vmcode_finally_t, + continue_offset); + finally->break_offset = offsetof(njs_vmcode_finally_t, + break_offset); + + if (try_block->continuation != NULL) { + /* + * block != NULL is checked + * by njs_generate_continue_statement() + */ + block = njs_generate_find_block(generator->block, + NJS_GENERATOR_LOOP); + + njs_generate_make_continuation_patch(vm, generator, block, + njs_code_offset(generator, finally) + + offsetof(njs_vmcode_finally_t, continue_offset)); + } + + if (try_block->exit != NULL) { + block = njs_generate_find_block(generator->block, + NJS_GENERATOR_ALL); + + if (block != NULL) { + njs_generate_make_exit_patch(vm, generator, block, + njs_code_offset(generator, finally) + + offsetof(njs_vmcode_finally_t, break_offset)); + } + } + } + /* TODO: release exception variable index. */ } else { @@ -2610,19 +2847,67 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, catch->code.retval = NJS_VMCODE_NO_RETVAL; catch->exception = catch_index; + ret = njs_generate_start_block(vm, generator, NJS_GENERATOR_TRY, + &no_label); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + ret = njs_generator(vm, generator, node->left->right); if (nxt_slow_path(ret != NXT_OK)) { return ret; } + catch_block = generator->block; + njs_generate_code(generator, njs_vmcode_try_end_t, catch_end); + catch_end_offset = njs_code_offset(generator, catch_end); catch_end->code.operation = njs_vmcode_try_end; catch_end->code.operands = NJS_VMCODE_NO_OPERAND; catch_end->code.retval = NJS_VMCODE_NO_RETVAL; + if (catch_block->exit != NULL) { + njs_generate_patch_try_exit_block(vm, generator, + catch_block->exit, + exit_index); + + njs_generate_code(generator, njs_vmcode_try_trampoline_t, + try_break); + try_break->code.operation = njs_vmcode_try_break; + try_break->code.operands = NJS_VMCODE_2OPERANDS; + try_break->code.retval = NJS_VMCODE_NO_RETVAL; + + try_break->exit_value = exit_index; + + try_break->offset = -sizeof(njs_vmcode_try_end_t); + + } else { + try_break = NULL; + } + + if (catch_block->continuation != NULL) { + njs_generate_patch_block(vm, generator, + catch_block->continuation); + + njs_generate_code(generator, njs_vmcode_try_trampoline_t, + try_continue); + try_continue->code.operation = njs_vmcode_try_continue; + try_continue->code.operands = NJS_VMCODE_2OPERANDS; + try_continue->code.retval = NJS_VMCODE_NO_RETVAL; + + try_continue->exit_value = exit_index; + + try_continue->offset = -sizeof(njs_vmcode_try_end_t); + + if (try_break != NULL) { + try_continue->offset -= sizeof(njs_vmcode_try_trampoline_t); + } + } + + generator->block = catch_block->next; + njs_code_set_jump_offset(generator, njs_vmcode_catch_t, catch_offset); - catch_offset = njs_code_offset(generator, catch_end); /* TODO: release exception variable index. */ @@ -2631,10 +2916,10 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, catch->code.operands = NJS_VMCODE_2OPERANDS; catch->code.retval = NJS_VMCODE_NO_RETVAL; catch->offset = sizeof(njs_vmcode_catch_t); - catch->exception = index; + catch->exception = exception_index; njs_code_set_jump_offset(generator, njs_vmcode_try_end_t, - catch_offset); + catch_end_offset); } else { /* A try/finally case. */ @@ -2644,7 +2929,9 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, catch->code.operands = NJS_VMCODE_2OPERANDS; catch->code.retval = NJS_VMCODE_NO_RETVAL; catch->offset = sizeof(njs_vmcode_catch_t); - catch->exception = index; + catch->exception = exception_index; + + catch_block = NULL; } njs_code_set_jump_offset(generator, njs_vmcode_try_end_t, try_offset); @@ -2656,12 +2943,43 @@ njs_generate_try_statement(njs_vm_t *vm, njs_generator_t *generator, njs_generate_code(generator, njs_vmcode_finally_t, finally); finally->code.operation = njs_vmcode_finally; - finally->code.operands = NJS_VMCODE_1OPERAND; + finally->code.operands = NJS_VMCODE_2OPERANDS; finally->code.retval = NJS_VMCODE_NO_RETVAL; - finally->retval = index; + finally->retval = exception_index; + finally->exit_value = exit_index; + finally->continue_offset = offsetof(njs_vmcode_finally_t, + continue_offset); + finally->break_offset = offsetof(njs_vmcode_finally_t, break_offset); + + if (try_block->continuation != NULL + || (catch_block && catch_block->continuation != NULL)) + { + /* + * block != NULL is checked + * by njs_generate_continue_statement() + */ + block = njs_generate_find_block(generator->block, + NJS_GENERATOR_LOOP); + + njs_generate_make_continuation_patch(vm, generator, block, + njs_code_offset(generator, finally) + + offsetof(njs_vmcode_finally_t, continue_offset)); + } + + if (try_block->exit != NULL + || (catch_block != NULL && catch_block->exit != NULL)) + { + block = njs_generate_find_block(generator->block, + NJS_GENERATOR_ALL); + if (block != NULL) { + njs_generate_make_exit_patch(vm, generator, block, + njs_code_offset(generator, finally) + + offsetof(njs_vmcode_finally_t, break_offset)); + } + } } - return njs_generate_index_release(vm, generator, index); + return njs_generate_index_release(vm, generator, exception_index); } diff --git a/njs/njs_vm.c b/njs/njs_vm.c index 05c13b11..4d8e46bd 100644 --- a/njs/njs_vm.c +++ b/njs/njs_vm.c @@ -132,7 +132,8 @@ start: * njs_vmcode_function_call(), * njs_vmcode_return(), * njs_vmcode_try_start(), - * njs_vmcode_try_next(), + * njs_vmcode_try_continue(), + * njs_vmcode_try_break(), * njs_vmcode_try_end(), * njs_vmcode_catch(). * njs_vmcode_throw(). @@ -2580,9 +2581,12 @@ njs_vmcode_stop(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval) */ njs_ret_t -njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *value, njs_value_t *offset) +njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *exception_value, + njs_value_t *offset) { - njs_exception_t *e; + njs_value_t *exit_value; + njs_exception_t *e; + njs_vmcode_try_start_t *try_start; if (vm->top_frame->exception.catch != NULL) { e = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_exception_t)); @@ -2597,12 +2601,67 @@ njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *value, njs_value_t *offset) vm->top_frame->exception.catch = vm->current + (njs_ret_t) offset; - njs_set_invalid(value); + njs_set_invalid(exception_value); + + try_start = (njs_vmcode_try_start_t *) vm->current; + exit_value = njs_vmcode_operand(vm, try_start->exit_value); + + njs_set_invalid(exit_value); + exit_value->data.u.number = 0; return sizeof(njs_vmcode_try_start_t); } +/* + * njs_vmcode_try_break() sets exit_value to INVALID 1, and jumps to + * the nearest try_end block. The exit_value is checked by njs_vmcode_finally(). + */ + +nxt_noinline njs_ret_t +njs_vmcode_try_break(njs_vm_t *vm, njs_value_t *exit_value, + njs_value_t *offset) +{ + exit_value->data.u.number = 1; + + return (njs_ret_t) offset; +} + + +/* + * njs_vmcode_try_break() sets exit_value to INVALID -1, and jumps to + * the nearest try_end block. The exit_value is checked by njs_vmcode_finally(). + */ + +nxt_noinline njs_ret_t +njs_vmcode_try_continue(njs_vm_t *vm, njs_value_t *exit_value, + njs_value_t *offset) +{ + exit_value->data.u.number = -1; + + return (njs_ret_t) offset; +} + +/* + * njs_vmcode_try_return() saves a return value to use it later by + * njs_vmcode_finally(), and jumps to the nearest try_end block. + */ + +nxt_noinline njs_ret_t +njs_vmcode_try_return(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld) +{ + njs_vmcode_try_return_t *try_return; + + vm->retval = *value; + + njs_retain(value); + + try_return = (njs_vmcode_try_return_t *) vm->current; + + return try_return->offset; +} + + /* * njs_vmcode_try_end() is set on the end of a "try" block to remove the block. * It is also set on the end of a "catch" block followed by a "finally" block. @@ -2664,24 +2723,48 @@ njs_vmcode_catch(njs_vm_t *vm, njs_value_t *exception, njs_value_t *offset) /* - * njs_vmcode_finally() is set on the end of a "finally" block to throw - * uncaught exception. + * njs_vmcode_finally() is set on the end of a "finally" or a "catch" block. + * 1) to throw uncaught exception. + * 2) to make a jump to an enslosing loop exit if "continue" or "break" + * statement was used inside try block. + * 3) to finalize "return" instruction from "try" block. */ njs_ret_t njs_vmcode_finally(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval) { - njs_value_t *value; + njs_value_t *exception_value, *exit_value; + njs_vmcode_finally_t *finally; - value = njs_vmcode_operand(vm, retval); + exception_value = njs_vmcode_operand(vm, retval); + + if (njs_is_valid(exception_value)) { + vm->retval = *exception_value; - if (!njs_is_valid(value)) { - return sizeof(njs_vmcode_finally_t); + return NXT_ERROR; } - vm->retval = *value; + finally = (njs_vmcode_finally_t *) vm->current; + exit_value = njs_vmcode_operand(vm, finally->exit_value); - return NXT_ERROR; + /* + * exit_value is set by: + * vmcode_try_start to INVALID 0 + * vmcode_try_break to INVALID 1 + * vmcode_try_continue to INVALID -1 + * vmcode_try_return to a valid return value + */ + + if (njs_is_valid(exit_value)) { + return njs_vmcode_return(vm, NULL, exit_value); + + } else if (exit_value->data.u.number != 0) { + return (njs_ret_t) (exit_value->data.u.number > 0) + ? finally->break_offset + : finally->continue_offset; + } + + return sizeof(njs_vmcode_finally_t); } diff --git a/njs/njs_vm.h b/njs/njs_vm.h index 631b451f..cb353db5 100644 --- a/njs/njs_vm.h +++ b/njs/njs_vm.h @@ -774,10 +774,18 @@ typedef struct { typedef struct { njs_vmcode_t code; njs_ret_t offset; - njs_index_t value; + njs_index_t exception_value; + njs_index_t exit_value; } njs_vmcode_try_start_t; +typedef struct { + njs_vmcode_t code; + njs_ret_t offset; + njs_index_t exit_value; +} njs_vmcode_try_trampoline_t; + + typedef struct { njs_vmcode_t code; njs_ret_t offset; @@ -799,7 +807,18 @@ typedef struct { typedef struct { njs_vmcode_t code; + njs_index_t save; njs_index_t retval; + njs_ret_t offset; +} njs_vmcode_try_return_t; + + +typedef struct { + njs_vmcode_t code; + njs_index_t retval; + njs_index_t exit_value; + njs_ret_t continue_offset; + njs_ret_t break_offset; } njs_vmcode_finally_t; @@ -1223,6 +1242,12 @@ njs_ret_t njs_vmcode_stop(njs_vm_t *vm, njs_value_t *invld, njs_ret_t njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *value, njs_value_t *offset); +njs_ret_t njs_vmcode_try_break(njs_vm_t *vm, njs_value_t *value, + njs_value_t *offset); +njs_ret_t njs_vmcode_try_continue(njs_vm_t *vm, njs_value_t *value, + njs_value_t *offset); +njs_ret_t njs_vmcode_try_return(njs_vm_t *vm, njs_value_t *value, + njs_value_t *offset); njs_ret_t njs_vmcode_try_end(njs_vm_t *vm, njs_value_t *invld, njs_value_t *offset); njs_ret_t njs_vmcode_throw(njs_vm_t *vm, njs_value_t *invld, diff --git a/njs/test/njs_expect_test.exp b/njs/test/njs_expect_test.exp index 5ca68029..18d83e42 100644 --- a/njs/test/njs_expect_test.exp +++ b/njs/test/njs_expect_test.exp @@ -535,6 +535,10 @@ njs_run "-v" "\\d+\.\\d+\.\\d+" njs_test { {"1+1\r\n" "00000 ADD*\r\n00040 STOP*\r\n\r\n2"} + {"for (var n in [1]) {try {break} finally{}}\r\n" + "00000 ARRAY*\r\n*TRY BREAK*STOP*\r\n\r\nundefined"} + {"(function() {try {return} finally{}})()\r\n" + "00000 TRY START*\r\n*TRY RETURN*STOP*\r\n\r\nundefined"} } "-d" # sandboxing diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index e2445b00..c5340f6d 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -6619,6 +6619,286 @@ static njs_unit_test_t njs_test[] = { nxt_string("throw\nnull"), nxt_string("SyntaxError: Illegal newline after throw in 2") }, + { nxt_string("for (var x in [1,2]) { try{ continue; } catch(e) {} } throw 1"), + nxt_string("1") }, + + { nxt_string("for (var x in [1,2]) { try{ break; } catch(e) {} } throw 1"), + nxt_string("1") }, + + { nxt_string("try\n {\n continue; } catch(e) {}"), + nxt_string("SyntaxError: Illegal continue statement in 3") }, + + { nxt_string("var a = 1; " + "switch (a) {" + "default:" + " try\n {\n continue; } " + " catch(e) {}" + "}"), + nxt_string("SyntaxError: Illegal continue statement in 3") }, + + { nxt_string("try\n {\n break; } catch(e) {}"), + nxt_string("SyntaxError: Illegal break statement in 3") }, + + { nxt_string("try\n { }\n catch(e) {continue;}"), + nxt_string("SyntaxError: Illegal continue statement in 3") }, + + { nxt_string("try { } catch(e) {break;}"), + nxt_string("SyntaxError: Illegal break statement in 1") }, + + { nxt_string("try { continue; } finally {}"), + nxt_string("SyntaxError: Illegal continue statement in 1") }, + + { nxt_string("try { break; } finally {}"), + nxt_string("SyntaxError: Illegal break statement in 1") }, + + { nxt_string("try\n {\n try\n {\n continue; } finally {} } finally {}"), + nxt_string("SyntaxError: Illegal continue statement in 5") }, + + /* break from try in try/catch. */ + + { nxt_string("function f(n) {" + " var pre = 0; var post = 0;" + " for (var x in [1, 2, 3]) {" + " pre++;" + " try { " + " if (n == 'b') {break;}" + " }" + " catch (e) {};" + " post++" + " }" + " return [pre, post];" + "}; njs.dump([f(),f('b')])"), + nxt_string("[[3,3]," + "[1,0]]") }, + + { nxt_string("function f(v, n) {" + " var pre = 0; var post = 0; var case2 = 0;" + " switch (v) {" + " case 1: " + " pre++;" + " try { " + " if (n == 'b') {break;}" + " }" + " catch (e) {};" + " post++;" + " break;" + " default:" + " case2++;" + " }" + " return [pre, post, case2];" + "}; njs.dump([f(),f(1)])"), + nxt_string("[[0,0,1]," + "[1,1,0]]") }, + + /* continue from try in try/catch. */ + + { nxt_string("function f(n) {" + " var pre = 0; var post = 0;" + " for (var x in [1, 2, 3]) {" + " pre++;" + " try { " + " if (n == 'c') {continue;}" + " }" + " catch (e) {};" + " post++" + " }" + " return [pre, post];" + "}; njs.dump([f(),f('c')])"), + nxt_string("[[3,3]," + "[3,0]]") }, + + /* Multiple break/continue from try in try/catch. */ + + { nxt_string("function f(n) {" + " var pre = 0; var mid = 0; var post = 0;" + " for (var x in [1, 2, 3]) {" + " pre++;" + " try { " + " if (n == 'c') {continue;}" + " if (n == 'b') {break;}" + " mid++;" + " if (n == 'c2') {continue;}" + " if (n == 'b2') {break;}" + " }" + " catch (e) {};" + " post++" + " }" + " return [pre, mid, post];" + "}; njs.dump([f(),f('c'),f('b'),f('c2'),f('b2')])"), + nxt_string("[[3,3,3]," + "[3,0,0]," + "[1,0,0]," + "[3,3,0]," + "[1,1,0]]") }, + + /* Multiple break/continue from catch in try/catch. */ + + { nxt_string("function f(t, n) {" + " var pre = 0; var mid = 0; var post = 0;" + " for (var x in [1, 2, 3]) {" + " pre++;" + " try { " + " if (t) {throw 'a'}" + " }" + " catch (e) {" + " if (n == 'c') {continue;}" + " if (n == 'b') {break;}" + " mid++;" + " if (n == 'c2') {continue;}" + " if (n == 'b2') {break;}" + " };" + " post++" + " }" + " return [pre, mid, post];" + "}; njs.dump([f(), f(1), f(1, 'c'), f(1, 'b'), f(1, 'c2'), f(1, 'b2')])"), + nxt_string("[[3,0,3]," + "[3,3,3]," + "[3,0,0]," + "[1,0,0]," + "[3,3,0]," + "[1,1,0]]") }, + + /* break from try in try/finally. */ + + { nxt_string("function f(n) {" + " var pre = 0; var mid = 0; var fin = 0; var post = 0;" + " for (var x in [1, 2, 3]) {" + " pre++;" + " try { " + " if (n == 'c') {continue;}" + " if (n == 'b') {break;}" + " mid++;" + " if (n == 'c2') {continue;}" + " if (n == 'b2') {break;}" + " }" + " finally {fin++};" + " post++" + " }" + " return [pre, mid, fin, post];" + "}; njs.dump([f(),f('c'),f('b'),f('c2'),f('b2')])"), + nxt_string("[[3,3,3,3]," + "[3,0,3,0]," + "[1,0,1,0]," + "[3,3,3,0]," + "[1,1,1,0]]") }, + + /* Multiple break/continue from try in try/catch/finally. */ + + { nxt_string("function f(n) {" + " var pre = 0; var mid = 0; var fin = 0; var post = 0;" + " for (var x in [1, 2, 3]) {" + " pre++;" + " try { " + " if (n == 'c') {continue;}" + " if (n == 'b') {break;}" + " mid++;" + " if (n == 'c2') {continue;}" + " if (n == 'b2') {break;}" + " }" + " catch (e) {}" + " finally {fin++};" + " post++" + " }" + " return [pre, mid, fin, post];" + "}; njs.dump([f(),f('c'),f('b'),f('c2'),f('b2')])"), + nxt_string("[[3,3,3,3]," + "[3,0,3,0]," + "[1,0,1,0]," + "[3,3,3,0]," + "[1,1,1,0]]") }, + + /* Multiple break/continue from catch in try/catch/finally. */ + + { nxt_string("function f(t, n) {" + " var pre = 0; var mid = 0; var fin = 0; var post = 0;" + " for (var x in [1, 2, 3]) {" + " pre++;" + " try {if (t) {throw 'a'}}" + " catch (e) { " + " if (n == 'c') {continue;}" + " if (n == 'b') {break;}" + " mid++;" + " if (n == 'c2') {continue;}" + " if (n == 'b2') {break;}" + " }" + " finally {fin++};" + " post++" + " }" + " return [pre, mid, fin, post];" + "}; njs.dump([f(), f(1), f(1, 'c'), f(1, 'b'), f(1, 'c2'), f(1, 'b2')])"), + nxt_string("[[3,0,3,3]," + "[3,3,3,3]," + "[3,0,3,0]," + "[1,0,1,0]," + "[3,3,3,0]," + "[1,1,1,0]]") }, + + /* Multiple return from try. */ + + { nxt_string("var r = 0; " + "function f(i, n) {" + " try { " + " var a = 'x'; " + " if (i != 0) {" + " return a.repeat(n);" + " } else {" + " return;" + " }" + " }" + " catch (e) { } " + " finally { r++; }};" + "[f(1,1), f(1,2), f(0), r]"), + nxt_string("x,xx,,3") }, + + { nxt_string("var r = 0; " + "function f(i) {" + " try { " + " return i;" + " }" + " catch (e) { } " + " finally { r++; }};" + "[f(true), f(false), r]"), + nxt_string("true,false,2") }, + + /* Multiple return from catch. */ + + { nxt_string("var r = 0; " + "function f(i, n) {" + " try { " + " throw 1;" + " }" + " catch (e) { " + " var a = 'x'; " + " if (i != 0) {" + " return a.repeat(n);" + " } else {" + " return;" + " }" + " } " + " finally { r++; }};" + "[f(1,1), f(1,2), f(0), r]"), + nxt_string("x,xx,,3") }, + + /* return overrun by finally. */ + + { nxt_string("function f() {" + " try { " + " return 'a';" + " }" + " catch (e) { " + " } " + " finally { " + " return 'b'; " + " }}; " + "f()"), + nxt_string("b") }, + + { nxt_string("(function (f, val) { " + " try { return f(val); } " + " finally { return val; }" + "})(function () {throw 'a'}, 'v')"), + nxt_string("v") }, + { nxt_string("var o = { valueOf: function() { return '3' } }; --o"), nxt_string("2") },