From 8baf18b75fb78da76e62620bcf1341d41f80132d Mon Sep 17 00:00:00 2001 From: Igor Sysoev Date: Tue, 15 Dec 2015 16:46:00 +0300 Subject: [PATCH] Support of "continue" and "break" statements without labels in loops. --- njs/njs_generator.c | 189 +++++++++++++++++++++++++++++++++++++++ njs/njs_parser.c | 88 ++++++++++++++++-- njs/njs_parser.h | 26 ++++++ njs/test/njs_unit_test.c | 124 +++++++++++++++++++++++++ 4 files changed, 422 insertions(+), 5 deletions(-) diff --git a/njs/njs_generator.c b/njs/njs_generator.c index 96343f63..72a2aad3 100644 --- a/njs/njs_generator.c +++ b/njs/njs_generator.c @@ -37,6 +37,16 @@ static nxt_int_t njs_generate_for_statement(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node); static nxt_int_t njs_generate_for_in_statement(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node); +static nxt_noinline nxt_int_t njs_generate_start_block(njs_vm_t *vm, + njs_parser_t *parser, njs_parser_block_type_t type, const nxt_str_t *label); +static nxt_noinline void njs_generate_patch_loop_continuation(njs_vm_t *vm, + njs_parser_t *parser); +static nxt_noinline void njs_generate_patch_block_exit(njs_vm_t *vm, + njs_parser_t *parser); +static nxt_int_t njs_generate_continue_statement(njs_vm_t *vm, + njs_parser_t *parser, njs_parser_node_t *node); +static nxt_int_t njs_generate_break_statement(njs_vm_t *vm, + njs_parser_t *parser, njs_parser_node_t *node); static nxt_int_t njs_generate_statement(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node); static nxt_int_t njs_generate_children(njs_vm_t *vm, njs_parser_t *parser, @@ -98,6 +108,9 @@ static nxt_noinline nxt_int_t njs_generator_index_release(njs_vm_t *vm, nxt_inline nxt_bool_t njs_generator_is_constant(njs_parser_node_t *node); +static const nxt_str_t no_label = { 0, NULL }; + + static nxt_int_t njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) { @@ -127,6 +140,12 @@ njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) case NJS_TOKEN_FOR_IN: return njs_generate_for_in_statement(vm, parser, node); + case NJS_TOKEN_CONTINUE: + return njs_generate_continue_statement(vm, parser, node); + + case NJS_TOKEN_BREAK: + return njs_generate_break_statement(vm, parser, node); + case NJS_TOKEN_STATEMENT: return njs_generate_statement(vm, parser, node); @@ -498,6 +517,11 @@ njs_generate_while_statement(njs_vm_t *vm, njs_parser_t *parser, /* The loop body. */ + ret = njs_generate_start_block(vm, parser, NJS_PARSER_LOOP, &no_label); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + loop = parser->code_end; ret = njs_generator(vm, parser, node->left); @@ -507,6 +531,8 @@ njs_generate_while_statement(njs_vm_t *vm, njs_parser_t *parser, /* The loop condition. */ + njs_generate_patch_loop_continuation(vm, parser); + jump->offset = parser->code_end - (u_char *) jump; condition = node->right; @@ -523,6 +549,8 @@ njs_generate_while_statement(njs_vm_t *vm, njs_parser_t *parser, cond_jump->offset = loop - (u_char *) cond_jump; cond_jump->cond = condition->index; + njs_generate_patch_block_exit(vm, parser); + return njs_generator_node_index_release(vm, parser, condition); } @@ -538,6 +566,11 @@ njs_generate_do_while_statement(njs_vm_t *vm, njs_parser_t *parser, /* The loop body. */ + ret = njs_generate_start_block(vm, parser, NJS_PARSER_LOOP, &no_label); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + loop = parser->code_end; ret = njs_generator(vm, parser, node->left); @@ -547,6 +580,8 @@ njs_generate_do_while_statement(njs_vm_t *vm, njs_parser_t *parser, /* The loop condition. */ + njs_generate_patch_loop_continuation(vm, parser); + condition = node->right; ret = njs_generator(vm, parser, condition); @@ -561,6 +596,8 @@ njs_generate_do_while_statement(njs_vm_t *vm, njs_parser_t *parser, cond_jump->offset = loop - (u_char *) cond_jump; cond_jump->cond = condition->index; + njs_generate_patch_block_exit(vm, parser); + return njs_generator_node_index_release(vm, parser, condition); } @@ -575,6 +612,11 @@ njs_generate_for_statement(njs_vm_t *vm, njs_parser_t *parser, njs_vmcode_jump_t *jump; njs_vmcode_cond_jump_t *cond_jump; + ret = njs_generate_start_block(vm, parser, NJS_PARSER_LOOP, &no_label); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + jump = NULL; /* The loop initialization. */ @@ -617,6 +659,8 @@ njs_generate_for_statement(njs_vm_t *vm, njs_parser_t *parser, /* The loop update. */ + njs_generate_patch_loop_continuation(vm, parser); + update = node->right; ret = njs_generator(vm, parser, update); @@ -646,6 +690,8 @@ njs_generate_for_statement(njs_vm_t *vm, njs_parser_t *parser, cond_jump->offset = loop - (u_char *) cond_jump; cond_jump->cond = condition->index; + njs_generate_patch_block_exit(vm, parser); + return njs_generator_node_index_release(vm, parser, condition); } @@ -655,6 +701,8 @@ njs_generate_for_statement(njs_vm_t *vm, njs_parser_t *parser, jump->code.retval = NJS_VMCODE_NO_RETVAL; jump->offset = loop - (u_char *) jump; + njs_generate_patch_block_exit(vm, parser); + return NXT_OK; } @@ -670,6 +718,11 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_parser_t *parser, njs_vmcode_prop_next_t *prop_next; njs_vmcode_prop_foreach_t *prop_foreach; + ret = njs_generate_start_block(vm, parser, NJS_PARSER_LOOP, &no_label); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + /* The object. */ foreach = node->left; @@ -699,6 +752,8 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_parser_t *parser, /* The loop iterator. */ + njs_generate_patch_loop_continuation(vm, parser); + prop_foreach->offset = parser->code_end - (u_char *) prop_foreach; ret = njs_generator(vm, parser, node->left->left); @@ -715,6 +770,8 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_parser_t *parser, prop_next->next = index; prop_next->offset = loop - (u_char *) prop_next; + njs_generate_patch_block_exit(vm, parser); + /* * Release object and iterator indexes: an object can be a function result * or a property of another object and an iterator can be given with "let". @@ -728,6 +785,138 @@ njs_generate_for_in_statement(njs_vm_t *vm, njs_parser_t *parser, } +static nxt_noinline nxt_int_t +njs_generate_start_block(njs_vm_t *vm, njs_parser_t *parser, + njs_parser_block_type_t type, const nxt_str_t *label) +{ + njs_parser_block_t *block; + + block = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_parser_block_t)); + + if (nxt_fast_path(block != NULL)) { + block->next = parser->block; + parser->block = block; + + block->type = type; + block->label = *label; + block->continuation = NULL; + block->exit = NULL; + + return NXT_OK; + } + + return NXT_ERROR; +} + + +static nxt_noinline void +njs_generate_patch_loop_continuation(njs_vm_t *vm, njs_parser_t *parser) +{ + njs_parser_block_t *block; + njs_parser_patch_t *patch, *next; + + block = parser->block; + + for (patch = block->continuation; patch != NULL; patch = next) { + *patch->address += parser->code_end - patch->address; + next = patch->next; + + nxt_mem_cache_free(vm->mem_cache_pool, patch); + } +} + + +static nxt_noinline void +njs_generate_patch_block_exit(njs_vm_t *vm, njs_parser_t *parser) +{ + njs_parser_block_t *block; + njs_parser_patch_t *patch, *next; + + block = parser->block; + parser->block = block->next; + + for (patch = block->exit; patch != NULL; patch = next) { + *patch->address += parser->code_end - patch->address; + next = patch->next; + + nxt_mem_cache_free(vm->mem_cache_pool, patch); + } + + nxt_mem_cache_free(vm->mem_cache_pool, block); +} + + +static nxt_int_t +njs_generate_continue_statement(njs_vm_t *vm, njs_parser_t *parser, + njs_parser_node_t *node) +{ + njs_vmcode_jump_t *jump; + njs_parser_patch_t *patch; + + if (parser->block == NULL) { + vm->exception = &njs_exception_syntax_error; + return NXT_ERROR; + } + + /* TODO: LABEL */ + + if (parser->block->type != NJS_PARSER_LOOP) { + vm->exception = &njs_exception_syntax_error; + return NXT_ERROR; + } + + patch = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_parser_patch_t)); + + if (nxt_fast_path(patch != NULL)) { + patch->next = parser->block->continuation; + parser->block->continuation = patch; + + njs_generate_code(parser, 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->address = (u_char *) &jump->offset; + } + + return NXT_OK; +} + + +static nxt_int_t +njs_generate_break_statement(njs_vm_t *vm, njs_parser_t *parser, + njs_parser_node_t *node) +{ + njs_vmcode_jump_t *jump; + njs_parser_patch_t *patch; + + if (parser->block == NULL) { + vm->exception = &njs_exception_syntax_error; + return NXT_ERROR; + } + + /* TODO: LABEL: loop and switch may have label, block must have label. */ + + patch = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_parser_patch_t)); + + if (nxt_fast_path(patch != NULL)) { + patch->next = parser->block->exit; + parser->block->exit = patch; + + njs_generate_code(parser, 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->address = (u_char *) &jump->offset; + } + + return NXT_OK; +} + + static nxt_int_t njs_generate_statement(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node) diff --git a/njs/njs_parser.c b/njs/njs_parser.c index f2d9216a..1eb20d5b 100644 --- a/njs/njs_parser.c +++ b/njs/njs_parser.c @@ -34,8 +34,8 @@ * is treated as a single expiression. */ -static njs_token_t njs_parser_statement_link(njs_vm_t *vm, njs_parser_t *parser, - njs_token_t token); +static njs_token_t njs_parser_statement_chain(njs_vm_t *vm, + njs_parser_t *parser, njs_token_t token); static njs_token_t njs_parser_statement(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token); static njs_token_t njs_parser_block(njs_vm_t *vm, njs_parser_t *parser); @@ -56,6 +56,10 @@ static njs_token_t njs_parser_do_while_statement(njs_vm_t *vm, static njs_token_t njs_parser_for_statement(njs_vm_t *vm, njs_parser_t *parser); static njs_token_t njs_parser_for_in_statement(njs_vm_t *vm, njs_parser_t *parser, 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_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, @@ -80,7 +84,7 @@ njs_parser(njs_vm_t *vm, njs_parser_t *parser) while (token != NJS_TOKEN_END) { - token = njs_parser_statement_link(vm, parser, token); + token = njs_parser_statement_chain(vm, parser, token); if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { return NULL; } @@ -110,7 +114,7 @@ njs_parser(njs_vm_t *vm, njs_parser_t *parser) static njs_token_t -njs_parser_statement_link(njs_vm_t *vm, njs_parser_t *parser, +njs_parser_statement_chain(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token) { njs_parser_node_t *node, *last; @@ -172,6 +176,12 @@ njs_parser_statement(njs_vm_t *vm, njs_parser_t *parser, case NJS_TOKEN_FOR: return njs_parser_for_statement(vm, parser); + case NJS_TOKEN_CONTINUE: + return njs_parser_continue_statement(vm, parser); + + case NJS_TOKEN_BREAK: + return njs_parser_break_statement(vm, parser); + case NJS_TOKEN_TRY: return njs_parser_try_statement(vm, parser); @@ -233,7 +243,7 @@ njs_parser_block(njs_vm_t *vm, njs_parser_t *parser) parser->node = NULL; while (token != NJS_TOKEN_CLOSE_BRACE) { - token = njs_parser_statement_link(vm, parser, token); + token = njs_parser_statement_chain(vm, parser, token); if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) { return token; } @@ -964,6 +974,74 @@ njs_parser_for_in_statement(njs_vm_t *vm, njs_parser_t *parser, } +static njs_token_t +njs_parser_continue_statement(njs_vm_t *vm, njs_parser_t *parser) +{ + njs_token_t token; + njs_parser_node_t *node; + + node = njs_parser_node_alloc(vm); + if (nxt_slow_path(node == NULL)) { + return NJS_TOKEN_ERROR; + } + + node->token = NJS_TOKEN_CONTINUE; + parser->node = node; + parser->code_size += sizeof(njs_vmcode_jump_t); + + token = njs_lexer_token(parser->lexer); + + switch (token) { + + case NJS_TOKEN_SEMICOLON: + case NJS_TOKEN_LINE_END: + return njs_parser_token(parser); + + 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_alloc(vm); + if (nxt_slow_path(node == NULL)) { + return NJS_TOKEN_ERROR; + } + + node->token = NJS_TOKEN_BREAK; + parser->node = node; + parser->code_size += sizeof(njs_vmcode_jump_t); + + token = njs_lexer_token(parser->lexer); + + switch (token) { + + case NJS_TOKEN_SEMICOLON: + case NJS_TOKEN_LINE_END: + return njs_parser_token(parser); + + case NJS_TOKEN_CLOSE_BRACE: + case NJS_TOKEN_END: + return token; + + default: + /* TODO: LABEL */ + return NJS_TOKEN_ILLEGAL; + } +} + + static njs_token_t njs_parser_try_statement(njs_vm_t *vm, njs_parser_t *parser) { diff --git a/njs/njs_parser.h b/njs/njs_parser.h index ebb3270c..01a0c895 100644 --- a/njs/njs_parser.h +++ b/njs/njs_parser.h @@ -231,12 +231,38 @@ struct njs_parser_node_s { nxt_mem_cache_zalloc((vm)->mem_cache_pool, sizeof(njs_parser_node_t)) +typedef struct njs_parser_patch_s njs_parser_patch_t; + +struct njs_parser_patch_s { + u_char *address; + njs_parser_patch_t *next; +}; + + +typedef enum { + NJS_PARSER_BLOCK = 0, + NJS_PARSER_LOOP, + NJS_PARSER_SWITCH, +} njs_parser_block_type_t; + +typedef struct njs_parser_block_s njs_parser_block_t; + +struct njs_parser_block_s { + njs_parser_block_type_t type; /* 2 bits */ + nxt_str_t label; + njs_parser_patch_t *continuation; + njs_parser_patch_t *exit; + njs_parser_block_t *next; +}; + + struct njs_parser_s { njs_lexer_t *lexer; njs_parser_node_t *node; /* Vector of njs_variable_t. */ nxt_array_t *arguments; + njs_parser_block_t *block; nxt_lvlhsh_t variables_hash; diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c index 73b7eb8a..4a1fc500 100644 --- a/njs/test/njs_unit_test.c +++ b/njs/test/njs_unit_test.c @@ -1374,6 +1374,130 @@ static njs_unit_test_t njs_test[] = { nxt_string("a = 3; if (true) if (false); else; a = 2; a"), nxt_string("2") }, + /* continue. */ + + { nxt_string("continue"), + nxt_string("SyntaxError") }, + + { nxt_string("do continue while (false)"), + nxt_string("SyntaxError") }, + + { nxt_string("do continue; while (false)"), + nxt_string("undefined") }, + + { nxt_string("do { continue } while (false)"), + nxt_string("undefined") }, + + { nxt_string("var i = 0; do if (i++ > 9) continue; while (i < 100); i"), + nxt_string("100") }, + + { nxt_string("while (false) continue"), + nxt_string("undefined") }, + + { nxt_string("while (false) continue;"), + nxt_string("undefined") }, + + { nxt_string("while (false) { continue }"), + nxt_string("undefined") }, + + { nxt_string("var i = 0; while (i < 100) if (i++ > 9) continue; i"), + nxt_string("100") }, + + { nxt_string("for ( ;null; ) continue"), + nxt_string("undefined") }, + + { nxt_string("for ( ;null; ) continue;"), + nxt_string("undefined") }, + + { nxt_string("for ( ;null; ) { continue }"), + nxt_string("undefined") }, + + { nxt_string("for (i = 0; i < 100; i++) if (i > 9) continue; i"), + nxt_string("100") }, + + { nxt_string("var a = []; for (i in a) continue"), + nxt_string("undefined") }, + + { nxt_string("var a = []; for (i in a) continue;"), + nxt_string("undefined") }, + + { nxt_string("var a = []; for (i in a) { continue }"), + nxt_string("undefined") }, + + { nxt_string("var a = [1,2,3,4,5]; var s = 0;" + "for (i in a) { if (a[i] > 4) continue; else s += a[i] } s"), + nxt_string("10") }, + + { nxt_string("var a = [1,2,3,4,5]; var s = 0;" + "for (i in a) { if (a[i] > 4) continue; s += a[i] } s"), + nxt_string("10") }, + + /* break. */ + + { nxt_string("break"), + nxt_string("SyntaxError") }, + + { nxt_string("do break while (true)"), + nxt_string("SyntaxError") }, + + { nxt_string("do break; while (true)"), + nxt_string("undefined") }, + + { nxt_string("do { break } while (true)"), + nxt_string("undefined") }, + + { nxt_string("var i = 0; do if (i++ > 9) break; while (i < 100); i"), + nxt_string("11") }, + + { nxt_string("while (true) break"), + nxt_string("undefined") }, + + { nxt_string("while (true) break;"), + nxt_string("undefined") }, + + { nxt_string("while (true) { break }"), + nxt_string("undefined") }, + + { nxt_string("var i = 0; while (i < 100) if (i++ > 9) break; i"), + nxt_string("11") }, + + { nxt_string("for ( ;; ) break"), + nxt_string("undefined") }, + + { nxt_string("for ( ;; ) break;"), + nxt_string("undefined") }, + + { nxt_string("for ( ;; ) { break }"), + nxt_string("undefined") }, + + { nxt_string("for (i = 0; i < 100; i++) if (i > 9) break; i"), + nxt_string("10") }, + + { nxt_string("var a = []; for (i in a) break"), + nxt_string("undefined") }, + + { nxt_string("var a = []; for (i in a) break;"), + nxt_string("undefined") }, + + { nxt_string("var a = []; for (i in a) { break }"), + nxt_string("undefined") }, + + { nxt_string("var a = [1,2,3,4,5]; var s = 0;" + "for (i in a) { if (a[i] > 4) break; else s += a[i] } s"), + nxt_string("10") }, + + { nxt_string("var a = [1,2,3,4,5]; var s = 0;" + "for (i in a) { if (a[i] > 4) break; s += a[i] } s"), + nxt_string("10") }, + +#if 0 + { nxt_string("var a = [1,2,3,4,5]; var s = 0;" + "for (i in a) if (a[i] > 4) break; s += a[i] } s"), + nxt_string("segfault") }, +#endif + + /**/ + { nxt_string("for (i = 0; i < 10; i++) { i += 1 } i"), nxt_string("10") }, -- 2.47.3