`continue` to an outer label of a label set (`a: b: for (…) continue a;`) gives "break/continue label not found"

オープン
#595 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 3 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
48/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
c, javascript
領域
compilers, testing

調査の方向性

Start in quickjs.c at js_parse_statement_or_decl, push_break_entry, and emit_break; the issue identifies the current limitation and includes a proposed change. Add or inspect regression coverage in tests/test_language.js for chained labels, including valid continue targets and invalid targets through non-loop statements. Run make test and compare test262 results with test262_errors.txt; done means the reproduction prints 2 and the invalid cases remain SyntaxErrors.

索引モデルが issue の本文から書いたものです。

説明

Environment
  • QuickJS master 712e8c0 (2026-11-02) and release 2026-06-04, make qjs, Debian 12 x86-64, gcc 12.2
  • Reference: node v26.13.0
  • Checked on 2026-11-03
Reproduction
var n = 0;
a: b: for (var i = 0; i < 2; i++) { n++; continue a; }
console.log(n);

Expected 2. QuickJS: SyntaxError: break/continue label not found. continue b and break a work. The same
happens with while, do … while, for-in/for-of and longer label chains.

Expected (spec)

ContainsUndefinedContinueTarget:
each LabelledStatement appends its label to labelSet, and BreakableStatement : IterationStatement adds the whole
labelSet to iterationSet. The loop's label set is « a, b », so continue a is valid and continues the loop
(LoopContinues). a: { b: for (;;) continue a; } and
a: b: if (1) for (;;) continue a; must remain SyntaxErrors.

Cause and patch

js_parse_statement_or_decl notes /* XXX: support multiple labels on loop statements */. Only the last label becomes
the loop's BlockEnv.label_name; the outer labels are labelled regular statements with label_cont == -1. The patch
below (with a test in tests/test_language.js; make test passes, and test262 shows no new failures against
test262_errors.txt) lets those entries point at the loop they directly label and maps continue <outer> to the loop's
label in emit_break():

diff --git a/quickjs.c b/quickjs.c
--- a/quickjs.c
+++ b/quickjs.c
@@ -22471,6 +22471,9 @@ enum {
 
 typedef struct BlockEnv {
     struct BlockEnv *prev;
+    /* loop directly labelled by this label statement (a: b: for ...), so
+       that `continue a` targets that loop */
+    struct BlockEnv *cont_alias;
     JSAtom label_name; /* JS_ATOM_NULL if none */
     int label_break; /* -1 if none */
     int label_cont; /* -1 if none */
@@ -22482,6 +22485,7 @@ typedef struct BlockEnv {
     uint8_t has_iterator : 1;
     uint8_t is_async_iterator : 1;
     uint8_t is_regular_stmt : 1; /* i.e. not a loop statement */
+    uint8_t labels_next : 1; /* label statement whose body is another label */
     uint8_t has_using : 1; /* the block contains a using declaration */
     uint8_t is_await_using : 1; /* the block has await using declarations */
 } BlockEnv;
@@ -28958,8 +28962,11 @@ static void push_break_entry(JSFunctionDef *fd, BlockEnv *be,
                              int label_break, int label_cont,
                              int drop_count)
 {
+    BlockEnv *p;
     be->prev = fd->top_break;
     fd->top_break = be;
+    be->cont_alias = NULL;
+    be->labels_next = 0;
     be->label_name = label_name;
     be->label_break = label_break;
     be->label_cont = label_cont;
@@ -28973,6 +28980,10 @@ static void push_break_entry(JSFunctionDef *fd, BlockEnv *be,
     be->is_await_using = FALSE;
     be->using_label_catch = -1;
     be->using_label_end = -1;
+    if (label_cont != -1) {
+        for (p = be->prev; p && p->is_regular_stmt && p->labels_next; p = p->prev)
+            p->cont_alias = be;
+    }
 }
 
 static void js_emit_dispose_scope(JSParseState *s, int scope,
@@ -29089,6 +29100,15 @@ static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont)
     int i, scope_level;
 
     scope_level = s->cur_func->scope_level;
+    if (is_cont && name != JS_ATOM_NULL) {
+        for (top = s->cur_func->top_break; top; top = top->prev) {
+            if (top->label_name == name) {
+                if (top->cont_alias)
+                    name = top->cont_alias->label_name;
+                break;
+            }
+        }
+    }
     top = s->cur_func->top_break;
     while (top != NULL) {
         close_scopes(s, scope_level, top->scope_level);
@@ -29807,7 +29827,6 @@ static __exception int js_parse_statement_or_decl(JSParseState *s,
     int tok;
 
     /* specific label handling */
-    /* XXX: support multiple labels on loop statements */
     label_name = JS_ATOM_NULL;
     if (is_label(s)) {
         BlockEnv *be;
@@ -29836,6 +29855,7 @@ static __exception int js_parse_statement_or_decl(JSParseState *s,
             push_break_entry(s->cur_func, &break_entry,
                              label_name, label_break, -1, 0);
             break_entry.is_regular_stmt = TRUE;
+            s->cur_func->top_break->labels_next = is_label(s);
             if (!(s->cur_func->js_mode & JS_MODE_STRICT) &&
                 (decl_mask & DECL_MASK_FUNC_WITH_LABEL)) {
                 mask = DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL;
Related
  • quickjs-ng has the same code: quickjs-ng/quickjs#1884
  • #592 (continue in for (let …) skips the per-iteration copy) is a separate bug in the same loop code. The fixes are independent.
主要言語
C
スター
11.1k
フォーク
1.2k
平均マージ
11時間 20分
マージ済み PR(30日)
1

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

bellard/quickjs のほかの issue

bellard/quickjs の issue をすべて見る

似ている issue

C の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。