Fix break statement in presence of labels (#742)

In this snippet...

    for (;;) label: break

...the break statement jumped back to the start of the loop instead of
*out* of the loop.

Fixes: https://github.com/quickjs-ng/quickjs/issues/741
This commit is contained in:
Ben Noordhuis 2024-12-04 23:56:52 +01:00 committed by GitHub
parent ebc1a655b4
commit 66732e78ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 11 deletions

View file

@ -18626,7 +18626,8 @@ typedef struct BlockEnv {
int drop_count; /* number of stack elements to drop */ int drop_count; /* number of stack elements to drop */
int label_finally; /* -1 if none */ int label_finally; /* -1 if none */
int scope_level; int scope_level;
int has_iterator; uint8_t has_iterator : 1;
uint8_t is_regular_stmt : 1; // i.e. not a loop statement
} BlockEnv; } BlockEnv;
typedef struct JSGlobalVar { typedef struct JSGlobalVar {
@ -24891,6 +24892,7 @@ static void push_break_entry(JSFunctionDef *fd, BlockEnv *be,
be->label_finally = -1; be->label_finally = -1;
be->scope_level = fd->scope_level; be->scope_level = fd->scope_level;
be->has_iterator = FALSE; be->has_iterator = FALSE;
be->is_regular_stmt = FALSE;
} }
static void pop_break_entry(JSFunctionDef *fd) static void pop_break_entry(JSFunctionDef *fd)
@ -24917,12 +24919,13 @@ static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont)
emit_goto(s, OP_goto, top->label_cont); emit_goto(s, OP_goto, top->label_cont);
return 0; return 0;
} }
if (!is_cont && if (!is_cont && top->label_break != -1) {
top->label_break != -1 && if (top->label_name == name ||
(name == JS_ATOM_NULL || top->label_name == name)) { (name == JS_ATOM_NULL && !top->is_regular_stmt)) {
emit_goto(s, OP_goto, top->label_break); emit_goto(s, OP_goto, top->label_break);
return 0; return 0;
} }
}
i = 0; i = 0;
if (top->has_iterator) { if (top->has_iterator) {
emit_op(s, OP_iterator_close); emit_op(s, OP_iterator_close);
@ -25335,7 +25338,8 @@ static __exception int js_parse_for_in_of(JSParseState *s, int label_name,
JS_FreeAtom(ctx, var_name); JS_FreeAtom(ctx, var_name);
if (token_is_pseudo_keyword(s, JS_ATOM_of)) { if (token_is_pseudo_keyword(s, JS_ATOM_of)) {
break_entry.has_iterator = is_for_of = TRUE; is_for_of = TRUE;
break_entry.has_iterator = TRUE;
break_entry.drop_count += 2; break_entry.drop_count += 2;
if (has_initializer) if (has_initializer)
goto initializer_error; goto initializer_error;
@ -25479,13 +25483,14 @@ static __exception int js_parse_statement_or_decl(JSParseState *s,
&& s->token.val != TOK_DO && s->token.val != TOK_DO
&& s->token.val != TOK_WHILE) { && s->token.val != TOK_WHILE) {
/* labelled regular statement */ /* labelled regular statement */
JSFunctionDef *fd = s->cur_func;
int label_break, mask; int label_break, mask;
BlockEnv break_entry; BlockEnv break_entry;
label_break = new_label(s); label_break = new_label(s);
push_break_entry(s->cur_func, &break_entry, push_break_entry(fd, &break_entry, label_name, label_break, -1, 0);
label_name, label_break, -1, 0); fd->top_break->is_regular_stmt = 1;
if (!s->cur_func->is_strict_mode && if (!fd->is_strict_mode &&
(decl_mask & DECL_MASK_FUNC_WITH_LABEL)) { (decl_mask & DECL_MASK_FUNC_WITH_LABEL)) {
mask = DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL; mask = DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL;
} else { } else {
@ -25494,7 +25499,7 @@ static __exception int js_parse_statement_or_decl(JSParseState *s,
if (js_parse_statement_or_decl(s, mask)) if (js_parse_statement_or_decl(s, mask))
goto fail; goto fail;
emit_label(s, label_break); emit_label(s, label_break);
pop_break_entry(s->cur_func); pop_break_entry(fd);
goto done; goto done;
} }
} }

19
tests/bug741.js Normal file
View file

@ -0,0 +1,19 @@
import {assert} from "./assert.js"
while (1) label: break
var i = 0
while (i < 3) label: {
if (i > 0)
break
i++
}
assert(i, 1)
for (;;) label: break
for (i = 0; i < 3; i++) label: {
if (i > 0)
break
}
assert(i, 1)