Skip to content

Fix unlabeled break escaping a labeled switch statement - #2607

Merged
lahma merged 6 commits into
sebastienros:mainfrom
svenrog:fix/switch-unlabeled-break-in-labeled-switch
Jul 9, 2026
Merged

lahma merged 6 commits into
sebastienros:mainfrom
svenrog:fix/switch-unlabeled-break-in-labeled-switch

Conversation

@svenrog

@svenrog svenrog commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Absorb an unlabeled break inside a labeled switch statement.

Details

An unlabeled break inside a labeled switch was not absorbed by the switch:

function f(e) {
    var o = 0;
    outer: switch (e) {
        case 1: o = 8; break;
        default: o = 29;
    }
    return o;
}
f(1); // returned undefined, should be 8

JintSwitchStatement.HandleCompletion only converted a Break completion to Normal when the break's target equalled the switch's own label:

if (r.Type == CompletionType.Break
    && string.Equals(context.Target, _statement.LabelSet?.Name, StringComparison.Ordinal))

For an unlabeled break, context.Target is null. When the switch is unlabeled, LabelSet?.Name is also null, so null == null absorbed it. But once the switch carries a label, LabelSet.Name is non-null, so the unlabeled break (target null) failed the equality check and propagated out as a Break completion — skipping statements after the switch and, at function scope, making the body complete abnormally so the function returned undefined.

All loop statements (for, for-in/of, while, do-while) already guard this with context.Target == null || string.Equals(...); the switch was the only breakable missing the null-target arm. This adds it, so a switch always absorbs an unlabeled break regardless of its own label — matching the loops and the spec (a SwitchStatement absorbs break completions with an empty target).

Found running minified React 19 inside a pure-JS DOM: createFiberFromTypeAndProps compiles to e: switch (e) { case T: o = 8; l |= 24; break; ... } and returned undefined for StrictMode elements, which then blew up downstream as Cannot convert undefined or null to object.

Linked issue

No tracking issue — discovered while running minified React 19 on Jint. Refs #

Test plan

  • Added or updated unit tests in Jint.Tests
  • Ran dotnet test --configuration Release locally
  • For ECMAScript spec changes: ran Jint.Tests.Test262 and confirmed no regressions
  • For interop changes: covered in Jint.Tests/Runtime/Interop
  • For perf changes: included before/after numbers from Jint.Benchmark

Added three regression tests to Jint.Tests/Runtime/SwitchTests.cs (unlabeled break absorbed by a labeled switch; labeled break still escapes correctly; unlabeled break in a nested switch stays innermost). 3168 unit tests pass, and 737 Test262 switch/labeled/break/continue conformance tests pass, 0 failures.

Breaking change?

No.

svenrog and others added 5 commits July 9, 2026 07:43
An unlabeled `break` inside a labeled switch (`outer: switch (x) { case 1:
foo(); break; }`) was not absorbed by the switch. JintSwitchStatement's
HandleCompletion only converted a Break completion to Normal when the break's
target equalled the switch's own label:

    if (r.Type == CompletionType.Break
        && string.Equals(context.Target, _statement.LabelSet?.Name, ...))

For an unlabeled break context.Target is null. When the switch is unlabeled
LabelSet?.Name is also null, so null == null absorbed it. But once the switch
carries a label, LabelSet.Name is non-null, so the unlabeled break (target
null) failed the equality check and propagated out as a Break completion —
skipping any statements after the switch and, at function scope, causing the
body to complete abnormally so the function returned undefined.

All loop statements (for, for-in/of, while, do-while) already guard this with
`context.Target == null || string.Equals(...)`; the switch was the only
breakable missing the null-target arm. Add it so a switch always absorbs an
unlabeled break regardless of its own label, matching the loops and the spec
(a SwitchStatement absorbs break completions with an empty target).

This surfaced running minified React 19 in a JS DOM: React's
createFiberFromTypeAndProps compiles to `e: switch (e) { case T: o = 8;
l |= 24; break; ... }` and returned undefined for StrictMode elements, which
then blew up downstream as "Cannot convert undefined or null to object".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@lahma lahma left a comment

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.

Thank you!

@lahma
lahma enabled auto-merge (squash) July 9, 2026 09:32
@lahma
lahma merged commit 4057968 into sebastienros:main Jul 9, 2026
10 of 12 checks passed
@svenrog
svenrog deleted the fix/switch-unlabeled-break-in-labeled-switch branch July 9, 2026 14:58
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.

2 participants