diff options
| author | Nobuyoshi Nakada <nobu@ruby-lang.org> | 2025-11-14 17:38:59 +0900 |
|---|---|---|
| committer | Nobuyoshi Nakada <nobu@ruby-lang.org> | 2026-05-12 20:04:00 +0900 |
| commit | 2f9432d2114833b105577ad72e323990daf5101e (patch) | |
| tree | 54efdf4fe8242966d954d01a7a8f6ed4e0b21f96 | |
| parent | 803b3169e0a8533cb870c995029d50cba190c322 (diff) | |
[Bug #20409] Make `break` and `redo` in `END` syntax error
| -rw-r--r-- | parse.y | 37 | ||||
| -rw-r--r-- | test/ruby/test_ast.rb | 6 |
2 files changed, 33 insertions, 10 deletions
@@ -1659,7 +1659,22 @@ static NODE *add_block_exit(struct parser_params *p, NODE *node); static rb_node_exits_t *init_block_exit(struct parser_params *p); static rb_node_exits_t *allow_block_exit(struct parser_params *p); static void restore_block_exit(struct parser_params *p, rb_node_exits_t *exits); -static void clear_block_exit(struct parser_params *p, bool error); +static void clear_block_exit(struct parser_params *p, unsigned int error_mask); + +static unsigned int +exits_mask(enum node_type t) +{ + switch (t) { + case NODE_BREAK: + case NODE_NEXT: + case NODE_REDO: + return 1u << (t - NODE_BREAK); + default: + UNREACHABLE_RETURN(0); + } +} + +#define EXITS_MASK_ALL (exits_mask(NODE_BREAK)|exits_mask(NODE_NEXT)|exits_mask(NODE_REDO)) static void next_rescue_context(struct lex_context *next, const struct lex_context *outer, enum rescue_context def) @@ -1677,7 +1692,7 @@ restore_defun(struct parser_params *p, rb_node_def_temp_t *temp) p->ctxt.in_rescue = ctxt.in_rescue; p->max_numparam = temp->save.max_numparam; numparam_pop(p, temp->save.numparam_save); - clear_block_exit(p, true); + clear_block_exit(p, EXITS_MASK_ALL); } static void @@ -1834,20 +1849,23 @@ restore_block_exit(struct parser_params *p, rb_node_exits_t *exits) } static void -clear_block_exit(struct parser_params *p, bool error) +clear_block_exit(struct parser_params *p, unsigned int error_mask) { rb_node_exits_t *exits = p->exits; if (!exits) return; - if (error) { + if (error_mask) { for (NODE *e = RNODE(exits); (e = RNODE_EXITS(e)->nd_chain) != 0; ) { switch (nd_type(e)) { case NODE_BREAK: + if (!(error_mask & exits_mask(NODE_BREAK))) break; yyerror1(&e->nd_loc, "Invalid break"); break; case NODE_NEXT: + if (!(error_mask & exits_mask(NODE_NEXT))) break; yyerror1(&e->nd_loc, "Invalid next"); break; case NODE_REDO: + if (!(error_mask & exits_mask(NODE_REDO))) break; yyerror1(&e->nd_loc, "Invalid redo"); break; default: @@ -3212,7 +3230,7 @@ top_stmts : none top_stmt : stmt { - clear_block_exit(p, true); + clear_block_exit(p, EXITS_MASK_ALL); $$ = $1; } | keyword_BEGIN begin_block @@ -3352,7 +3370,7 @@ stmt : keyword_alias[kw] fitem[new] {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fit } | stmt[body] modifier_while[mod] expr_value[cond_expr] { - clear_block_exit(p, false); + clear_block_exit(p, 0); if ($body && nd_type_p($body, NODE_BEGIN)) { $$ = NEW_WHILE(cond(p, $cond_expr, &@cond_expr), RNODE_BEGIN($body)->nd_body, 0, &@$, &@mod, &NULL_LOC); } @@ -3363,7 +3381,7 @@ stmt : keyword_alias[kw] fitem[new] {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fit } | stmt[body] modifier_until[mod] expr_value[cond_expr] { - clear_block_exit(p, false); + clear_block_exit(p, 0); if ($body && nd_type_p($body, NODE_BEGIN)) { $$ = NEW_UNTIL(cond(p, $cond_expr, &@cond_expr), RNODE_BEGIN($body)->nd_body, 0, &@$, &@mod, &NULL_LOC); } @@ -3381,9 +3399,10 @@ stmt : keyword_alias[kw] fitem[new] {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fit $$ = NEW_RESCUE(remove_begin($body), resq, 0, &@$); /*% ripper: rescue_mod!($:body, $:resbody) %*/ } - | k_END[k_end] allow_exits[allow] '{'[lbrace] compstmt(stmts)[body] '}'[rbrace] + | k_END[k_end] block_open[lbrace] compstmt(stmts)[body] '}'[rbrace] { - restore_block_exit(p, $allow); + clear_block_exit(p, exits_mask(NODE_BREAK) | exits_mask(NODE_REDO)); + restore_block_exit(p, $block_open); p->ctxt = $k_end; { NODE *scope = NEW_SCOPE2(0 /* tbl */, 0 /* args */, $body /* body */, NULL /* parent */, &@$); diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 5d95dcd46f..a59ce277d1 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -255,7 +255,11 @@ class TestAst < Test::Unit::TestCase assert_invalid_parse(msg, "#{code}") assert_invalid_parse(msg, "def m; #{code}; end") assert_invalid_parse(msg, "begin; #{code}; end") - assert_parse("END {#{code}}") + if code.start_with?("next") + assert_parse("END {#{code}}") + else + assert_invalid_parse(msg, "END {#{code}}") + end assert_parse("!defined?(#{code})") assert_parse("def m; defined?(#{code}); end") |
