From 6282b35c4cb01f0a49bc2b0068e55bbb8a3a8fc2 Mon Sep 17 00:00:00 2001 From: Nate Biggs Date: Tue, 9 Jun 2026 19:23:06 -0700 Subject: [PATCH] [dart2js] Fix labeled jump target resolution in SSA builder When generating SSA jump instructions (HContinue and HBreak) for AST break statements, ensure that we only select labels from the target's label list that are explicitly marked as valid continue (isContinueTarget) or break (isBreakTarget) targets. Previously, handler.labels.first was selected indiscriminately if non-empty, which caused unlabeled continue statements inside loops that had an outer break label to incorrectly generate labeled continue jumps targeting the break label. Fixes: https://github.com/dart-lang/sdk/issues/63456 Change-Id: Ic497776141a192edb0930f4585cdaae2feecb3d5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/510280 Commit-Queue: Nate Biggs Reviewed-by: Stephen Adams --- pkg/compiler/lib/src/ssa/builder.dart | 22 +++++++++++--- tests/web/regress/issue/63456_test.dart | 39 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 tests/web/regress/issue/63456_test.dart diff --git a/pkg/compiler/lib/src/ssa/builder.dart b/pkg/compiler/lib/src/ssa/builder.dart index b306ee12e11..74b3ddbb27a 100644 --- a/pkg/compiler/lib/src/ssa/builder.dart +++ b/pkg/compiler/lib/src/ssa/builder.dart @@ -3457,14 +3457,28 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault JumpHandler handler = jumpTargets[target]!; final sourceInformation = _sourceInformationBuilder.buildGoto(node); if (_localsMap.generateContinueForBreak(node)) { - if (handler.labels.isNotEmpty) { - handler.generateContinue(sourceInformation, handler.labels.first); + LabelDefinition? continueLabel; + for (final label in handler.labels) { + if (label.isContinueTarget) { + continueLabel = label; + break; + } + } + if (continueLabel != null) { + handler.generateContinue(sourceInformation, continueLabel); } else { handler.generateContinue(sourceInformation); } } else { - if (handler.labels.isNotEmpty) { - handler.generateBreak(sourceInformation, handler.labels.first); + LabelDefinition? breakLabel; + for (final label in handler.labels) { + if (label.isBreakTarget) { + breakLabel = label; + break; + } + } + if (breakLabel != null) { + handler.generateBreak(sourceInformation, breakLabel); } else { handler.generateBreak(sourceInformation); } diff --git a/tests/web/regress/issue/63456_test.dart b/tests/web/regress/issue/63456_test.dart new file mode 100644 index 00000000000..1ed75a2afd9 --- /dev/null +++ b/tests/web/regress/issue/63456_test.dart @@ -0,0 +1,39 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:expect/expect.dart'; + +@pragma('dart2js:never-inline') +List> getPolys() => [ + [1, 3], + [3, 4], + [5], +]; + +@pragma('dart2js:prefer-inline') +int nextP(int p) { + if (p < 0) print(p); + return p + 1; +} + +void main() { + var polys = getPolys(); + int count = 0; + for (int iter = 0; iter < 2; iter++) { + bool removedAny = false; + outer: + for (int p = 0; p < polys.length; p = nextP(p)) { + final poly = polys[p]; + if (poly.length <= 1) continue outer; + for (int i = 0; i < poly.length; i++) { + if (poly[i] == 2) { + removedAny = true; + break outer; + } + } + } + count++; + } + Expect.equals(2, count); +}