summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNobuyoshi Nakada <nobu@ruby-lang.org>2025-11-14 17:38:59 +0900
committerNobuyoshi Nakada <nobu@ruby-lang.org>2026-05-12 20:04:00 +0900
commit2f9432d2114833b105577ad72e323990daf5101e (patch)
tree54efdf4fe8242966d954d01a7a8f6ed4e0b21f96
parent803b3169e0a8533cb870c995029d50cba190c322 (diff)
[Bug #20409] Make `break` and `redo` in `END` syntax error
-rw-r--r--parse.y37
-rw-r--r--test/ruby/test_ast.rb6
2 files changed, 33 insertions, 10 deletions
diff --git a/parse.y b/parse.y
index d21abbe7e4..d163d2a050 100644
--- a/parse.y
+++ b/parse.y
@@ -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")