Skip to content

fix(parser): restrict continue targets to iteration statement labels - #646

Merged
3cp merged 2 commits into
meriyah:mainfrom
MaxFreedomPollard:fix-continue-label-iteration-target
Sep 9, 2026
Merged

3cp merged 2 commits into
meriyah:mainfrom
MaxFreedomPollard:fix-continue-label-iteration-target

Conversation

@MaxFreedomPollard

Copy link
Copy Markdown
Contributor

a: { for (;;) { continue a; } } parses. A continue may only target a label of an iteration statement it is nested in, and a here labels a block, so ContainsUndefinedContinueTarget makes it an early SyntaxError. The same hole accepts a label on an if, on a switch case, on a try block, and on a block nested inside a loop. V8 and Acorn reject all of them, and the whitelisted test262 fixture language/statements/block/labeled-continue.js is exactly this shape.

isValidLabel in src/common.ts walks the label set chain outwards from the continue and cleared its "must be inside an iteration statement" requirement at the first set marked loop, then accepted the label wherever it turned up further out. Any loop between the continue and the label satisfied the check, whatever statement the label was actually attached to.

parseIterationStatementBody marks an iteration statement's body set with loop, and a labelled statement declares its label into the set it is itself parsed with, so the set directly above a loop set holds that iteration statement's own labels. The walk now tracks whether the set it is looking at is such a set, and only accepts a continue label declared there. break passes isIterationStatement as 0 and is untouched, so a: { for (;;) { break a; } } still parses; there is a test for that.

Verification on Node 22 against main at 56416fa. The seven newly rejected shapes fail on unmodified main with "Expect a ParserError thrown" and pass with the change; six accepted shapes are added alongside them, including a: b: for (;;) { continue a; }, a: for (;;) { for (;;) continue a; } and a: while (x) { b: { continue a; } }. vitest run is 91860 passing over 139 files with no existing snapshot touched. Full test262 at the pinned commit 7710052 goes from 8694 to 8696 invalid programs rejected while valid programs accepted stays at 94569, so nothing valid was tightened out, and the two labeled-continue.js whitelist lines come out. The ast alignment, jsx alignment and production suites pass, and eslint, prettier --check, tsc, cspell, knip and generate-unicode.mjs --check are clean.

`a: { for (;;) { continue a; } }` parsed. A `continue` may only target a label
of an iteration statement it is nested in, and here `a` labels a block, so
ContainsUndefinedContinueTarget makes it an early SyntaxError. The same hole
accepted a label on an `if`, on a `switch` case, on a `try` block and on a block
nested inside a loop.

`isValidLabel` in src/common.ts walks the label set chain outwards from the
`continue` and cleared its "must be inside an iteration statement" requirement at
the first set marked `loop`, then accepted the label wherever it turned up further
out, whatever statement it was attached to.

`parseIterationStatementBody` marks an iteration statement's body set with `loop`,
and a labelled statement declares its label into the set it is itself parsed with,
so the set directly above a `loop` set holds that iteration statement's own labels.
The walk now tracks whether the set it is looking at is such a set and only accepts
a `continue` label declared there. `break` passes `isIterationStatement` as 0 and is
unchanged.

Drops the two `language/statements/block/labeled-continue.js` lines from
test262/whitelist.txt.
@pkg-pr-new

pkg-pr-new Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/meriyah@646

commit: f1f565a

@3cp 3cp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx! Only might need two small adjustments.

Comment on lines +55 to +56
> 1 | a: { b: for (;;) { continue a; } }
| ^ continue statement must be nested within an iteration statement"

@3cp 3cp Sep 7, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error location can be fixed by swapping parseIdentifer and if (!isValidLabel) in src/parser.ts.

There are two usages in src/parser.ts. Take one as example:

// one in parseContinueStatement
    const { tokenValue } = parser;
    if (!isValidLabel(parser, labels, tokenValue, /* requireIterationStatement */ 1))
      parser.report(Errors.UnknownLabel, tokenValue);
    label = parseIdentifier(parser, context | Context.AllowRegExp);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the printed error "continue statement must be nested within an iteration statement" sounds very confusing.

@fisker any suggestion to improve that error message? It probably should reflect the label was not for a loop statement.

Acorn uses "Unsyntactic break/continue" for all types of error. It isn't much better.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acorn uses "Unsyntactic break/continue" for all types of error. It isn't much better.

Agree. And babel also use the same, I guess because they started as a fork of acorn.

Oxc uses

Label 'a' does not denote an iteration statement

better?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely better than ours :-)

@3cp

3cp commented Sep 7, 2026

Copy link
Copy Markdown
Member

@fisker we probably need to improve the type def for export type Labels = any;.

Validate the label before parseIdentifier consumes it, in both
parseContinueStatement and parseBreakStatement, so the reported error
points at the label itself instead of the token after it.

Reword the iteration statement error to "Label 'a' does not denote an
iteration statement", the wording Oxc uses, and pass the label name to
it. Snapshots regenerated.
@3cp
3cp merged commit 3df8ade into meriyah:main Sep 9, 2026
8 checks passed
@3cp

3cp commented Sep 9, 2026

Copy link
Copy Markdown
Member

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants