[clang] [clang-tools-extra] [Clang] [C2y] Implement N3355 ‘NamedLoops’ (PR #152870)

via cfe-commits cfe-commits at lists.llvm.org
Sat Aug 9 10:07:37 PDT 2025


https://github.com/Sirraide created https://github.com/llvm/llvm-project/pull/152870

This implements support for [named loops](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm) for C2y. We also make them available in earlier language modes and in C++ as an extension, with support for templates and constant evaluation (though I haven’t added support for it to the experimental interpreter because this patch is already quite long and I’m not too familiar w/ it candidly).

The basic approach is that we treat `break label` and `continue label` more or less like `goto label`; that is, we defer all the checking to JumpDiagnostics.cpp. This *does* mean that we run the jump checker on the entire function if it contains a labeled break/continue. The alternative from what I can tell would have been to create the loop statements early before we parse their body; that’s what I tried originally, but it was shaping up to be quite the large refactor. Also, breaking out of a nested loop isn’t that common anyway, so I don’t think running the jump checker on the entire function is really that big of a deal because of that.

@AaronBallman mentioned that it would be nice to have a link from e.g. a WhileStmt back to any label(s) naming it for AST matchers; I haven’t implemented anything to facilitate that yet, but my idea for that would be to add a `LabelDecl*` to `WhileStmt`, `ForStmt`, etc. (basically anything that can be named by a label), and then when we are done parsing a `LabelStmt`, we check if its child is e.g. a `WhileStmt` (skipping any intervening `LabelStmt`s), and if so, we store that statement in that `WhileStmt`. That is, if we have `a: b: c: while`, then we’d ultimately store the decl for the outermost label (that being `a` here) in the `WhileStmt`; the remaining labels can be obtained by walking the AST. 

Unfortunately, that does mean that every `WhileStmt` etc. would need to have an extra `Decl*`, which would be `nullptr` in the overwhelming majority of cases. I guess we could try and sort of pass a `IsNamedByLabel` flag down throughout the parser when we start parsing a `LabelStmt`, which might let us put it in the trailing data where it can just be omitted entirely if there is no label, but I haven’t thought this through in any amount of detail.

Alternatively, we can just not support that and require people to write `labelStmt(hasName("b"), whileLoop())` or whatever (I candidly don’t know the proper AST matcher syntax for that because I basically never use AST matchers). Aaron pointed out that that doesn’t work if there are multiple labels (e.g. if we’re trying to match a loop w/ the name `b` in `a: b: c: while`), but I’m not sure whether multiple labels in a row naming the same statement is really a use case we care to support.

>From 56a2eff96699b3e11404cc16cc4249367a8bbcf5 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 06:10:05 +0200
Subject: [PATCH 01/19] Parsing labeled break/continue

---
 clang/include/clang/AST/Stmt.h                | 111 ++++++++++--------
 .../clang/Basic/DiagnosticSemaKinds.td        |   3 +
 clang/include/clang/Basic/StmtNodes.td        |   7 +-
 clang/lib/AST/ASTImporter.cpp                 |  26 ++--
 clang/lib/Parse/ParseStmt.cpp                 |  16 ++-
 clang/lib/Serialization/ASTReaderStmt.cpp     |  16 ++-
 clang/lib/Serialization/ASTWriterStmt.cpp     |  17 ++-
 clang/lib/Tooling/Syntax/BuildTree.cpp        |   4 +-
 8 files changed, 130 insertions(+), 70 deletions(-)

diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index a5b0d5053003f..7eec610673a5f 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -277,24 +277,14 @@ class alignas(void *) Stmt {
     SourceLocation GotoLoc;
   };
 
-  class ContinueStmtBitfields {
-    friend class ContinueStmt;
+  class LoopControlStmtBitfields {
+    friend class LoopControlStmt;
 
     LLVM_PREFERRED_TYPE(StmtBitfields)
     unsigned : NumStmtBits;
 
-    /// The location of the "continue".
-    SourceLocation ContinueLoc;
-  };
-
-  class BreakStmtBitfields {
-    friend class BreakStmt;
-
-    LLVM_PREFERRED_TYPE(StmtBitfields)
-    unsigned : NumStmtBits;
-
-    /// The location of the "break".
-    SourceLocation BreakLoc;
+    /// The location of the "continue"/"break".
+    SourceLocation KwLoc;
   };
 
   class ReturnStmtBitfields {
@@ -1325,8 +1315,7 @@ class alignas(void *) Stmt {
     DoStmtBitfields DoStmtBits;
     ForStmtBitfields ForStmtBits;
     GotoStmtBitfields GotoStmtBits;
-    ContinueStmtBitfields ContinueStmtBits;
-    BreakStmtBitfields BreakStmtBits;
+    LoopControlStmtBitfields LoopControlStmtBits;
     ReturnStmtBitfields ReturnStmtBits;
     SwitchCaseBitfields SwitchCaseBits;
 
@@ -3056,26 +3045,39 @@ class IndirectGotoStmt : public Stmt {
   }
 };
 
-/// ContinueStmt - This represents a continue.
-class ContinueStmt : public Stmt {
-public:
-  ContinueStmt(SourceLocation CL) : Stmt(ContinueStmtClass) {
-    setContinueLoc(CL);
-  }
+/// Base class for BreakStmt and ContinueStmt.
+class LoopControlStmt : public Stmt {
+  /// If this is a labeled break, the loop or switch statement that we need
+  /// to continue/break.
+  Stmt* LabeledStmt = nullptr;
 
-  /// Build an empty continue statement.
-  explicit ContinueStmt(EmptyShell Empty) : Stmt(ContinueStmtClass, Empty) {}
+  /// Location of the label, if any.
+  SourceLocation Label;
 
-  SourceLocation getContinueLoc() const { return ContinueStmtBits.ContinueLoc; }
-  void setContinueLoc(SourceLocation L) { ContinueStmtBits.ContinueLoc = L; }
+protected:
+  LoopControlStmt(StmtClass Class, SourceLocation Loc) : Stmt(Class) {
+    setKwLoc(Loc);
+  }
 
-  SourceLocation getBeginLoc() const { return getContinueLoc(); }
-  SourceLocation getEndLoc() const { return getContinueLoc(); }
+  LoopControlStmt(StmtClass Class, EmptyShell ES): Stmt(Class, ES) {}
 
-  static bool classof(const Stmt *T) {
-    return T->getStmtClass() == ContinueStmtClass;
+public:
+  SourceLocation getKwLoc() const { return LoopControlStmtBits.KwLoc; }
+  void setKwLoc(SourceLocation L) { LoopControlStmtBits.KwLoc = L; }
+
+  SourceLocation getBeginLoc() const { return getKwLoc(); }
+  SourceLocation getEndLoc() const {
+    return isLabeled() ? getLabelLoc() : getKwLoc();
   }
 
+  bool isLabeled() const { return Label != SourceLocation(); }
+
+  SourceLocation getLabelLoc() const { return Label; }
+  void setLabelLoc(SourceLocation L) { Label = L; }
+
+  Stmt* getLabeledStmt() const { return LabeledStmt; }
+  void setLabeledStmt(Stmt* S) { LabeledStmt = S; }
+
   // Iterators
   child_range children() {
     return child_range(child_iterator(), child_iterator());
@@ -3084,35 +3086,48 @@ class ContinueStmt : public Stmt {
   const_child_range children() const {
     return const_child_range(const_child_iterator(), const_child_iterator());
   }
+
+  static bool classof(const Stmt *T) {
+    StmtClass Class = T->getStmtClass();
+    return Class == ContinueStmtClass || Class == BreakStmtClass;
+  }
 };
 
-/// BreakStmt - This represents a break.
-class BreakStmt : public Stmt {
+/// ContinueStmt - This represents a continue.
+class ContinueStmt : public LoopControlStmt {
 public:
-  BreakStmt(SourceLocation BL) : Stmt(BreakStmtClass) {
-    setBreakLoc(BL);
+  ContinueStmt(SourceLocation CL) : LoopControlStmt(ContinueStmtClass, CL) {}
+  ContinueStmt(SourceLocation CL, SourceLocation LabelLoc, Stmt *Loop)
+    : LoopControlStmt(ContinueStmtClass, CL) {
+    setLabelLoc(LabelLoc);
+    setLabeledStmt(Loop);
   }
 
-  /// Build an empty break statement.
-  explicit BreakStmt(EmptyShell Empty) : Stmt(BreakStmtClass, Empty) {}
-
-  SourceLocation getBreakLoc() const { return BreakStmtBits.BreakLoc; }
-  void setBreakLoc(SourceLocation L) { BreakStmtBits.BreakLoc = L; }
-
-  SourceLocation getBeginLoc() const { return getBreakLoc(); }
-  SourceLocation getEndLoc() const { return getBreakLoc(); }
+  /// Build an empty continue statement.
+  explicit ContinueStmt(EmptyShell Empty)
+      : LoopControlStmt(ContinueStmtClass, Empty) {}
 
   static bool classof(const Stmt *T) {
-    return T->getStmtClass() == BreakStmtClass;
+    return T->getStmtClass() == ContinueStmtClass;
   }
+};
 
-  // Iterators
-  child_range children() {
-    return child_range(child_iterator(), child_iterator());
+/// BreakStmt - This represents a break.
+class BreakStmt : public LoopControlStmt {
+public:
+  BreakStmt(SourceLocation BL) : LoopControlStmt(BreakStmtClass, BL) {}
+  BreakStmt(SourceLocation CL, SourceLocation LabelLoc, Stmt *LoopOrSwitch)
+    : LoopControlStmt(BreakStmtClass, CL) {
+    setLabelLoc(LabelLoc);
+    setLabeledStmt(LoopOrSwitch);
   }
 
-  const_child_range children() const {
-    return const_child_range(const_child_iterator(), const_child_iterator());
+  /// Build an empty break statement.
+  explicit BreakStmt(EmptyShell Empty)
+      : LoopControlStmt(BreakStmtClass, Empty) {}
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == BreakStmtClass;
   }
 };
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index cf23594201143..227849ca3d5a7 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10796,6 +10796,9 @@ def err_continue_not_in_loop : Error<
   "'continue' statement not in loop statement">;
 def err_break_not_in_loop_or_switch : Error<
   "'break' statement not in loop or switch statement">;
+def err_break_continue_label_not_found: Error<
+    "'%select{continue|break}0' label does not name an enclosing %select{loop|loop or 'switch'}0">;
+def err_continue_switch: Error<"label of 'continue' refers to a switch statement">;
 def warn_loop_ctrl_binds_to_inner : Warning<
   "'%0' is bound to current loop, GCC binds it to the enclosing loop">,
   InGroup<GccCompat>;
diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index c9c173f5c7469..046ef4f30e232 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -16,8 +16,6 @@ def DoStmt : StmtNode<Stmt>;
 def ForStmt : StmtNode<Stmt>;
 def GotoStmt : StmtNode<Stmt>;
 def IndirectGotoStmt : StmtNode<Stmt>;
-def ContinueStmt : StmtNode<Stmt>;
-def BreakStmt : StmtNode<Stmt>;
 def ReturnStmt : StmtNode<Stmt>;
 def DeclStmt  : StmtNode<Stmt>;
 def SwitchCase : StmtNode<Stmt, 1>;
@@ -26,6 +24,11 @@ def DefaultStmt : StmtNode<SwitchCase>;
 def CapturedStmt : StmtNode<Stmt>;
 def SYCLKernelCallStmt : StmtNode<Stmt>;
 
+// Break/continue.
+def LoopControlStmt : StmtNode<Stmt, 1>;
+def ContinueStmt : StmtNode<LoopControlStmt>;
+def BreakStmt : StmtNode<LoopControlStmt>;
+
 // Statements that might produce a value (for example, as the last non-null
 // statement in a GNU statement-expression).
 def ValueStmt : StmtNode<Stmt, 1>;
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 8e2927bdc8d6f..4295b7dc3dfef 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -7407,18 +7407,28 @@ ExpectedStmt ASTNodeImporter::VisitIndirectGotoStmt(IndirectGotoStmt *S) {
       ToGotoLoc, ToStarLoc, ToTarget);
 }
 
+template <typename StmtClass>
+static ExpectedStmt ImportLoopControlStmt(ASTNodeImporter &NodeImporter,
+                                          ASTImporter &Importer, StmtClass *S) {
+  Error Err = Error::success();
+  auto ToLoc = NodeImporter.importChecked(Err, S->getKwLoc());
+  auto ToLabelLoc = S->isLabeled()
+                        ? NodeImporter.importChecked(Err, S->getLabelLoc())
+                        : SourceLocation();
+  auto ToStmt = S->isLabeled()
+                    ? NodeImporter.importChecked(Err, S->getLabeledStmt())
+                    : nullptr;
+  if (Err)
+    return std::move(Err);
+  return new (Importer.getToContext()) StmtClass(ToLoc, ToLabelLoc, ToStmt);
+}
+
 ExpectedStmt ASTNodeImporter::VisitContinueStmt(ContinueStmt *S) {
-  ExpectedSLoc ToContinueLocOrErr = import(S->getContinueLoc());
-  if (!ToContinueLocOrErr)
-    return ToContinueLocOrErr.takeError();
-  return new (Importer.getToContext()) ContinueStmt(*ToContinueLocOrErr);
+  return ImportLoopControlStmt(*this, Importer, S);
 }
 
 ExpectedStmt ASTNodeImporter::VisitBreakStmt(BreakStmt *S) {
-  auto ToBreakLocOrErr = import(S->getBreakLoc());
-  if (!ToBreakLocOrErr)
-    return ToBreakLocOrErr.takeError();
-  return new (Importer.getToContext()) BreakStmt(*ToBreakLocOrErr);
+  return ImportLoopControlStmt(*this, Importer, S);
 }
 
 ExpectedStmt ASTNodeImporter::VisitReturnStmt(ReturnStmt *S) {
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index bf1978c22ee9f..148dc6e5bc0b8 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -2290,12 +2290,24 @@ StmtResult Parser::ParseGotoStatement() {
 
 StmtResult Parser::ParseContinueStatement() {
   SourceLocation ContinueLoc = ConsumeToken();  // eat the 'continue'.
-  return Actions.ActOnContinueStmt(ContinueLoc, getCurScope());
+  if (!Tok.is(tok::identifier))
+    return Actions.ActOnContinueStmt(ContinueLoc, getCurScope());
+
+  StmtResult Res = Actions.ActOnLabelledContinueStmt(
+      ContinueLoc, Tok.getIdentifierInfo(), Tok.getLocation());
+  ConsumeToken();
+  return Res;
 }
 
 StmtResult Parser::ParseBreakStatement() {
   SourceLocation BreakLoc = ConsumeToken();  // eat the 'break'.
-  return Actions.ActOnBreakStmt(BreakLoc, getCurScope());
+  if (!Tok.is(tok::identifier))
+    return Actions.ActOnBreakStmt(BreakLoc, getCurScope());
+
+  StmtResult Res = Actions.ActOnLabelledBreakStmt(
+      BreakLoc, Tok.getIdentifierInfo(), Tok.getLocation());
+  ConsumeToken();
+  return Res;
 }
 
 StmtResult Parser::ParseReturnStatement() {
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 3f37dfbc3dea9..46d93fe626f54 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -116,6 +116,7 @@ namespace clang {
                                    TemplateArgumentLoc *ArgsLocArray,
                                    unsigned NumTemplateArgs);
 
+    void VisitLoopControlStmt(LoopControlStmt *S);
     void VisitStmt(Stmt *S);
 #define STMT(Type, Base) \
     void Visit##Type(Type *);
@@ -320,14 +321,21 @@ void ASTStmtReader::VisitIndirectGotoStmt(IndirectGotoStmt *S) {
   S->setTarget(Record.readSubExpr());
 }
 
-void ASTStmtReader::VisitContinueStmt(ContinueStmt *S) {
+void ASTStmtReader::VisitLoopControlStmt(LoopControlStmt *S) {
   VisitStmt(S);
-  S->setContinueLoc(readSourceLocation());
+  S->setKwLoc(readSourceLocation());
+  if (Record.readBool()) {
+    S->setLabeledStmt(Record.readSubStmt());
+    S->setLabelLoc(readSourceLocation());
+  }
+}
+
+void ASTStmtReader::VisitContinueStmt(ContinueStmt *S) {
+  VisitLoopControlStmt(S);
 }
 
 void ASTStmtReader::VisitBreakStmt(BreakStmt *S) {
-  VisitStmt(S);
-  S->setBreakLoc(readSourceLocation());
+  VisitLoopControlStmt(S);
 }
 
 void ASTStmtReader::VisitReturnStmt(ReturnStmt *S) {
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index be9bad9e96cc1..57832871b970a 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -109,6 +109,7 @@ namespace clang {
     void AddTemplateKWAndArgsInfo(const ASTTemplateKWAndArgsInfo &ArgInfo,
                                   const TemplateArgumentLoc *Args);
 
+    void VisitLoopControlStmt(LoopControlStmt *S);
     void VisitStmt(Stmt *S);
 #define STMT(Type, Base) \
     void Visit##Type(Type *);
@@ -310,15 +311,23 @@ void ASTStmtWriter::VisitIndirectGotoStmt(IndirectGotoStmt *S) {
   Code = serialization::STMT_INDIRECT_GOTO;
 }
 
-void ASTStmtWriter::VisitContinueStmt(ContinueStmt *S) {
+void ASTStmtWriter::VisitLoopControlStmt(LoopControlStmt *S) {
   VisitStmt(S);
-  Record.AddSourceLocation(S->getContinueLoc());
+  Record.AddSourceLocation(S->getKwLoc());
+  Record.push_back(S->isLabeled());
+  if (S->isLabeled()) {
+    Record.AddStmt(S->getLabeledStmt());
+    Record.AddSourceLocation(S->getLabelLoc());
+  }
+}
+
+void ASTStmtWriter::VisitContinueStmt(ContinueStmt *S) {
+  VisitLoopControlStmt(S);
   Code = serialization::STMT_CONTINUE;
 }
 
 void ASTStmtWriter::VisitBreakStmt(BreakStmt *S) {
-  VisitStmt(S);
-  Record.AddSourceLocation(S->getBreakLoc());
+  VisitLoopControlStmt(S);
   Code = serialization::STMT_BREAK;
 }
 
diff --git a/clang/lib/Tooling/Syntax/BuildTree.cpp b/clang/lib/Tooling/Syntax/BuildTree.cpp
index eb9fa7a7fa1e8..7688e91dc09f1 100644
--- a/clang/lib/Tooling/Syntax/BuildTree.cpp
+++ b/clang/lib/Tooling/Syntax/BuildTree.cpp
@@ -1483,7 +1483,7 @@ class BuildTreeVisitor : public RecursiveASTVisitor<BuildTreeVisitor> {
   }
 
   bool WalkUpFromContinueStmt(ContinueStmt *S) {
-    Builder.markChildToken(S->getContinueLoc(),
+    Builder.markChildToken(S->getKwLoc(),
                            syntax::NodeRole::IntroducerKeyword);
     Builder.foldNode(Builder.getStmtRange(S),
                      new (allocator()) syntax::ContinueStatement, S);
@@ -1491,7 +1491,7 @@ class BuildTreeVisitor : public RecursiveASTVisitor<BuildTreeVisitor> {
   }
 
   bool WalkUpFromBreakStmt(BreakStmt *S) {
-    Builder.markChildToken(S->getBreakLoc(),
+    Builder.markChildToken(S->getKwLoc(),
                            syntax::NodeRole::IntroducerKeyword);
     Builder.foldNode(Builder.getStmtRange(S),
                      new (allocator()) syntax::BreakStatement, S);

>From 746b1ab54ed34f2e05b626471a678a4e1cb68e1c Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 06:10:38 +0200
Subject: [PATCH 02/19] Rename getBreak/ContinueLoc

---
 clang-tools-extra/clangd/XRefs.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 83a8b7289aec3..a98b17bd33475 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1106,11 +1106,11 @@ class FindControlFlow : public RecursiveASTVisitor<FindControlFlow> {
     return true;
   }
   bool VisitBreakStmt(BreakStmt *B) {
-    found(Break, B->getBreakLoc());
+    found(Break, B->getKwLoc());
     return true;
   }
   bool VisitContinueStmt(ContinueStmt *C) {
-    found(Continue, C->getContinueLoc());
+    found(Continue, C->getKwLoc());
     return true;
   }
   bool VisitSwitchCase(SwitchCase *C) {

>From 27a2eae68f763aefc79258113cdf6f295c56eb24 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 06:13:13 +0200
Subject: [PATCH 03/19] more renaming

---
 clang/lib/Sema/SemaStmt.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index a5f92020f49f8..18fabd982f486 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -2129,12 +2129,12 @@ namespace {
     typedef ConstEvaluatedExprVisitor<BreakContinueFinder> Inherited;
 
     void VisitContinueStmt(const ContinueStmt* E) {
-      ContinueLoc = E->getContinueLoc();
+      ContinueLoc = E->getKwLoc();
     }
 
     void VisitBreakStmt(const BreakStmt* E) {
       if (!InSwitch)
-        BreakLoc = E->getBreakLoc();
+        BreakLoc = E->getKwLoc();
     }
 
     void VisitSwitchStmt(const SwitchStmt* S) {

>From 674bcf4fff754aa62d7ec9fd2f0beff33823a5c0 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 10:47:18 +0200
Subject: [PATCH 04/19] Sema

---
 clang/include/clang/AST/Stmt.h            |  24 ++--
 clang/include/clang/AST/TextNodeDumper.h  |   1 +
 clang/include/clang/Sema/ScopeInfo.h      |  15 ++-
 clang/include/clang/Sema/Sema.h           |   6 +-
 clang/lib/AST/ASTImporter.cpp             |   6 +-
 clang/lib/AST/Stmt.cpp                    |   7 +
 clang/lib/AST/TextNodeDumper.cpp          |  20 +++
 clang/lib/Parse/ParseStmt.cpp             |  30 +++--
 clang/lib/Sema/JumpDiagnostics.cpp        | 100 ++++++++++++--
 clang/lib/Sema/SemaStmt.cpp               |  21 ++-
 clang/lib/Serialization/ASTReaderStmt.cpp |   3 +-
 clang/lib/Serialization/ASTWriterStmt.cpp |   3 +-
 clang/test/Sema/labeled-break-continue.c  | 152 ++++++++++++++++++++++
 13 files changed, 335 insertions(+), 53 deletions(-)
 create mode 100644 clang/test/Sema/labeled-break-continue.c

diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index 7eec610673a5f..b4c6752823eb0 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -3047,9 +3047,9 @@ class IndirectGotoStmt : public Stmt {
 
 /// Base class for BreakStmt and ContinueStmt.
 class LoopControlStmt : public Stmt {
-  /// If this is a labeled break, the loop or switch statement that we need
-  /// to continue/break.
-  Stmt* LabeledStmt = nullptr;
+  /// If this is a labeled break/continue, the label whose statement we're
+  /// targeting.
+  LabelDecl* TargetLabel = nullptr;
 
   /// Location of the label, if any.
   SourceLocation Label;
@@ -3070,13 +3070,17 @@ class LoopControlStmt : public Stmt {
     return isLabeled() ? getLabelLoc() : getKwLoc();
   }
 
-  bool isLabeled() const { return Label != SourceLocation(); }
+  bool isLabeled() const { return TargetLabel; }
 
   SourceLocation getLabelLoc() const { return Label; }
   void setLabelLoc(SourceLocation L) { Label = L; }
 
-  Stmt* getLabeledStmt() const { return LabeledStmt; }
-  void setLabeledStmt(Stmt* S) { LabeledStmt = S; }
+  LabelDecl* getLabelDecl() const { return TargetLabel; }
+  void setLabelDecl(LabelDecl* S) { TargetLabel = S; }
+
+  /// If this is a labeled break/continue, get the loop or switch statement
+  /// that this targets.
+  Stmt *getLabelTarget() const;
 
   // Iterators
   child_range children() {
@@ -3097,10 +3101,10 @@ class LoopControlStmt : public Stmt {
 class ContinueStmt : public LoopControlStmt {
 public:
   ContinueStmt(SourceLocation CL) : LoopControlStmt(ContinueStmtClass, CL) {}
-  ContinueStmt(SourceLocation CL, SourceLocation LabelLoc, Stmt *Loop)
+  ContinueStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target)
     : LoopControlStmt(ContinueStmtClass, CL) {
     setLabelLoc(LabelLoc);
-    setLabeledStmt(Loop);
+    setLabelDecl(Target);
   }
 
   /// Build an empty continue statement.
@@ -3116,10 +3120,10 @@ class ContinueStmt : public LoopControlStmt {
 class BreakStmt : public LoopControlStmt {
 public:
   BreakStmt(SourceLocation BL) : LoopControlStmt(BreakStmtClass, BL) {}
-  BreakStmt(SourceLocation CL, SourceLocation LabelLoc, Stmt *LoopOrSwitch)
+  BreakStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target)
     : LoopControlStmt(BreakStmtClass, CL) {
     setLabelLoc(LabelLoc);
-    setLabeledStmt(LoopOrSwitch);
+    setLabelDecl(Target);
   }
 
   /// Build an empty break statement.
diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index 1917a8ac29f05..324d9bc26aae0 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -255,6 +255,7 @@ class TextNodeDumper
   void VisitExpressionTemplateArgument(const TemplateArgument &TA);
   void VisitPackTemplateArgument(const TemplateArgument &TA);
 
+  void VisitLoopControlStmt(const LoopControlStmt *L);
   void VisitIfStmt(const IfStmt *Node);
   void VisitSwitchStmt(const SwitchStmt *Node);
   void VisitWhileStmt(const WhileStmt *Node);
diff --git a/clang/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h
index 94b247a689c2d..78f8de42c5f2b 100644
--- a/clang/include/clang/Sema/ScopeInfo.h
+++ b/clang/include/clang/Sema/ScopeInfo.h
@@ -124,6 +124,9 @@ class FunctionScopeInfo {
   /// Whether this function contains any indirect gotos.
   bool HasIndirectGoto : 1;
 
+  /// Whether this function contains any labeled break or continue statements.
+  bool HasLabeledBreakOrContinue : 1;
+
   /// Whether this function contains any statement marked with
   /// \c [[clang::musttail]].
   bool HasMustTail : 1;
@@ -391,7 +394,8 @@ class FunctionScopeInfo {
 public:
   FunctionScopeInfo(DiagnosticsEngine &Diag)
       : Kind(SK_Function), HasBranchProtectedScope(false),
-        HasBranchIntoScope(false), HasIndirectGoto(false), HasMustTail(false),
+        HasBranchIntoScope(false), HasIndirectGoto(false),
+        HasLabeledBreakOrContinue(false), HasMustTail(false),
         HasDroppedStmt(false), HasOMPDeclareReductionCombiner(false),
         HasFallthroughStmt(false), UsesFPIntrin(false),
         HasPotentialAvailabilityViolations(false), ObjCShouldCallSuper(false),
@@ -436,6 +440,10 @@ class FunctionScopeInfo {
     HasBranchIntoScope = true;
   }
 
+  void setHasLabeledBreakOrContinue() {
+    HasLabeledBreakOrContinue = true;
+  }
+
   void setHasBranchProtectedScope() {
     HasBranchProtectedScope = true;
   }
@@ -485,8 +493,9 @@ class FunctionScopeInfo {
   }
 
   bool NeedsScopeChecking() const {
-    return !HasDroppedStmt && (HasIndirectGoto || HasMustTail ||
-                               (HasBranchProtectedScope && HasBranchIntoScope));
+    return !HasDroppedStmt &&
+           (HasIndirectGoto || HasMustTail || HasLabeledBreakOrContinue ||
+            (HasBranchProtectedScope && HasBranchIntoScope));
   }
 
   // Add a block introduced in this function.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5211373367677..7db36a64679d3 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -11033,8 +11033,10 @@ class Sema final : public SemaBase {
                            LabelDecl *TheDecl);
   StmtResult ActOnIndirectGotoStmt(SourceLocation GotoLoc,
                                    SourceLocation StarLoc, Expr *DestExp);
-  StmtResult ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope);
-  StmtResult ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope);
+  StmtResult ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
+                               LabelDecl *Label, SourceLocation LabelLoc);
+  StmtResult ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope,
+                            LabelDecl *Label, SourceLocation LabelLoc);
 
   struct NamedReturnInfo {
     const VarDecl *Candidate;
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 4295b7dc3dfef..79583b68b4112 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -7415,12 +7415,12 @@ static ExpectedStmt ImportLoopControlStmt(ASTNodeImporter &NodeImporter,
   auto ToLabelLoc = S->isLabeled()
                         ? NodeImporter.importChecked(Err, S->getLabelLoc())
                         : SourceLocation();
-  auto ToStmt = S->isLabeled()
-                    ? NodeImporter.importChecked(Err, S->getLabeledStmt())
+  auto ToDecl = S->isLabeled()
+                    ? NodeImporter.importChecked(Err, S->getLabelDecl())
                     : nullptr;
   if (Err)
     return std::move(Err);
-  return new (Importer.getToContext()) StmtClass(ToLoc, ToLabelLoc, ToStmt);
+  return new (Importer.getToContext()) StmtClass(ToLoc, ToLabelLoc, ToDecl);
 }
 
 ExpectedStmt ASTNodeImporter::VisitContinueStmt(ContinueStmt *S) {
diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp
index 4fc4a99ad2405..030da50223f7b 100644
--- a/clang/lib/AST/Stmt.cpp
+++ b/clang/lib/AST/Stmt.cpp
@@ -1482,3 +1482,10 @@ bool CapturedStmt::capturesVariable(const VarDecl *Var) const {
 
   return false;
 }
+
+Stmt *LoopControlStmt::getLabelTarget() const {
+  Stmt *Target = TargetLabel->getStmt();
+  while (isa_and_present<LabelStmt>(Target))
+    Target = cast<LabelStmt>(Target)->getSubStmt();
+  return Target;
+}
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 6b524cfcd2d71..c2d51f986ff80 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -1413,6 +1413,26 @@ static void dumpBasePath(raw_ostream &OS, const CastExpr *Node) {
   OS << ')';
 }
 
+void TextNodeDumper::VisitLoopControlStmt(const LoopControlStmt *Node) {
+  if (!Node->isLabeled())
+    return;
+
+  OS << " '" << Node->getLabelDecl()->getIdentifier()->getName() << "' (";
+
+  auto *Target = Node->getLabelTarget();
+  if (!Target) {
+    ColorScope Color(OS, ShowColors, NullColor);
+    OS << "<<<NULL>>>";
+  } else {
+    {
+      ColorScope Color(OS, ShowColors, StmtColor);
+      OS << Target->getStmtClassName();
+    }
+    dumpPointer(Target);
+  }
+  OS << ")";
+}
+
 void TextNodeDumper::VisitIfStmt(const IfStmt *Node) {
   if (Node->hasInitStorage())
     OS << " has_init";
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 148dc6e5bc0b8..4da057ad0ae81 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -2290,24 +2290,26 @@ StmtResult Parser::ParseGotoStatement() {
 
 StmtResult Parser::ParseContinueStatement() {
   SourceLocation ContinueLoc = ConsumeToken();  // eat the 'continue'.
-  if (!Tok.is(tok::identifier))
-    return Actions.ActOnContinueStmt(ContinueLoc, getCurScope());
-
-  StmtResult Res = Actions.ActOnLabelledContinueStmt(
-      ContinueLoc, Tok.getIdentifierInfo(), Tok.getLocation());
-  ConsumeToken();
-  return Res;
+  SourceLocation LabelLoc;
+  LabelDecl *Target = nullptr;
+  if (Tok.is(tok::identifier)) {
+    Target =
+        Actions.LookupOrCreateLabel(Tok.getIdentifierInfo(), Tok.getLocation());
+    LabelLoc = ConsumeToken();
+  }
+  return Actions.ActOnContinueStmt(ContinueLoc, getCurScope(), Target, LabelLoc);
 }
 
 StmtResult Parser::ParseBreakStatement() {
   SourceLocation BreakLoc = ConsumeToken();  // eat the 'break'.
-  if (!Tok.is(tok::identifier))
-    return Actions.ActOnBreakStmt(BreakLoc, getCurScope());
-
-  StmtResult Res = Actions.ActOnLabelledBreakStmt(
-      BreakLoc, Tok.getIdentifierInfo(), Tok.getLocation());
-  ConsumeToken();
-  return Res;
+  SourceLocation LabelLoc;
+  LabelDecl *Target = nullptr;
+  if (Tok.is(tok::identifier)) {
+    Target =
+        Actions.LookupOrCreateLabel(Tok.getIdentifierInfo(), Tok.getLocation());
+    LabelLoc = ConsumeToken();
+  }
+  return Actions.ActOnBreakStmt(BreakLoc, getCurScope(), Target, LabelLoc);
 }
 
 StmtResult Parser::ParseReturnStatement() {
diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp
index 36704c3826dfd..6a00eda39a7ff 100644
--- a/clang/lib/Sema/JumpDiagnostics.cpp
+++ b/clang/lib/Sema/JumpDiagnostics.cpp
@@ -84,6 +84,7 @@ class JumpScopeChecker {
                              unsigned &ParentScope);
   void BuildScopeInformation(CompoundLiteralExpr *CLE, unsigned &ParentScope);
   void BuildScopeInformation(Stmt *S, unsigned &origParentScope);
+  void BuildScopeInformationForLoopOrSwitch(Stmt *S, Stmt *Body, unsigned& ParentScope);
 
   void VerifyJumps();
   void VerifyIndirectJumps();
@@ -296,6 +297,28 @@ void JumpScopeChecker::BuildScopeInformation(CompoundLiteralExpr *CLE,
   ParentScope = Scopes.size() - 1;
 }
 
+/// Build scope information for an iteration or 'switch' statement.
+///
+/// This pushes a new scope for the body of the loop so we can check if any
+/// labeled break/continue statements that target this loop are actually
+/// inside it.
+///
+/// The loop condition etc. are *not* included in it though; this forbids doing
+/// horrible things such as 'x: while (({ continue x; })) {}'.
+void JumpScopeChecker::BuildScopeInformationForLoopOrSwitch(
+    Stmt *S, Stmt *Body, unsigned &ParentScope) {
+  for (Stmt *Child : S->children()) {
+    if (!Child || Child == Body)
+      continue;
+    BuildScopeInformation(Child, ParentScope);
+  }
+
+  unsigned NewParentScope = Scopes.size();
+  Scopes.push_back(GotoScope(ParentScope, 0, 0, S->getBeginLoc()));
+  LabelAndGotoScopes[S] = NewParentScope;
+  BuildScopeInformation(Body, NewParentScope);
+}
+
 /// BuildScopeInformation - The statements from CI to CE are known to form a
 /// coherent VLA scope with a specified parent node.  Walk through the
 /// statements, adding any labels or gotos to LabelAndGotoScopes and recursively
@@ -339,18 +362,11 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
     IndirectJumps.push_back(S);
     break;
 
-  case Stmt::SwitchStmtClass:
-    // Evaluate the C++17 init stmt and condition variable
-    // before entering the scope of the switch statement.
-    if (Stmt *Init = cast<SwitchStmt>(S)->getInit()) {
-      BuildScopeInformation(Init, ParentScope);
-      ++StmtsToSkip;
-    }
-    if (VarDecl *Var = cast<SwitchStmt>(S)->getConditionVariable()) {
-      BuildScopeInformation(Var, ParentScope);
-      ++StmtsToSkip;
-    }
-    goto RecordJumpScope;
+  case Stmt::SwitchStmtClass: {
+    BuildScopeInformationForLoopOrSwitch(S, cast<SwitchStmt>(S)->getBody(), ParentScope);
+    Jumps.push_back(S);
+    return;
+  }
 
   case Stmt::GCCAsmStmtClass:
     if (!cast<GCCAsmStmt>(S)->isAsmGoto())
@@ -365,6 +381,29 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
     Jumps.push_back(S);
     break;
 
+  case Stmt::BreakStmtClass:
+  case Stmt::ContinueStmtClass:
+    if (cast<LoopControlStmt>(S)->isLabeled()) goto RecordJumpScope;
+    break;
+
+  case Stmt::WhileStmtClass: {
+    BuildScopeInformationForLoopOrSwitch(S, cast<WhileStmt>(S)->getBody(),
+                                         ParentScope);
+    return;
+  }
+
+  case Stmt::DoStmtClass: {
+    BuildScopeInformationForLoopOrSwitch(S, cast<DoStmt>(S)->getBody(),
+                                         ParentScope);
+    return;
+  }
+
+  case Stmt::ForStmtClass: {
+    BuildScopeInformationForLoopOrSwitch(S, cast<ForStmt>(S)->getBody(),
+                                         ParentScope);
+    return;
+  }
+
   case Stmt::IfStmtClass: {
     IfStmt *IS = cast<IfStmt>(S);
     if (!(IS->isConstexpr() || IS->isConsteval() ||
@@ -721,6 +760,34 @@ void JumpScopeChecker::VerifyJumps() {
       continue;
     }
 
+    // Any labeled break/continue statements must also be handled here.
+    if (auto *L = dyn_cast<LoopControlStmt>(Jump)) {
+      assert(L->isLabeled() && "expected labeled break/continue");
+      bool IsContinue = isa<ContinueStmt>(L);
+
+      // The jump target didn't exist yet when we parsed the break/continue, so
+      // verify it now. Note that if the target is null, then Sema will have
+      // already complained about an undeclared label.
+      Stmt *Target = L->getLabelTarget();
+      if (!Target)
+        continue;
+
+      if (!isa<SwitchStmt, WhileStmt, ForStmt, DoStmt, CXXForRangeStmt,
+               ObjCForCollectionStmt>(Target)) {
+        S.Diag(L->getLabelLoc(), diag::err_break_continue_label_not_found)
+          << !IsContinue;
+        continue;
+      }
+
+      if (IsContinue && isa<SwitchStmt>(Target)) {
+        S.Diag(L->getLabelLoc(), diag::err_continue_switch);
+        continue;
+      }
+
+      CheckJump(L, Target, L->getKwLoc(), 0, 0, 0);
+      continue;
+    }
+
     SwitchStmt *SS = cast<SwitchStmt>(Jump);
     for (SwitchCase *SC = SS->getSwitchCaseList(); SC;
          SC = SC->getNextSwitchCase()) {
@@ -973,7 +1040,7 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
   if (FromScope == ToScope) return;
 
   // Warn on gotos out of __finally blocks.
-  if (isa<GotoStmt>(From) || isa<IndirectGotoStmt>(From)) {
+  if (isa<GotoStmt, IndirectGotoStmt, LoopControlStmt>(From)) {
     // If FromScope > ToScope, FromScope is more nested and the jump goes to a
     // less nested scope.  Check if it crosses a __finally along the way.
     for (unsigned I = FromScope; I > ToScope; I = Scopes[I].ParentScope) {
@@ -999,6 +1066,13 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
   // It's okay to jump out from a nested scope.
   if (CommonScope == ToScope) return;
 
+  // Error if we're trying to break/continue out of a non-enclosing statement.
+  if (auto L = dyn_cast<LoopControlStmt>(From)) {
+    S.Diag(L->getLabelLoc(), diag::err_break_continue_label_not_found)
+          << isa<BreakStmt>(L);
+    return;
+  }
+
   // Pull out (and reverse) any scopes we might need to diagnose skipping.
   SmallVector<unsigned, 10> ToScopesCompat;
   SmallVector<unsigned, 10> ToScopesError;
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 18fabd982f486..d31236bdd5828 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -3282,8 +3282,15 @@ static void CheckJumpOutOfSEHFinally(Sema &S, SourceLocation Loc,
   }
 }
 
-StmtResult
-Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope) {
+StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
+                                   LabelDecl *Target,
+                                   SourceLocation LabelLoc) {
+  // We can only check this after we're done parsing label that this targets.
+  if (Target) {
+    getCurFunction()->setHasLabeledBreakOrContinue();
+    return new (Context) ContinueStmt(ContinueLoc, LabelLoc, Target);
+  }
+
   Scope *S = CurScope->getContinueParent();
   if (!S) {
     // C99 6.8.6.2p1: A break shall appear only in or as a loop body.
@@ -3309,8 +3316,14 @@ Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope) {
   return new (Context) ContinueStmt(ContinueLoc);
 }
 
-StmtResult
-Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope) {
+StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope,
+                                LabelDecl *Target, SourceLocation LabelLoc) {
+  // We can only check this after we're done parsing label that this targets.
+  if (Target) {
+    getCurFunction()->setHasLabeledBreakOrContinue();
+    return new (Context) BreakStmt(BreakLoc, LabelLoc, Target);
+  }
+
   Scope *S = CurScope->getBreakParent();
   if (!S) {
     // C99 6.8.6.3p1: A break shall appear only in or as a switch/loop body.
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 46d93fe626f54..0e16619fa188e 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -116,7 +116,6 @@ namespace clang {
                                    TemplateArgumentLoc *ArgsLocArray,
                                    unsigned NumTemplateArgs);
 
-    void VisitLoopControlStmt(LoopControlStmt *S);
     void VisitStmt(Stmt *S);
 #define STMT(Type, Base) \
     void Visit##Type(Type *);
@@ -325,7 +324,7 @@ void ASTStmtReader::VisitLoopControlStmt(LoopControlStmt *S) {
   VisitStmt(S);
   S->setKwLoc(readSourceLocation());
   if (Record.readBool()) {
-    S->setLabeledStmt(Record.readSubStmt());
+    S->setLabelDecl(readDeclAs<LabelDecl>());
     S->setLabelLoc(readSourceLocation());
   }
 }
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 57832871b970a..9baaa21121ce7 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -109,7 +109,6 @@ namespace clang {
     void AddTemplateKWAndArgsInfo(const ASTTemplateKWAndArgsInfo &ArgInfo,
                                   const TemplateArgumentLoc *Args);
 
-    void VisitLoopControlStmt(LoopControlStmt *S);
     void VisitStmt(Stmt *S);
 #define STMT(Type, Base) \
     void Visit##Type(Type *);
@@ -316,7 +315,7 @@ void ASTStmtWriter::VisitLoopControlStmt(LoopControlStmt *S) {
   Record.AddSourceLocation(S->getKwLoc());
   Record.push_back(S->isLabeled());
   if (S->isLabeled()) {
-    Record.AddStmt(S->getLabeledStmt());
+    Record.AddDeclRef(S->getLabelDecl());
     Record.AddSourceLocation(S->getLabelLoc());
   }
 }
diff --git a/clang/test/Sema/labeled-break-continue.c b/clang/test/Sema/labeled-break-continue.c
new file mode 100644
index 0000000000000..c5580aab607ad
--- /dev/null
+++ b/clang/test/Sema/labeled-break-continue.c
@@ -0,0 +1,152 @@
+// RUN: %clang_cc1 -std=c2y -verify -fsyntax-only -fblocks %s
+
+void f1() {
+  l1: while (true) {
+    break l1;
+    continue l1;
+  }
+
+  l2: for (;;) {
+    break l2;
+    continue l2;
+  }
+
+  l3: do {
+    break l3;
+    continue l3;
+  } while (true);
+
+  l4: switch (1) {
+    case 1:
+      break l4;
+  }
+}
+
+void f2() {
+  l1:;
+  break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+  continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
+
+  l2: while (true) {
+    break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+    continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
+  }
+
+  while (true) {
+    break l2; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+    continue l2; // expected-error {{'continue' label does not name an enclosing loop}}
+  }
+}
+
+void f3() {
+  a: b: c: d: while (true) {
+    break a;
+    break b;
+    break c;
+    break d;
+
+    continue a;
+    continue b;
+    continue c;
+    continue d;
+
+    e: while (true) {
+      break a;
+      break b;
+      break c;
+      break d;
+      break e;
+
+      continue a;
+      continue b;
+      continue c;
+      continue d;
+      continue e;
+    }
+
+    break e; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+    continue e; // expected-error {{'continue' label does not name an enclosing loop}}
+  }
+}
+
+void f4() {
+  a: switch (1) {
+    case 1: {
+      continue a; // expected-error {{label of 'continue' refers to a switch statement}}
+    }
+  }
+}
+
+void f5() {
+  a: {
+    break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+  }
+
+  b: {
+    while (true)
+      break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+  }
+}
+
+void f6() {
+  a: while (({
+    break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+    continue a; // expected-error {{'continue' label does not name an enclosing loop}}
+    1;
+  })) {}
+
+  b: for (
+    int x = ({
+      break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+      continue b; // expected-error {{'continue' label does not name an enclosing loop}}
+      1;
+    });
+    ({
+      break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+      continue b; // expected-error {{'continue' label does not name an enclosing loop}}
+      1;
+    });
+    (void) ({
+      break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+      continue b; // expected-error {{'continue' label does not name an enclosing loop}}
+      1;
+    })
+  ) {}
+
+  c: do {} while (({
+    break c; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+    continue c; // expected-error {{'continue' label does not name an enclosing loop}}
+    1;
+  }));
+
+  d: switch (({
+    break d; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+    continue d; // expected-error {{label of 'continue' refers to a switch statement}}
+    1;
+  })) { case 1:; }
+}
+
+void f7() {
+  a: b: while (true) {
+    (void) ^{
+      break a; // expected-error {{use of undeclared label 'a'}}
+      continue b; // expected-error {{use of undeclared label 'b'}}
+    };
+  }
+
+  while (true) {
+    break c; // expected-error {{use of undeclared label 'c'}}
+    continue d; // expected-error {{use of undeclared label 'd'}}
+  }
+}
+
+
+// TODO:
+//  - CodeGen
+//  - Compat diags
+//  - Add tests for all the stuff that ActOnBreakStmt normally checks (SEH __finally etc.)
+//  - C++ support: range-based for loops
+//  - ObjC support: 'for in' loops
+//  - Constant evaluation
+//  - Template instantiation (need to get the instantiated LabelDecl)
+//  - Tests for TextNodeDumper / JSONNodeDumper / AST printing

>From a2e7bc23da9608603582b987b031bf94f7d2c2a8 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 10:47:49 +0200
Subject: [PATCH 05/19] Remove unused variable

---
 clang/lib/Sema/JumpDiagnostics.cpp | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp
index 6a00eda39a7ff..2e70497a7ac76 100644
--- a/clang/lib/Sema/JumpDiagnostics.cpp
+++ b/clang/lib/Sema/JumpDiagnostics.cpp
@@ -332,8 +332,6 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
   unsigned &ParentScope = ((isa<Expr>(S) && !isa<StmtExpr>(S))
                             ? origParentScope : independentParentScope);
 
-  unsigned StmtsToSkip = 0u;
-
   // If we found a label, remember that it is in ParentScope scope.
   switch (S->getStmtClass()) {
   case Stmt::AddrLabelExprClass:
@@ -679,10 +677,6 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
   for (Stmt *SubStmt : S->children()) {
     if (!SubStmt)
         continue;
-    if (StmtsToSkip) {
-      --StmtsToSkip;
-      continue;
-    }
 
     // Cases, labels, attributes, and defaults aren't "scope parents".  It's also
     // important to handle these iteratively instead of recursively in

>From 42b1041ae254c58523f366aa00608ab7939a4d33 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 11:32:44 +0200
Subject: [PATCH 06/19] CodeGen

---
 clang/lib/CodeGen/CGObjC.cpp                |   2 +-
 clang/lib/CodeGen/CGStmt.cpp                |  27 +-
 clang/lib/CodeGen/CGStmtOpenMP.cpp          |   6 +-
 clang/lib/CodeGen/CodeGenFunction.h         |   8 +-
 clang/test/CodeGen/labeled-break-continue.c | 281 ++++++++++++++++++++
 clang/test/Sema/labeled-break-continue.c    |   1 -
 6 files changed, 311 insertions(+), 14 deletions(-)
 create mode 100644 clang/test/CodeGen/labeled-break-continue.c

diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp
index 24b6ce7c1c70d..b1cb83547f313 100644
--- a/clang/lib/CodeGen/CGObjC.cpp
+++ b/clang/lib/CodeGen/CGObjC.cpp
@@ -2056,7 +2056,7 @@ void CodeGenFunction::EmitObjCForCollectionStmt(const ObjCForCollectionStmt &S){
     EmitAutoVarCleanups(variable);
 
   // Perform the loop body, setting up break and continue labels.
-  BreakContinueStack.push_back(BreakContinue(LoopEnd, AfterBody));
+  BreakContinueStack.push_back(BreakContinue(S, LoopEnd, AfterBody));
   {
     RunCleanupsScope Scope(*this);
     EmitStmt(S.getBody());
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 1a8c6f015bda1..ba1ed65f04063 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -1088,7 +1088,7 @@ void CodeGenFunction::EmitWhileStmt(const WhileStmt &S,
   JumpDest LoopExit = getJumpDestInCurrentScope("while.end");
 
   // Store the blocks to use for break and continue.
-  BreakContinueStack.push_back(BreakContinue(LoopExit, LoopHeader));
+  BreakContinueStack.push_back(BreakContinue(S, LoopExit, LoopHeader));
 
   // C++ [stmt.while]p2:
   //   When the condition of a while statement is a declaration, the
@@ -1207,7 +1207,7 @@ void CodeGenFunction::EmitDoStmt(const DoStmt &S,
   uint64_t ParentCount = getCurrentProfileCount();
 
   // Store the blocks to use for break and continue.
-  BreakContinueStack.push_back(BreakContinue(LoopExit, LoopCond));
+  BreakContinueStack.push_back(BreakContinue(S, LoopExit, LoopCond));
 
   // Emit the body of the loop.
   llvm::BasicBlock *LoopBody = createBasicBlock("do.body");
@@ -1328,7 +1328,7 @@ void CodeGenFunction::EmitForStmt(const ForStmt &S,
     Continue = CondDest;
   else if (!S.getConditionVariable())
     Continue = getJumpDestInCurrentScope("for.inc");
-  BreakContinueStack.push_back(BreakContinue(LoopExit, Continue));
+  BreakContinueStack.push_back(BreakContinue(S, LoopExit, Continue));
 
   if (S.getCond()) {
     // If the for statement has a condition scope, emit the local variable
@@ -1510,7 +1510,7 @@ CodeGenFunction::EmitCXXForRangeStmt(const CXXForRangeStmt &S,
   JumpDest Continue = getJumpDestInCurrentScope("for.inc");
 
   // Store the blocks to use for break and continue.
-  BreakContinueStack.push_back(BreakContinue(LoopExit, Continue));
+  BreakContinueStack.push_back(BreakContinue(S, LoopExit, Continue));
 
   {
     // Create a separate cleanup scope for the loop variable and body.
@@ -1732,6 +1732,19 @@ void CodeGenFunction::EmitDeclStmt(const DeclStmt &S) {
     EmitDecl(*I, /*EvaluateConditionDecl=*/true);
 }
 
+auto CodeGenFunction::GetDestForLoopControlStmt(const LoopControlStmt& S) -> const BreakContinue* {
+  if (!S.isLabeled())
+    return &BreakContinueStack.back();
+
+  Stmt *LoopOrSwitch = S.getLabelTarget();
+  assert(LoopOrSwitch && "break/continue target not set?");
+  for (const BreakContinue& BC : llvm::reverse(BreakContinueStack))
+    if (BC.LoopOrSwitch == LoopOrSwitch)
+      return &BC;
+
+  llvm_unreachable("break/continue target not found");
+}
+
 void CodeGenFunction::EmitBreakStmt(const BreakStmt &S) {
   assert(!BreakContinueStack.empty() && "break stmt not in a loop or switch!");
 
@@ -1742,7 +1755,7 @@ void CodeGenFunction::EmitBreakStmt(const BreakStmt &S) {
     EmitStopPoint(&S);
 
   ApplyAtomGroup Grp(getDebugInfo());
-  EmitBranchThroughCleanup(BreakContinueStack.back().BreakBlock);
+  EmitBranchThroughCleanup(GetDestForLoopControlStmt(S)->BreakBlock);
 }
 
 void CodeGenFunction::EmitContinueStmt(const ContinueStmt &S) {
@@ -1755,7 +1768,7 @@ void CodeGenFunction::EmitContinueStmt(const ContinueStmt &S) {
     EmitStopPoint(&S);
 
   ApplyAtomGroup Grp(getDebugInfo());
-  EmitBranchThroughCleanup(BreakContinueStack.back().ContinueBlock);
+  EmitBranchThroughCleanup(GetDestForLoopControlStmt(S)->ContinueBlock);
 }
 
 /// EmitCaseStmtRange - If case statement range is not too big then
@@ -2384,7 +2397,7 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) {
   if (!BreakContinueStack.empty())
     OuterContinue = BreakContinueStack.back().ContinueBlock;
 
-  BreakContinueStack.push_back(BreakContinue(SwitchExit, OuterContinue));
+  BreakContinueStack.push_back(BreakContinue(S, SwitchExit, OuterContinue));
 
   // Emit switch body.
   EmitStmt(S.getBody());
diff --git a/clang/lib/CodeGen/CGStmtOpenMP.cpp b/clang/lib/CodeGen/CGStmtOpenMP.cpp
index 5822e0f6db89a..dcdd2126c3acd 100644
--- a/clang/lib/CodeGen/CGStmtOpenMP.cpp
+++ b/clang/lib/CodeGen/CGStmtOpenMP.cpp
@@ -1969,7 +1969,7 @@ void CodeGenFunction::EmitOMPLoopBody(const OMPLoopDirective &D,
 
   // On a continue in the body, jump to the end.
   JumpDest Continue = getJumpDestInCurrentScope("omp.body.continue");
-  BreakContinueStack.push_back(BreakContinue(LoopExit, Continue));
+  BreakContinueStack.push_back(BreakContinue(D, LoopExit, Continue));
   for (const Expr *E : D.finals_conditions()) {
     if (!E)
       continue;
@@ -2198,7 +2198,7 @@ void CodeGenFunction::EmitOMPInnerLoop(
 
   // Create a block for the increment.
   JumpDest Continue = getJumpDestInCurrentScope("omp.inner.for.inc");
-  BreakContinueStack.push_back(BreakContinue(LoopExit, Continue));
+  BreakContinueStack.push_back(BreakContinue(S, LoopExit, Continue));
 
   BodyGen(*this);
 
@@ -3043,7 +3043,7 @@ void CodeGenFunction::EmitOMPOuterLoop(
 
   // Create a block for the increment.
   JumpDest Continue = getJumpDestInCurrentScope("omp.dispatch.inc");
-  BreakContinueStack.push_back(BreakContinue(LoopExit, Continue));
+  BreakContinueStack.push_back(BreakContinue(S, LoopExit, Continue));
 
   OpenMPDirectiveKind EKind = getEffectiveDirectiveKind(S);
   emitCommonSimdLoop(
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 6c32c98cec011..c16581d064048 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1553,9 +1553,11 @@ class CodeGenFunction : public CodeGenTypeCache {
   // BreakContinueStack - This keeps track of where break and continue
   // statements should jump to.
   struct BreakContinue {
-    BreakContinue(JumpDest Break, JumpDest Continue)
-        : BreakBlock(Break), ContinueBlock(Continue) {}
+    BreakContinue(const Stmt &LoopOrSwitch, JumpDest Break, JumpDest Continue)
+        : LoopOrSwitch(&LoopOrSwitch), BreakBlock(Break),
+          ContinueBlock(Continue) {}
 
+    const Stmt *LoopOrSwitch;
     JumpDest BreakBlock;
     JumpDest ContinueBlock;
   };
@@ -3608,6 +3610,8 @@ class CodeGenFunction : public CodeGenTypeCache {
   void EmitCaseStmtRange(const CaseStmt &S, ArrayRef<const Attr *> Attrs);
   void EmitAsmStmt(const AsmStmt &S);
 
+  const BreakContinue *GetDestForLoopControlStmt(const LoopControlStmt& S);
+
   void EmitObjCForCollectionStmt(const ObjCForCollectionStmt &S);
   void EmitObjCAtTryStmt(const ObjCAtTryStmt &S);
   void EmitObjCAtThrowStmt(const ObjCAtThrowStmt &S);
diff --git a/clang/test/CodeGen/labeled-break-continue.c b/clang/test/CodeGen/labeled-break-continue.c
new file mode 100644
index 0000000000000..f307a1bd79ab8
--- /dev/null
+++ b/clang/test/CodeGen/labeled-break-continue.c
@@ -0,0 +1,281 @@
+// RUN: %clang_cc1 -std=c2y -triple x86_64-unknown-linux -emit-llvm -o - %s | FileCheck %s
+
+bool g1();
+bool g2();
+bool g3();
+
+// CHECK-LABEL: define {{.*}} void @f1()
+// CHECK: entry:
+// CHECK:   br label %l1
+// CHECK: l1:
+// CHECK:   br label %while.body
+// CHECK: while.body:
+// CHECK:   br label %while.end
+// CHECK: while.end:
+// CHECK:   br label %l2
+// CHECK: l2:
+// CHECK:   br label %while.body1
+// CHECK: while.body1:
+// CHECK:   br label %while.body1
+void f1() {
+  l1: while (true) break l1;
+  l2: while (true) continue l2;
+}
+
+// CHECK-LABEL: define {{.*}} void @f2()
+// CHECK: entry:
+// CHECK:   br label %l1
+// CHECK: l1:
+// CHECK:   br label %for.cond
+// CHECK: for.cond:
+// CHECK:   br label %for.end
+// CHECK: for.end:
+// CHECK:   br label %l2
+// CHECK: l2:
+// CHECK:   br label %for.cond1
+// CHECK: for.cond1:
+// CHECK:   br label %for.cond1
+void f2() {
+  l1: for (;;) break l1;
+  l2: for (;;) continue l2;
+}
+
+// CHECK-LABEL: define {{.*}} void @f3()
+// CHECK: entry:
+// CHECK:   br label %l1
+// CHECK: l1:
+// CHECK:   br label %do.body
+// CHECK: do.body:
+// CHECK:   br label %do.end
+// CHECK: do.cond:
+// CHECK:   br i1 true, label %do.body, label %do.end
+// CHECK: do.end:
+// CHECK:   br label %l2
+// CHECK: l2:
+// CHECK:   br label %do.body1
+// CHECK: do.body1:
+// CHECK:   br label %do.cond2
+// CHECK: do.cond2:
+// CHECK:   br i1 true, label %do.body1, label %do.end3
+// CHECK: do.end3:
+// CHECK:   ret void
+void f3() {
+  l1: do { break l1; } while (true);
+  l2: do { continue l2; } while (true);
+}
+
+// CHECK-LABEL: define {{.*}} void @f4()
+// CHECK: entry:
+// CHECK:   br label %l1
+// CHECK: l1:
+// CHECK:   br label %while.cond
+// CHECK: while.cond:
+// CHECK:   %call = call {{.*}} i1 @g1()
+// CHECK:   br i1 %call, label %while.body, label %while.end14
+// CHECK: while.body:
+// CHECK:   br label %l2
+// CHECK: l2:
+// CHECK:   br label %while.cond1
+// CHECK: while.cond1:
+// CHECK:   %call2 = call {{.*}} i1 @g2()
+// CHECK:   br i1 %call2, label %while.body3, label %while.end
+// CHECK: while.body3:
+// CHECK:   %call4 = call {{.*}} i1 @g3()
+// CHECK:   br i1 %call4, label %if.then, label %if.end
+// CHECK: if.then:
+// CHECK:   br label %while.end14
+// CHECK: if.end:
+// CHECK:   %call5 = call {{.*}} i1 @g3()
+// CHECK:   br i1 %call5, label %if.then6, label %if.end7
+// CHECK: if.then6:
+// CHECK:   br label %while.end
+// CHECK: if.end7:
+// CHECK:   %call8 = call {{.*}} i1 @g3()
+// CHECK:   br i1 %call8, label %if.then9, label %if.end10
+// CHECK: if.then9:
+// CHECK:   br label %while.cond
+// CHECK: if.end10:
+// CHECK:   %call11 = call {{.*}} i1 @g3()
+// CHECK:   br i1 %call11, label %if.then12, label %if.end13
+// CHECK: if.then12:
+// CHECK:   br label %while.cond1
+// CHECK: if.end13:
+// CHECK:   br label %while.cond1
+// CHECK: while.end:
+// CHECK:   br label %while.cond
+// CHECK: while.end14:
+// CHECK:   ret void
+void f4() {
+  l1: while (g1()) {
+    l2: while (g2()) {
+      if (g3()) break l1;
+      if (g3()) break l2;
+      if (g3()) continue l1;
+      if (g3()) continue l2;
+    }
+  }
+}
+
+// CHECK-LABEL: define {{.*}} void @f5()
+// CHECK: entry:
+// CHECK:   br label %l1
+// CHECK: l1:
+// CHECK:   br label %while.cond
+// CHECK: while.cond:
+// CHECK:   %call = call {{.*}} i1 @g1()
+// CHECK:   br i1 %call, label %while.body, label %while.end
+// CHECK: while.body:
+// CHECK:   br label %l2
+// CHECK: l2:
+// CHECK:   %call1 = call {{.*}} i1 @g2()
+// CHECK:   %conv = zext i1 %call1 to i32
+// CHECK:   switch i32 %conv, label %sw.epilog [
+// CHECK:     i32 1, label %sw.bb
+// CHECK:     i32 2, label %sw.bb2
+// CHECK:     i32 3, label %sw.bb3
+// CHECK:   ]
+// CHECK: sw.bb:
+// CHECK:   br label %while.end
+// CHECK: sw.bb2:
+// CHECK:   br label %sw.epilog
+// CHECK: sw.bb3:
+// CHECK:   br label %while.cond
+// CHECK: sw.epilog:
+// CHECK:   br label %while.cond
+// CHECK: while.end:
+// CHECK:   ret void
+void f5() {
+  l1: while (g1()) {
+    l2: switch (g2()) {
+      case 1: break l1;
+      case 2: break l2;
+      case 3: continue l1;
+    }
+  }
+}
+
+// CHECK-LABEL: define {{.*}} void @f6()
+// CHECK: entry:
+// CHECK:   br label %l1
+// CHECK: l1:
+// CHECK:   br label %while.cond
+// CHECK: while.cond:
+// CHECK:   %call = call {{.*}} i1 @g1()
+// CHECK:   br i1 %call, label %while.body, label %while.end28
+// CHECK: while.body:
+// CHECK:   br label %l2
+// CHECK: l2:
+// CHECK:   br label %for.cond
+// CHECK: for.cond:
+// CHECK:   %call1 = call {{.*}} i1 @g1()
+// CHECK:   br i1 %call1, label %for.body, label %for.end
+// CHECK: for.body:
+// CHECK:   br label %l3
+// CHECK: l3:
+// CHECK:   br label %do.body
+// CHECK: do.body:
+// CHECK:   br label %l4
+// CHECK: l4:
+// CHECK:   br label %while.cond2
+// CHECK: while.cond2:
+// CHECK:   %call3 = call {{.*}} i1 @g1()
+// CHECK:   br i1 %call3, label %while.body4, label %while.end
+// CHECK: while.body4:
+// CHECK:   %call5 = call {{.*}} i1 @g2()
+// CHECK:   br i1 %call5, label %if.then, label %if.end
+// CHECK: if.then:
+// CHECK:   br label %while.end28
+// CHECK: if.end:
+// CHECK:   %call6 = call {{.*}} i1 @g2()
+// CHECK:   br i1 %call6, label %if.then7, label %if.end8
+// CHECK: if.then7:
+// CHECK:   br label %for.end
+// CHECK: if.end8:
+// CHECK:   %call9 = call {{.*}} i1 @g2()
+// CHECK:   br i1 %call9, label %if.then10, label %if.end11
+// CHECK: if.then10:
+// CHECK:   br label %do.end
+// CHECK: if.end11:
+// CHECK:   %call12 = call {{.*}} i1 @g2()
+// CHECK:   br i1 %call12, label %if.then13, label %if.end14
+// CHECK: if.then13:
+// CHECK:   br label %while.end
+// CHECK: if.end14:
+// CHECK:   %call15 = call {{.*}} i1 @g2()
+// CHECK:   br i1 %call15, label %if.then16, label %if.end17
+// CHECK: if.then16:
+// CHECK:   br label %while.cond
+// CHECK: if.end17:
+// CHECK:   %call18 = call {{.*}} i1 @g2()
+// CHECK:   br i1 %call18, label %if.then19, label %if.end20
+// CHECK: if.then19:
+// CHECK:   br label %for.cond
+// CHECK: if.end20:
+// CHECK:   %call21 = call {{.*}} i1 @g2()
+// CHECK:   br i1 %call21, label %if.then22, label %if.end23
+// CHECK: if.then22:
+// CHECK:   br label %do.cond
+// CHECK: if.end23:
+// CHECK:   %call24 = call {{.*}} i1 @g2()
+// CHECK:   br i1 %call24, label %if.then25, label %if.end26
+// CHECK: if.then25:
+// CHECK:   br label %while.cond2
+// CHECK: if.end26:
+// CHECK:   br label %while.cond2
+// CHECK: while.end:
+// CHECK:   br label %do.cond
+// CHECK: do.cond:
+// CHECK:   %call27 = call {{.*}} i1 @g1()
+// CHECK:   br i1 %call27, label %do.body, label %do.end
+// CHECK: do.end:
+// CHECK:   br label %for.cond
+// CHECK: for.end:
+// CHECK:   br label %while.cond
+// CHECK: while.end28:
+// CHECK:   ret void
+void f6() {
+  l1: while (g1()) {
+    l2: for (; g1();) {
+      l3: do {
+        l4: while (g1()) {
+          if (g2()) break l1;
+          if (g2()) break l2;
+          if (g2()) break l3;
+          if (g2()) break l4;
+          if (g2()) continue l1;
+          if (g2()) continue l2;
+          if (g2()) continue l3;
+          if (g2()) continue l4;
+        }
+      } while (g1());
+    }
+  }
+}
+
+// CHECK-LABEL: define {{.*}} void @f7()
+// CHECK: entry:
+// CHECK:   br label %loop
+// CHECK: loop:
+// CHECK:   br label %while.cond
+// CHECK: while.cond:
+// CHECK:   %call = call {{.*}} i1 @g1()
+// CHECK:   br i1 %call, label %while.body, label %while.end
+// CHECK: while.body:
+// CHECK:   %call1 = call {{.*}} i1 @g2()
+// CHECK:   %conv = zext i1 %call1 to i32
+// CHECK:   switch i32 %conv, label %sw.epilog [
+// CHECK:     i32 1, label %sw.bb
+// CHECK:   ]
+// CHECK: sw.bb:
+// CHECK:   br label %while.end
+// CHECK: sw.epilog:
+// CHECK:   br label %while.cond
+// CHECK: while.end:
+// CHECK:   ret void
+void f7() {
+  loop: while (g1()) {
+    switch (g2()) {
+      case 1: break loop;
+    }
+  }
+}
diff --git a/clang/test/Sema/labeled-break-continue.c b/clang/test/Sema/labeled-break-continue.c
index c5580aab607ad..8555612a28b63 100644
--- a/clang/test/Sema/labeled-break-continue.c
+++ b/clang/test/Sema/labeled-break-continue.c
@@ -142,7 +142,6 @@ void f7() {
 
 
 // TODO:
-//  - CodeGen
 //  - Compat diags
 //  - Add tests for all the stuff that ActOnBreakStmt normally checks (SEH __finally etc.)
 //  - C++ support: range-based for loops

>From 5a0965eeb52136b67b2acf8ce1acfed025ff35d7 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 12:08:17 +0200
Subject: [PATCH 07/19] Add compat diags

---
 .../clang/Basic/DiagnosticParseKinds.td       |  5 ++++
 clang/include/clang/Parse/Parser.h            |  2 ++
 clang/lib/Parse/ParseStmt.cpp                 | 27 ++++++++++---------
 clang/test/Parser/labeled-break-continue.c    | 10 +++++++
 clang/test/Sema/labeled-break-continue.c      |  1 -
 5 files changed, 32 insertions(+), 13 deletions(-)
 create mode 100644 clang/test/Parser/labeled-break-continue.c

diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 0042afccba2c8..8a124acfc771d 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -215,6 +215,11 @@ def warn_c23_compat_case_range : Warning<
   DefaultIgnore, InGroup<CPre2yCompat>;
 def ext_c2y_case_range : Extension<
   "case ranges are a C2y extension">, InGroup<C2y>;
+def warn_c2y_labeled_break_continue: Warning<
+  "labeled %select{'break'|'continue'}0 is incompatible with C standards before C2y">,
+  DefaultIgnore, InGroup<CPre2yCompat>;
+def ext_c2y_labeled_break_continue: Extension<
+  "labeled %select{'break'|'continue'}0 is a C2y extension">, InGroup<C2y>;
 
 // Generic errors.
 def err_expected_expression : Error<"expected expression">;
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index e9437e6d46366..35e3f0c1917ec 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7484,6 +7484,8 @@ class Parser : public CodeCompletionHandler {
   /// \endverbatim
   StmtResult ParseReturnStatement();
 
+  StmtResult ParseBreakOrContinueStatement(bool IsContinue);
+
   StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx,
                                  SourceLocation *TrailingElseLoc,
                                  ParsedAttributes &Attrs);
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 4da057ad0ae81..7f5599fbd577d 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -2288,28 +2288,31 @@ StmtResult Parser::ParseGotoStatement() {
   return Res;
 }
 
-StmtResult Parser::ParseContinueStatement() {
-  SourceLocation ContinueLoc = ConsumeToken();  // eat the 'continue'.
+StmtResult Parser::ParseBreakOrContinueStatement(bool IsContinue) {
+  SourceLocation KwLoc = ConsumeToken();  // Eat the keyword.
   SourceLocation LabelLoc;
   LabelDecl *Target = nullptr;
   if (Tok.is(tok::identifier)) {
     Target =
         Actions.LookupOrCreateLabel(Tok.getIdentifierInfo(), Tok.getLocation());
     LabelLoc = ConsumeToken();
+    Diag(LabelLoc, getLangOpts().C2y ? diag::warn_c2y_labeled_break_continue
+                                     : diag::ext_c2y_labeled_break_continue)
+        << IsContinue;
   }
-  return Actions.ActOnContinueStmt(ContinueLoc, getCurScope(), Target, LabelLoc);
+
+  if (IsContinue)
+    return Actions.ActOnContinueStmt(KwLoc, getCurScope(), Target, LabelLoc);
+  return Actions.ActOnBreakStmt(KwLoc, getCurScope(), Target, LabelLoc);
+}
+
+
+StmtResult Parser::ParseContinueStatement() {
+  return ParseBreakOrContinueStatement(/*IsContinue=*/true);
 }
 
 StmtResult Parser::ParseBreakStatement() {
-  SourceLocation BreakLoc = ConsumeToken();  // eat the 'break'.
-  SourceLocation LabelLoc;
-  LabelDecl *Target = nullptr;
-  if (Tok.is(tok::identifier)) {
-    Target =
-        Actions.LookupOrCreateLabel(Tok.getIdentifierInfo(), Tok.getLocation());
-    LabelLoc = ConsumeToken();
-  }
-  return Actions.ActOnBreakStmt(BreakLoc, getCurScope(), Target, LabelLoc);
+  return ParseBreakOrContinueStatement(/*IsContinue=*/false);
 }
 
 StmtResult Parser::ParseReturnStatement() {
diff --git a/clang/test/Parser/labeled-break-continue.c b/clang/test/Parser/labeled-break-continue.c
new file mode 100644
index 0000000000000..4d6ce83c2dcae
--- /dev/null
+++ b/clang/test/Parser/labeled-break-continue.c
@@ -0,0 +1,10 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c2y %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c23 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -x c++ %s
+// RUN: %clang_cc1 -fsyntax-only -verify=pedantic -std=c23 -pedantic %s
+// RUN: %clang_cc1 -fsyntax-only -verify=pedantic -x c++ -pedantic %s
+// expected-no-diagnostics
+
+void f() {
+  x: while (1) break x; // pedantic-warning {{labeled 'break' is a C2y extension}}
+}
diff --git a/clang/test/Sema/labeled-break-continue.c b/clang/test/Sema/labeled-break-continue.c
index 8555612a28b63..c4c32dfacd180 100644
--- a/clang/test/Sema/labeled-break-continue.c
+++ b/clang/test/Sema/labeled-break-continue.c
@@ -142,7 +142,6 @@ void f7() {
 
 
 // TODO:
-//  - Compat diags
 //  - Add tests for all the stuff that ActOnBreakStmt normally checks (SEH __finally etc.)
 //  - C++ support: range-based for loops
 //  - ObjC support: 'for in' loops

>From 76d56e153128fd95b78152901bbf57d3f47c4090 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 12:39:05 +0200
Subject: [PATCH 08/19] Basic C++ support

---
 clang/lib/Sema/JumpDiagnostics.cpp            |   6 +
 .../CodeGenCXX/labeled-break-continue.cpp     | 169 ++++++++++++++++++
 clang/test/Sema/labeled-break-continue.c      |   1 +
 clang/test/SemaCXX/labeled-break-continue.cpp |  51 ++++++
 4 files changed, 227 insertions(+)
 create mode 100644 clang/test/CodeGenCXX/labeled-break-continue.cpp
 create mode 100644 clang/test/SemaCXX/labeled-break-continue.cpp

diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp
index 2e70497a7ac76..879b5ecbf09ab 100644
--- a/clang/lib/Sema/JumpDiagnostics.cpp
+++ b/clang/lib/Sema/JumpDiagnostics.cpp
@@ -402,6 +402,12 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
     return;
   }
 
+  case Stmt::CXXForRangeStmtClass: {
+    BuildScopeInformationForLoopOrSwitch(S, cast<CXXForRangeStmt>(S)->getBody(),
+                                         ParentScope);
+    return;
+  }
+
   case Stmt::IfStmtClass: {
     IfStmt *IS = cast<IfStmt>(S);
     if (!(IS->isConstexpr() || IS->isConsteval() ||
diff --git a/clang/test/CodeGenCXX/labeled-break-continue.cpp b/clang/test/CodeGenCXX/labeled-break-continue.cpp
new file mode 100644
index 0000000000000..bf066ce4eacda
--- /dev/null
+++ b/clang/test/CodeGenCXX/labeled-break-continue.cpp
@@ -0,0 +1,169 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c++20 -emit-llvm -o - %s | FileCheck %s
+
+static int a[10]{};
+struct NonTrivialDestructor {
+  ~NonTrivialDestructor();
+};
+
+bool g(int);
+bool h();
+
+// CHECK-LABEL: define {{.*}} void @_Z2f1v()
+// CHECK: entry:
+// CHECK:   %__range1 = alloca ptr, align 8
+// CHECK:   %__begin1 = alloca ptr, align 8
+// CHECK:   %__end1 = alloca ptr, align 8
+// CHECK:   %i = alloca i32, align 4
+// CHECK:   br label %x
+// CHECK: x:
+// CHECK:   store ptr @_ZL1a, ptr %__range1, align 8
+// CHECK:   store ptr @_ZL1a, ptr %__begin1, align 8
+// CHECK:   store ptr getelementptr inbounds (i32, ptr @_ZL1a, i64 10), ptr %__end1, align 8
+// CHECK:   br label %for.cond
+// CHECK: for.cond:
+// CHECK:   %0 = load ptr, ptr %__begin1, align 8
+// CHECK:   %1 = load ptr, ptr %__end1, align 8
+// CHECK:   %cmp = icmp ne ptr %0, %1
+// CHECK:   br i1 %cmp, label %for.body, label %for.end
+// CHECK: for.body:
+// CHECK:   %2 = load ptr, ptr %__begin1, align 8
+// CHECK:   %3 = load i32, ptr %2, align 4
+// CHECK:   store i32 %3, ptr %i, align 4
+// CHECK:   %4 = load i32, ptr %i, align 4
+// CHECK:   %call = call {{.*}} i1 @_Z1gi(i32 {{.*}} %4)
+// CHECK:   br i1 %call, label %if.then, label %if.end
+// CHECK: if.then:
+// CHECK:   br label %for.end
+// CHECK: if.end:
+// CHECK:   %5 = load i32, ptr %i, align 4
+// CHECK:   %call1 = call {{.*}} i1 @_Z1gi(i32 {{.*}} %5)
+// CHECK:   br i1 %call1, label %if.then2, label %if.end3
+// CHECK: if.then2:
+// CHECK:   br label %for.inc
+// CHECK: if.end3:
+// CHECK:   br label %for.inc
+// CHECK: for.inc:
+// CHECK:   %6 = load ptr, ptr %__begin1, align 8
+// CHECK:   %incdec.ptr = getelementptr inbounds nuw i32, ptr %6, i32 1
+// CHECK:   store ptr %incdec.ptr, ptr %__begin1, align 8
+// CHECK:   br label %for.cond
+// CHECK: for.end:
+// CHECK:   ret void
+void f1() {
+  x: for (int i : a) {
+    if (g(i)) break x;
+    if (g(i)) continue x;
+  }
+}
+
+// CHECK-LABEL: define {{.*}} void @_Z2f2v()
+// CHECK: entry:
+// CHECK:   %n1 = alloca %struct.NonTrivialDestructor, align 1
+// CHECK:   %__range2 = alloca ptr, align 8
+// CHECK:   %__begin2 = alloca ptr, align 8
+// CHECK:   %__end2 = alloca ptr, align 8
+// CHECK:   %i = alloca i32, align 4
+// CHECK:   %n2 = alloca %struct.NonTrivialDestructor, align 1
+// CHECK:   %cleanup.dest.slot = alloca i32, align 4
+// CHECK:   %n3 = alloca %struct.NonTrivialDestructor, align 1
+// CHECK:   %n4 = alloca %struct.NonTrivialDestructor, align 1
+// CHECK:   br label %l1
+// CHECK: l1:
+// CHECK:   br label %while.cond
+// CHECK: while.cond:
+// CHECK:   %call = call {{.*}} i1 @_Z1gi(i32 {{.*}} 0)
+// CHECK:   br i1 %call, label %while.body, label %while.end
+// CHECK: while.body:
+// CHECK:   br label %l2
+// CHECK: l2:
+// CHECK:   store ptr @_ZL1a, ptr %__range2, align 8
+// CHECK:   store ptr @_ZL1a, ptr %__begin2, align 8
+// CHECK:   store ptr getelementptr inbounds (i32, ptr @_ZL1a, i64 10), ptr %__end2, align 8
+// CHECK:   br label %for.cond
+// CHECK: for.cond:
+// CHECK:   %0 = load ptr, ptr %__begin2, align 8
+// CHECK:   %1 = load ptr, ptr %__end2, align 8
+// CHECK:   %cmp = icmp ne ptr %0, %1
+// CHECK:   br i1 %cmp, label %for.body, label %for.end
+// CHECK: for.body:
+// CHECK:   %2 = load ptr, ptr %__begin2, align 8
+// CHECK:   %3 = load i32, ptr %2, align 4
+// CHECK:   store i32 %3, ptr %i, align 4
+// CHECK:   %4 = load i32, ptr %i, align 4
+// CHECK:   %call1 = call {{.*}} i1 @_Z1gi(i32 {{.*}} %4)
+// CHECK:   br i1 %call1, label %if.then, label %if.end
+// CHECK: if.then:
+// CHECK:   store i32 4, ptr %cleanup.dest.slot, align 4
+// CHECK:   br label %cleanup
+// CHECK: if.end:
+// CHECK:   %5 = load i32, ptr %i, align 4
+// CHECK:   %call2 = call {{.*}} i1 @_Z1gi(i32 {{.*}} %5)
+// CHECK:   br i1 %call2, label %if.then3, label %if.end4
+// CHECK: if.then3:
+// CHECK:   store i32 3, ptr %cleanup.dest.slot, align 4
+// CHECK:   br label %cleanup
+// CHECK: if.end4:
+// CHECK:   %6 = load i32, ptr %i, align 4
+// CHECK:   %call5 = call {{.*}} i1 @_Z1gi(i32 {{.*}} %6)
+// CHECK:   br i1 %call5, label %if.then6, label %if.end7
+// CHECK: if.then6:
+// CHECK:   store i32 6, ptr %cleanup.dest.slot, align 4
+// CHECK:   br label %cleanup
+// CHECK: if.end7:
+// CHECK:   %7 = load i32, ptr %i, align 4
+// CHECK:   %call8 = call {{.*}} i1 @_Z1gi(i32 {{.*}} %7)
+// CHECK:   br i1 %call8, label %if.then9, label %if.end10
+// CHECK: if.then9:
+// CHECK:   store i32 7, ptr %cleanup.dest.slot, align 4
+// CHECK:   br label %cleanup
+// CHECK: if.end10:
+// CHECK:   call void @_ZN20NonTrivialDestructorD1Ev(ptr {{.*}} %n3)
+// CHECK:   store i32 0, ptr %cleanup.dest.slot, align 4
+// CHECK:   br label %cleanup
+// CHECK: cleanup:
+// CHECK:   call void @_ZN20NonTrivialDestructorD1Ev(ptr {{.*}} %n2)
+// CHECK:   %cleanup.dest = load i32, ptr %cleanup.dest.slot, align 4
+// CHECK:   switch i32 %cleanup.dest, label %cleanup11 [
+// CHECK:     i32 0, label %cleanup.cont
+// CHECK:     i32 6, label %for.end
+// CHECK:     i32 7, label %for.inc
+// CHECK:   ]
+// CHECK: cleanup.cont:
+// CHECK:   br label %for.inc
+// CHECK: for.inc:
+// CHECK:   %8 = load ptr, ptr %__begin2, align 8
+// CHECK:   %incdec.ptr = getelementptr inbounds nuw i32, ptr %8, i32 1
+// CHECK:   store ptr %incdec.ptr, ptr %__begin2, align 8
+// CHECK:   br label %for.cond
+// CHECK: for.end:
+// CHECK:   call void @_ZN20NonTrivialDestructorD1Ev(ptr {{.*}} %n4)
+// CHECK:   store i32 0, ptr %cleanup.dest.slot, align 4
+// CHECK:   br label %cleanup11
+// CHECK: cleanup11:
+// CHECK:   call void @_ZN20NonTrivialDestructorD1Ev(ptr {{.*}} %n1)
+// CHECK:   %cleanup.dest12 = load i32, ptr %cleanup.dest.slot, align 4
+// CHECK:   switch i32 %cleanup.dest12, label %unreachable [
+// CHECK:     i32 0, label %cleanup.cont13
+// CHECK:     i32 4, label %while.end
+// CHECK:     i32 3, label %while.cond
+// CHECK:   ]
+// CHECK: cleanup.cont13:
+// CHECK:   br label %while.cond
+// CHECK: while.end:
+// CHECK:   ret void
+// CHECK: unreachable:
+// CHECK:   unreachable
+void f2() {
+  l1: while (g(0)) {
+    NonTrivialDestructor n1;
+    l2: for (int i : a) {
+      NonTrivialDestructor n2;
+      if (g(i)) break l1;
+      if (g(i)) continue l1;
+      if (g(i)) break l2;
+      if (g(i)) continue l2;
+      NonTrivialDestructor n3;
+    }
+    NonTrivialDestructor n4;
+  }
+}
diff --git a/clang/test/Sema/labeled-break-continue.c b/clang/test/Sema/labeled-break-continue.c
index c4c32dfacd180..a51b70672dc4e 100644
--- a/clang/test/Sema/labeled-break-continue.c
+++ b/clang/test/Sema/labeled-break-continue.c
@@ -1,4 +1,5 @@
 // RUN: %clang_cc1 -std=c2y -verify -fsyntax-only -fblocks %s
+// RUN: %clang_cc1 -x c++ -verify -fsyntax-only -fblocks %s
 
 void f1() {
   l1: while (true) {
diff --git a/clang/test/SemaCXX/labeled-break-continue.cpp b/clang/test/SemaCXX/labeled-break-continue.cpp
new file mode 100644
index 0000000000000..45608b872589a
--- /dev/null
+++ b/clang/test/SemaCXX/labeled-break-continue.cpp
@@ -0,0 +1,51 @@
+// RUN: %clang_cc1 -std=c++20 -verify -fsyntax-only %s
+
+int a[10]{};
+struct S {
+  int a[10]{};
+};
+
+void f1() {
+  l1: for (int x : a) {
+    break l1;
+    continue l1;
+  }
+
+  l2: for (int x : a) {
+    break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+    continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
+  }
+
+  l3: for (int x : a) {
+    l4: for (int x : a) {
+      break l3;
+      break l4;
+      continue l3;
+      continue l4;
+    }
+  }
+}
+
+void f2() {
+  l1: for (
+    int x = ({
+      break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+      continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
+      1;
+    });
+    int y : ({
+      break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+      continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
+      S();
+    }).a
+  ) {}
+}
+
+void f3() {
+  a: b: while (true) {
+    (void) []{
+      break a; // expected-error {{use of undeclared label 'a'}}
+      continue b; // expected-error {{use of undeclared label 'b'}}
+    };
+  }
+}

>From 1b39ff4ad90baf68d3864376d45e13529bb5e835 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 12:53:10 +0200
Subject: [PATCH 09/19] ObjC support

---
 clang/lib/Sema/JumpDiagnostics.cpp            |  15 +-
 .../test/CodeGenObjC/labeled-break-continue.m | 174 ++++++++++++++++++
 clang/test/Sema/labeled-break-continue.c      |   9 -
 clang/test/SemaObjC/labeled-break-continue.m  |  39 ++++
 4 files changed, 221 insertions(+), 16 deletions(-)
 create mode 100644 clang/test/CodeGenObjC/labeled-break-continue.m
 create mode 100644 clang/test/SemaObjC/labeled-break-continue.m

diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp
index 879b5ecbf09ab..efd99411ff8b3 100644
--- a/clang/lib/Sema/JumpDiagnostics.cpp
+++ b/clang/lib/Sema/JumpDiagnostics.cpp
@@ -84,7 +84,9 @@ class JumpScopeChecker {
                              unsigned &ParentScope);
   void BuildScopeInformation(CompoundLiteralExpr *CLE, unsigned &ParentScope);
   void BuildScopeInformation(Stmt *S, unsigned &origParentScope);
-  void BuildScopeInformationForLoopOrSwitch(Stmt *S, Stmt *Body, unsigned& ParentScope);
+  void BuildScopeInformationForLoopOrSwitch(Stmt *S, Stmt *Body,
+                                            unsigned &ParentScope,
+                                            unsigned InDiag = 0);
 
   void VerifyJumps();
   void VerifyIndirectJumps();
@@ -306,7 +308,7 @@ void JumpScopeChecker::BuildScopeInformation(CompoundLiteralExpr *CLE,
 /// The loop condition etc. are *not* included in it though; this forbids doing
 /// horrible things such as 'x: while (({ continue x; })) {}'.
 void JumpScopeChecker::BuildScopeInformationForLoopOrSwitch(
-    Stmt *S, Stmt *Body, unsigned &ParentScope) {
+    Stmt *S, Stmt *Body, unsigned &ParentScope, unsigned InDiag) {
   for (Stmt *Child : S->children()) {
     if (!Child || Child == Body)
       continue;
@@ -314,7 +316,7 @@ void JumpScopeChecker::BuildScopeInformationForLoopOrSwitch(
   }
 
   unsigned NewParentScope = Scopes.size();
-  Scopes.push_back(GotoScope(ParentScope, 0, 0, S->getBeginLoc()));
+  Scopes.push_back(GotoScope(ParentScope, InDiag, 0, S->getBeginLoc()));
   LabelAndGotoScopes[S] = NewParentScope;
   BuildScopeInformation(Body, NewParentScope);
 }
@@ -340,10 +342,9 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
 
   case Stmt::ObjCForCollectionStmtClass: {
     auto *CS = cast<ObjCForCollectionStmt>(S);
-    unsigned Diag = diag::note_protected_by_objc_fast_enumeration;
-    unsigned NewParentScope = Scopes.size();
-    Scopes.push_back(GotoScope(ParentScope, Diag, 0, S->getBeginLoc()));
-    BuildScopeInformation(CS->getBody(), NewParentScope);
+    BuildScopeInformationForLoopOrSwitch(
+        S, CS->getBody(), ParentScope,
+        diag::note_protected_by_objc_fast_enumeration);
     return;
   }
 
diff --git a/clang/test/CodeGenObjC/labeled-break-continue.m b/clang/test/CodeGenObjC/labeled-break-continue.m
new file mode 100644
index 0000000000000..2ab9aab88f294
--- /dev/null
+++ b/clang/test/CodeGenObjC/labeled-break-continue.m
@@ -0,0 +1,174 @@
+// RUN: %clang_cc1 -triple x86_64-apple-darwin -Wno-objc-root-class -emit-llvm -o - %s | FileCheck %s
+
+int g(id x);
+
+// CHECK-LABEL: define void @f1(ptr {{.*}} %y)
+// CHECK: entry:
+// CHECK:   %y.addr = alloca ptr, align 8
+// CHECK:   %x1 = alloca ptr, align 8
+// CHECK:   %state.ptr = alloca %struct.__objcFastEnumerationState, align 8
+// CHECK:   %items.ptr = alloca [16 x ptr], align 8
+// CHECK:   store ptr %y, ptr %y.addr, align 8
+// CHECK:   br label %x
+// CHECK: x:
+// CHECK:   call void @llvm.memset.p0.i64(ptr align 8 %state.ptr, i8 0, i64 64, i1 false)
+// CHECK:   %0 = load ptr, ptr %y.addr, align 8
+// CHECK:   %1 = load ptr, ptr @OBJC_SELECTOR_REFERENCES_, align 8
+// CHECK:   %call = call i64 @objc_msgSend(ptr {{.*}} %0, ptr {{.*}} %1, ptr {{.*}} %state.ptr, ptr {{.*}} %items.ptr, i64 {{.*}} 16)
+// CHECK:   %iszero = icmp eq i64 %call, 0
+// CHECK:   br i1 %iszero, label %forcoll.empty, label %forcoll.loopinit
+// CHECK: forcoll.loopinit:
+// CHECK:   %mutationsptr.ptr = getelementptr inbounds nuw %struct.__objcFastEnumerationState, ptr %state.ptr, i32 0, i32 2
+// CHECK:   %mutationsptr = load ptr, ptr %mutationsptr.ptr, align 8
+// CHECK:   %forcoll.initial-mutations = load i64, ptr %mutationsptr, align 8
+// CHECK:   br label %forcoll.loopbody
+// CHECK: forcoll.loopbody:
+// CHECK:   %forcoll.index = phi i64 [ 0, %forcoll.loopinit ], [ %6, %forcoll.next ], [ 0, %forcoll.refetch ]
+// CHECK:   %forcoll.count = phi i64 [ %call, %forcoll.loopinit ], [ %forcoll.count, %forcoll.next ], [ %call8, %forcoll.refetch ]
+// CHECK:   %mutationsptr2 = load ptr, ptr %mutationsptr.ptr, align 8
+// CHECK:   %statemutations = load i64, ptr %mutationsptr2, align 8
+// CHECK:   %2 = icmp eq i64 %statemutations, %forcoll.initial-mutations
+// CHECK:   br i1 %2, label %forcoll.notmutated, label %forcoll.mutated
+// CHECK: forcoll.mutated:
+// CHECK:   call void @objc_enumerationMutation(ptr {{.*}} %0)
+// CHECK:   br label %forcoll.notmutated
+// CHECK: forcoll.notmutated:
+// CHECK:   %stateitems.ptr = getelementptr inbounds nuw %struct.__objcFastEnumerationState, ptr %state.ptr, i32 0, i32 1
+// CHECK:   %stateitems = load ptr, ptr %stateitems.ptr, align 8
+// CHECK:   %currentitem.ptr = getelementptr inbounds ptr, ptr %stateitems, i64 %forcoll.index
+// CHECK:   %3 = load ptr, ptr %currentitem.ptr, align 8
+// CHECK:   store ptr %3, ptr %x1, align 8
+// CHECK:   %4 = load ptr, ptr %x1, align 8
+// CHECK:   %call3 = call i32 @g(ptr {{.*}} %4)
+// CHECK:   %tobool = icmp ne i32 %call3, 0
+// CHECK:   br i1 %tobool, label %if.then, label %if.end
+// CHECK: if.then:
+// CHECK:   br label %forcoll.end
+// CHECK: if.end:
+// CHECK:   %5 = load ptr, ptr %x1, align 8
+// CHECK:   %call4 = call i32 @g(ptr {{.*}} %5)
+// CHECK:   %tobool5 = icmp ne i32 %call4, 0
+// CHECK:   br i1 %tobool5, label %if.then6, label %if.end7
+// CHECK: if.then6:
+// CHECK:   br label %forcoll.next
+// CHECK: if.end7:
+// CHECK:   br label %forcoll.next
+// CHECK: forcoll.next:
+// CHECK:   %6 = add nuw i64 %forcoll.index, 1
+// CHECK:   %7 = icmp ult i64 %6, %forcoll.count
+// CHECK:   br i1 %7, label %forcoll.loopbody, label %forcoll.refetch
+// CHECK: forcoll.refetch:
+// CHECK:   %8 = load ptr, ptr @OBJC_SELECTOR_REFERENCES_, align 8
+// CHECK:   %call8 = call i64 @objc_msgSend(ptr {{.*}} %0, ptr {{.*}} %8, ptr {{.*}} %state.ptr, ptr {{.*}} %items.ptr, i64 {{.*}} 16)
+// CHECK:   %9 = icmp eq i64 %call8, 0
+// CHECK:   br i1 %9, label %forcoll.empty, label %forcoll.loopbody
+// CHECK: forcoll.empty:
+// CHECK:   br label %forcoll.end
+// CHECK: forcoll.end:
+// CHECK:   ret void
+void f1(id y) {
+  x: for (id x in y) {
+    if (g(x)) break x;
+    if (g(x)) continue x;
+  }
+}
+
+// CHECK-LABEL: define void @f2(ptr {{.*}} %y)
+// CHECK: entry:
+// CHECK:   %y.addr = alloca ptr, align 8
+// CHECK:   %x = alloca ptr, align 8
+// CHECK:   %state.ptr = alloca %struct.__objcFastEnumerationState, align 8
+// CHECK:   %items.ptr = alloca [16 x ptr], align 8
+// CHECK:   store ptr %y, ptr %y.addr, align 8
+// CHECK:   br label %a
+// CHECK: a:
+// CHECK:   br label %while.cond
+// CHECK: while.cond:
+// CHECK:   %0 = load ptr, ptr %y.addr, align 8
+// CHECK:   %call = call i32 @g(ptr {{.*}} %0)
+// CHECK:   %tobool = icmp ne i32 %call, 0
+// CHECK:   br i1 %tobool, label %while.body, label %while.end
+// CHECK: while.body:
+// CHECK:   br label %b
+// CHECK: b:
+// CHECK:   call void @llvm.memset.p0.i64(ptr align 8 %state.ptr, i8 0, i64 64, i1 false)
+// CHECK:   %1 = load ptr, ptr %y.addr, align 8
+// CHECK:   %2 = load ptr, ptr @OBJC_SELECTOR_REFERENCES_, align 8
+// CHECK:   %call1 = call i64 @objc_msgSend(ptr {{.*}} %1, ptr {{.*}} %2, ptr {{.*}} %state.ptr, ptr {{.*}} %items.ptr, i64 {{.*}} 16)
+// CHECK:   %iszero = icmp eq i64 %call1, 0
+// CHECK:   br i1 %iszero, label %forcoll.empty, label %forcoll.loopinit
+// CHECK: forcoll.loopinit:
+// CHECK:   %mutationsptr.ptr = getelementptr inbounds nuw %struct.__objcFastEnumerationState, ptr %state.ptr, i32 0, i32 2
+// CHECK:   %mutationsptr = load ptr, ptr %mutationsptr.ptr, align 8
+// CHECK:   %forcoll.initial-mutations = load i64, ptr %mutationsptr, align 8
+// CHECK:   br label %forcoll.loopbody
+// CHECK: forcoll.loopbody:
+// CHECK:   %forcoll.index = phi i64 [ 0, %forcoll.loopinit ], [ %9, %forcoll.next ], [ 0, %forcoll.refetch ]
+// CHECK:   %forcoll.count = phi i64 [ %call1, %forcoll.loopinit ], [ %forcoll.count, %forcoll.next ], [ %call17, %forcoll.refetch ]
+// CHECK:   %mutationsptr2 = load ptr, ptr %mutationsptr.ptr, align 8
+// CHECK:   %statemutations = load i64, ptr %mutationsptr2, align 8
+// CHECK:   %3 = icmp eq i64 %statemutations, %forcoll.initial-mutations
+// CHECK:   br i1 %3, label %forcoll.notmutated, label %forcoll.mutated
+// CHECK: forcoll.mutated:
+// CHECK:   call void @objc_enumerationMutation(ptr {{.*}} %1)
+// CHECK:   br label %forcoll.notmutated
+// CHECK: forcoll.notmutated:
+// CHECK:   %stateitems.ptr = getelementptr inbounds nuw %struct.__objcFastEnumerationState, ptr %state.ptr, i32 0, i32 1
+// CHECK:   %stateitems = load ptr, ptr %stateitems.ptr, align 8
+// CHECK:   %currentitem.ptr = getelementptr inbounds ptr, ptr %stateitems, i64 %forcoll.index
+// CHECK:   %4 = load ptr, ptr %currentitem.ptr, align 8
+// CHECK:   store ptr %4, ptr %x, align 8
+// CHECK:   %5 = load ptr, ptr %x, align 8
+// CHECK:   %call3 = call i32 @g(ptr {{.*}} %5)
+// CHECK:   %tobool4 = icmp ne i32 %call3, 0
+// CHECK:   br i1 %tobool4, label %if.then, label %if.end
+// CHECK: if.then:
+// CHECK:   br label %while.end
+// CHECK: if.end:
+// CHECK:   %6 = load ptr, ptr %x, align 8
+// CHECK:   %call5 = call i32 @g(ptr {{.*}} %6)
+// CHECK:   %tobool6 = icmp ne i32 %call5, 0
+// CHECK:   br i1 %tobool6, label %if.then7, label %if.end8
+// CHECK: if.then7:
+// CHECK:   br label %while.cond
+// CHECK: if.end8:
+// CHECK:   %7 = load ptr, ptr %x, align 8
+// CHECK:   %call9 = call i32 @g(ptr {{.*}} %7)
+// CHECK:   %tobool10 = icmp ne i32 %call9, 0
+// CHECK:   br i1 %tobool10, label %if.then11, label %if.end12
+// CHECK: if.then11:
+// CHECK:   br label %forcoll.end
+// CHECK: if.end12:
+// CHECK:   %8 = load ptr, ptr %x, align 8
+// CHECK:   %call13 = call i32 @g(ptr {{.*}} %8)
+// CHECK:   %tobool14 = icmp ne i32 %call13, 0
+// CHECK:   br i1 %tobool14, label %if.then15, label %if.end16
+// CHECK: if.then15:
+// CHECK:   br label %forcoll.next
+// CHECK: if.end16:
+// CHECK:   br label %forcoll.next
+// CHECK: forcoll.next:
+// CHECK:   %9 = add nuw i64 %forcoll.index, 1
+// CHECK:   %10 = icmp ult i64 %9, %forcoll.count
+// CHECK:   br i1 %10, label %forcoll.loopbody, label %forcoll.refetch
+// CHECK: forcoll.refetch:
+// CHECK:   %11 = load ptr, ptr @OBJC_SELECTOR_REFERENCES_, align 8
+// CHECK:   %call17 = call i64 @objc_msgSend(ptr {{.*}} %1, ptr {{.*}} %11, ptr {{.*}} %state.ptr, ptr {{.*}} %items.ptr, i64 {{.*}} 16)
+// CHECK:   %12 = icmp eq i64 %call17, 0
+// CHECK:   br i1 %12, label %forcoll.empty, label %forcoll.loopbody
+// CHECK: forcoll.empty:
+// CHECK:   br label %forcoll.end
+// CHECK: forcoll.end:
+// CHECK:   br label %while.cond
+// CHECK: while.end:
+// CHECK:   ret void
+void f2(id y) {
+  a: while (g(y)) {
+    b: for (id x in y) {
+      if (g(x)) break a;
+      if (g(x)) continue a;
+      if (g(x)) break b;
+      if (g(x)) continue b;
+    }
+  }
+}
diff --git a/clang/test/Sema/labeled-break-continue.c b/clang/test/Sema/labeled-break-continue.c
index a51b70672dc4e..27ddf1387a30e 100644
--- a/clang/test/Sema/labeled-break-continue.c
+++ b/clang/test/Sema/labeled-break-continue.c
@@ -140,12 +140,3 @@ void f7() {
     continue d; // expected-error {{use of undeclared label 'd'}}
   }
 }
-
-
-// TODO:
-//  - Add tests for all the stuff that ActOnBreakStmt normally checks (SEH __finally etc.)
-//  - C++ support: range-based for loops
-//  - ObjC support: 'for in' loops
-//  - Constant evaluation
-//  - Template instantiation (need to get the instantiated LabelDecl)
-//  - Tests for TextNodeDumper / JSONNodeDumper / AST printing
diff --git a/clang/test/SemaObjC/labeled-break-continue.m b/clang/test/SemaObjC/labeled-break-continue.m
new file mode 100644
index 0000000000000..72cf07912ed3c
--- /dev/null
+++ b/clang/test/SemaObjC/labeled-break-continue.m
@@ -0,0 +1,39 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -fblocks %s
+
+void f1(id y) {
+    l1: for (id x in y) {
+        break l1;
+        continue l1;
+    }
+
+    l2: for (id x in y) {
+        break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+        continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
+    }
+
+    l3: for (id x in y) {
+        l4: for (id x in y) {
+            break l3;
+            break l4;
+            continue l3;
+            continue l4;
+        }
+    }
+}
+
+void f2(id y) {
+    l1: for (id x in ({
+        break l1; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+        continue l1; // expected-error {{'continue' label does not name an enclosing loop}}
+        y;
+    })) {}
+}
+
+void f3(id y) {
+  a: b: for (id x in y) {
+    (void) ^{
+      break a; // expected-error {{use of undeclared label 'a'}}
+      continue b; // expected-error {{use of undeclared label 'b'}}
+    };
+  }
+}

>From 08474674f69fe6ba1df3405920e824685c41c3b9 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 14:36:23 +0200
Subject: [PATCH 10/19] Constexpr support

---
 clang/lib/AST/ExprConstant.cpp                |  84 ++++++++--
 .../labeled-break-continue-constexpr.cpp      | 155 ++++++++++++++++++
 2 files changed, 222 insertions(+), 17 deletions(-)
 create mode 100644 clang/test/SemaCXX/labeled-break-continue-constexpr.cpp

diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 3679327da7b0c..04594dd152b96 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -894,6 +894,11 @@ namespace {
     /// declaration whose initializer is being evaluated, if any.
     APValue *EvaluatingDeclValue;
 
+    /// Stack of loops and 'switch' statements which we're currently
+    /// breaking/continuing; null entries are used to mark unlabeled
+    /// break/continue.
+    SmallVector<Stmt *> BreakContinueStack;
+
     /// Set of objects that are currently being constructed.
     llvm::DenseMap<ObjectUnderConstruction, ConstructionPhase>
         ObjectsUnderConstruction;
@@ -5385,6 +5390,45 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
                                    const Stmt *S,
                                    const SwitchCase *SC = nullptr);
 
+
+/// Helper to implement labeled break/continue. Returns 'true' if the evaluation
+/// result should be propagated up. Otherwise, it sets the evaluation result
+/// to either Continue to continue the current loop, or Succeeded to break it.
+static bool ShouldPropagateBreakContinue(EvalInfo &Info,
+                                         const Stmt *LoopOrSwitch,
+                                         ArrayRef<BlockScopeRAII *> Scopes,
+                                         EvalStmtResult &ESR) {
+  bool IsSwitch = isa<SwitchStmt>(LoopOrSwitch);
+
+  // For loops, map Succeeded to Continue so we don't have to check for both.
+  if (!IsSwitch && ESR == ESR_Succeeded) {
+    ESR = ESR_Continue;
+    return false;
+  }
+
+  if (ESR != ESR_Break && ESR != ESR_Continue)
+    return false;
+
+  // Are we breaking out of or continuing this statement?
+  bool CanBreakOrContinue = !IsSwitch || ESR == ESR_Break;
+  Stmt *StackTop = Info.BreakContinueStack.back();
+  if (CanBreakOrContinue && (StackTop == nullptr || StackTop == LoopOrSwitch)) {
+    Info.BreakContinueStack.pop_back();
+    if (ESR == ESR_Break)
+      ESR = ESR_Succeeded;
+    return false;
+  }
+
+  // We're not. Propagate the result up.
+  for (BlockScopeRAII* S : Scopes) {
+    if (!S->destroy()) {
+      ESR = ESR_Failed;
+      break;
+    }
+  }
+  return true;
+}
+
 /// Evaluate the body of a loop, and translate the result as appropriate.
 static EvalStmtResult EvaluateLoopBody(StmtResult &Result, EvalInfo &Info,
                                        const Stmt *Body,
@@ -5395,18 +5439,7 @@ static EvalStmtResult EvaluateLoopBody(StmtResult &Result, EvalInfo &Info,
   if (ESR != ESR_Failed && ESR != ESR_CaseNotFound && !Scope.destroy())
     ESR = ESR_Failed;
 
-  switch (ESR) {
-  case ESR_Break:
-    return ESR_Succeeded;
-  case ESR_Succeeded:
-  case ESR_Continue:
-    return ESR_Continue;
-  case ESR_Failed:
-  case ESR_Returned:
-  case ESR_CaseNotFound:
-    return ESR;
-  }
-  llvm_unreachable("Invalid EvalStmtResult!");
+  return ESR;
 }
 
 /// Evaluate a switch statement.
@@ -5472,10 +5505,12 @@ static EvalStmtResult EvaluateSwitch(StmtResult &Result, EvalInfo &Info,
   EvalStmtResult ESR = EvaluateStmt(Result, Info, SS->getBody(), Found);
   if (ESR != ESR_Failed && ESR != ESR_CaseNotFound && !Scope.destroy())
     return ESR_Failed;
+  if (ShouldPropagateBreakContinue(Info, SS, /*Scopes=*/{}, ESR))
+    return ESR;
 
   switch (ESR) {
   case ESR_Break:
-    return ESR_Succeeded;
+    llvm_unreachable("Should have been converted to Succeeded");
   case ESR_Succeeded:
   case ESR_Continue:
   case ESR_Failed:
@@ -5573,6 +5608,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
     case Stmt::WhileStmtClass: {
       EvalStmtResult ESR =
           EvaluateLoopBody(Result, Info, cast<WhileStmt>(S)->getBody(), Case);
+      if (ShouldPropagateBreakContinue(Info, S, /*Scopes=*/{}, ESR))
+        return ESR;
       if (ESR != ESR_Continue)
         return ESR;
       break;
@@ -5594,6 +5631,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
 
       EvalStmtResult ESR =
           EvaluateLoopBody(Result, Info, FS->getBody(), Case);
+      if (ShouldPropagateBreakContinue(Info, FS, /*Scopes=*/{}, ESR))
+        return ESR;
       if (ESR != ESR_Continue)
         return ESR;
       if (const auto *Inc = FS->getInc()) {
@@ -5756,6 +5795,9 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
         break;
 
       EvalStmtResult ESR = EvaluateLoopBody(Result, Info, WS->getBody());
+      if (ShouldPropagateBreakContinue(Info, WS, &Scope, ESR))
+        return ESR;
+
       if (ESR != ESR_Continue) {
         if (ESR != ESR_Failed && !Scope.destroy())
           return ESR_Failed;
@@ -5772,6 +5814,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
     bool Continue;
     do {
       EvalStmtResult ESR = EvaluateLoopBody(Result, Info, DS->getBody(), Case);
+      if (ShouldPropagateBreakContinue(Info, DS, /*Scopes=*/{}, ESR))
+        return ESR;
       if (ESR != ESR_Continue)
         return ESR;
       Case = nullptr;
@@ -5814,6 +5858,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
       }
 
       EvalStmtResult ESR = EvaluateLoopBody(Result, Info, FS->getBody());
+      if (ShouldPropagateBreakContinue(Info, FS, {&IterScope, &ForScope}, ESR))
+        return ESR;
       if (ESR != ESR_Continue) {
         if (ESR != ESR_Failed && (!IterScope.destroy() || !ForScope.destroy()))
           return ESR_Failed;
@@ -5905,6 +5951,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
 
       // Loop body.
       ESR = EvaluateLoopBody(Result, Info, FS->getBody());
+      if (ShouldPropagateBreakContinue(Info, FS, {&InnerScope, &Scope}, ESR))
+        return ESR;
       if (ESR != ESR_Continue) {
         if (ESR != ESR_Failed && (!InnerScope.destroy() || !Scope.destroy()))
           return ESR_Failed;
@@ -5930,10 +5978,12 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
     return EvaluateSwitch(Result, Info, cast<SwitchStmt>(S));
 
   case Stmt::ContinueStmtClass:
-    return ESR_Continue;
-
-  case Stmt::BreakStmtClass:
-    return ESR_Break;
+  case Stmt::BreakStmtClass: {
+    auto *B = cast<LoopControlStmt>(S);
+    Info.BreakContinueStack.push_back(B->isLabeled() ? B->getLabelTarget()
+                                                   : nullptr);
+    return isa<ContinueStmt>(S) ? ESR_Continue : ESR_Break;
+  }
 
   case Stmt::LabelStmtClass:
     return EvaluateStmt(Result, Info, cast<LabelStmt>(S)->getSubStmt(), Case);
diff --git a/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
new file mode 100644
index 0000000000000..b83819ce3fa41
--- /dev/null
+++ b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
@@ -0,0 +1,155 @@
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+struct Tracker {
+  bool& destroyed;
+  constexpr Tracker(bool& destroyed) : destroyed{destroyed} {}
+  constexpr ~Tracker() { destroyed = true; }
+};
+
+constexpr int f1() {
+  a: for (;;) {
+    for (;;) {
+      break a;
+    }
+  }
+  return 1;
+}
+static_assert(f1() == 1);
+
+constexpr int f2() {
+  int x{};
+  a: for (int i = 0; i < 10; i++) {
+    b: for (int j = 0; j < 10; j++) {
+      x += j;
+      if (i == 2 && j == 2) break a;
+    }
+  }
+  return x;
+}
+static_assert(f2() == 93);
+
+constexpr int f3() {
+  int x{};
+  a: for (int i = 0; i < 10; i++) {
+    x += i;
+    continue a;
+  }
+  return x;
+}
+static_assert(f3() == 45);
+
+constexpr int f4() {
+  int x{};
+  a: for (int i = 1; i < 10; i++) {
+    x += i;
+    break a;
+  }
+  return x;
+}
+static_assert(f4() == 1);
+
+constexpr bool f5(bool should_break) {
+  bool destroyed = false;
+  a: while (!destroyed) {
+    while (true) {
+      Tracker _{destroyed};
+      if (should_break) break a;
+      continue a;
+    }
+  }
+  return destroyed;
+}
+static_assert(f5(true));
+static_assert(f5(false));
+
+constexpr bool f6(bool should_break) {
+  bool destroyed = false;
+  a: while (!destroyed) {
+    while (true) {
+      while (true) {
+        Tracker _{destroyed};
+        while (true) {
+          while (true) {
+            if (should_break) break a;
+            continue a;
+          }
+        }
+      }
+    }
+  }
+  return destroyed;
+}
+static_assert(f6(true));
+static_assert(f6(false));
+
+constexpr int f7(bool should_break) {
+  int x = 100;
+  a: for (int i = 0; i < 10; i++) {
+    b: switch (1) {
+      case 1:
+        x += i;
+        if (should_break) break a;
+        break b;
+    }
+  }
+  return x;
+}
+static_assert(f7(true) == 100);
+static_assert(f7(false) == 145);
+
+constexpr bool f8() {
+  a: switch (1) {
+    case 1: {
+      while (true) {
+        switch (1) {
+          case 1: break a;
+        }
+      }
+    }
+  }
+  return true;
+}
+static_assert(f8());
+
+constexpr bool f9() {
+  a: do {
+    while (true) {
+      break a;
+    }
+  } while (true);
+  return true;
+}
+static_assert(f9());
+
+constexpr int f10(bool should_break) {
+  int a[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+  int x{};
+  a: for (int v : a) {
+    for (int i = 0; i < 3; i++) {
+      x += v;
+      if (should_break && v == 5) break a;
+    }
+  }
+  return x;
+}
+
+static_assert(f10(true) == 35);
+static_assert(f10(false) == 165);
+
+constexpr bool f11() {
+  struct X {
+    int a[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+    Tracker t;
+    constexpr X(bool& b) : t{b} {}
+  };
+
+  bool destroyed = false;
+  a: for (int v : X(destroyed).a) {
+    for (int i = 0; i < 3; i++) {
+      if (v == 5) break a;
+    }
+  }
+  return destroyed;
+}
+static_assert(f11());

>From 6301ef534d1bdb2d7364b57af0272fca004690fb Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 15:49:32 +0200
Subject: [PATCH 11/19] Template support

---
 clang/lib/Sema/SemaStmt.cpp                   |  2 +
 clang/lib/Sema/TreeTransform.h                | 22 +++++++-
 .../CodeGenCXX/labeled-break-continue.cpp     | 52 +++++++++++++++++++
 .../labeled-break-continue-constexpr.cpp      | 14 +++++
 4 files changed, 88 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index d31236bdd5828..a8fddfb1fc0b5 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -3291,6 +3291,7 @@ StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
     return new (Context) ContinueStmt(ContinueLoc, LabelLoc, Target);
   }
 
+  assert(CurScope && "unlabeled continue requires a scope");
   Scope *S = CurScope->getContinueParent();
   if (!S) {
     // C99 6.8.6.2p1: A break shall appear only in or as a loop body.
@@ -3324,6 +3325,7 @@ StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope,
     return new (Context) BreakStmt(BreakLoc, LabelLoc, Target);
   }
 
+  assert(CurScope && "unlabeled break requires a scope");
   Scope *S = CurScope->getBreakParent();
   if (!S) {
     // C99 6.8.6.3p1: A break shall appear only in or as a switch/loop body.
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 0030946301a93..cc01f32a7e724 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -8553,13 +8553,31 @@ TreeTransform<Derived>::TransformIndirectGotoStmt(IndirectGotoStmt *S) {
 template<typename Derived>
 StmtResult
 TreeTransform<Derived>::TransformContinueStmt(ContinueStmt *S) {
-  return S;
+  if (!S->isLabeled())
+    return S;
+
+  Decl *LD = getDerived().TransformDecl(S->getLabelDecl()->getLocation(),
+                                        S->getLabelDecl());
+  if (!LD)
+    return StmtError();
+
+  return SemaRef.ActOnContinueStmt(S->getKwLoc(), /*CurScope=*/nullptr,
+                                   cast<LabelDecl>(LD), S->getLabelLoc());
 }
 
 template<typename Derived>
 StmtResult
 TreeTransform<Derived>::TransformBreakStmt(BreakStmt *S) {
-  return S;
+  if (!S->isLabeled())
+    return S;
+
+  Decl *LD = getDerived().TransformDecl(S->getLabelDecl()->getLocation(),
+                                        S->getLabelDecl());
+  if (!LD)
+    return StmtError();
+
+  return SemaRef.ActOnBreakStmt(S->getKwLoc(), /*CurScope=*/nullptr,
+                                cast<LabelDecl>(LD), S->getLabelLoc());
 }
 
 template<typename Derived>
diff --git a/clang/test/CodeGenCXX/labeled-break-continue.cpp b/clang/test/CodeGenCXX/labeled-break-continue.cpp
index bf066ce4eacda..bf1b6d520efc4 100644
--- a/clang/test/CodeGenCXX/labeled-break-continue.cpp
+++ b/clang/test/CodeGenCXX/labeled-break-continue.cpp
@@ -167,3 +167,55 @@ void f2() {
     NonTrivialDestructor n4;
   }
 }
+
+template <bool Continue>
+void f3() {
+  l1: while (g(1)) {
+    for (;g(2);) {
+      if constexpr (Continue) continue l1;
+      else break l1;
+    }
+  }
+}
+
+// CHECK-LABEL: define {{.*}} void @_Z2f3ILb1EEvv()
+// CHECK: entry:
+// CHECK:   br label %l1
+// CHECK: l1:
+// CHECK:   br label %while.cond
+// CHECK: while.cond:
+// CHECK:   %call = call {{.*}} i1 @_Z1gi(i32 {{.*}} 1)
+// CHECK:   br i1 %call, label %while.body, label %while.end
+// CHECK: while.body:
+// CHECK:   br label %for.cond
+// CHECK: for.cond:
+// CHECK:   %call1 = call {{.*}} i1 @_Z1gi(i32 {{.*}} 2)
+// CHECK:   br i1 %call1, label %for.body, label %for.end
+// CHECK: for.body:
+// CHECK:   br label %while.cond
+// CHECK: for.end:
+// CHECK:   br label %while.cond
+// CHECK: while.end:
+// CHECK:   ret void
+template void f3<true>();
+
+// CHECK-LABEL: define {{.*}} void @_Z2f3ILb0EEvv()
+// CHECK: entry:
+// CHECK:   br label %l1
+// CHECK: l1:
+// CHECK:   br label %while.cond
+// CHECK: while.cond:
+// CHECK:   %call = call {{.*}} i1 @_Z1gi(i32 {{.*}} 1)
+// CHECK:   br i1 %call, label %while.body, label %while.end
+// CHECK: while.body:
+// CHECK:   br label %for.cond
+// CHECK: for.cond:
+// CHECK:   %call1 = call {{.*}} i1 @_Z1gi(i32 {{.*}} 2)
+// CHECK:   br i1 %call1, label %for.body, label %for.end
+// CHECK: for.body:
+// CHECK:   br label %while.end
+// CHECK: for.end:
+// CHECK:   br label %while.cond
+// CHECK: while.end:
+// CHECK:   ret void
+template void f3<false>();
diff --git a/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
index b83819ce3fa41..fe58004ca6ff0 100644
--- a/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
+++ b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
@@ -153,3 +153,17 @@ constexpr bool f11() {
   return destroyed;
 }
 static_assert(f11());
+
+template <typename T>
+constexpr T f12() {
+  T x{};
+  a: for (T i = 0; i < 10; i++) {
+    b: for (T j = 0; j < 10; j++) {
+      x += j;
+      if (i == 2 && j == 2) break a;
+    }
+  }
+  return x;
+}
+static_assert(f12<int>() == 93);
+static_assert(f12<unsigned>() == 93u);

>From e05b8b130d622f7709413fb64c5c2702e78c0566 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 16:57:00 +0200
Subject: [PATCH 12/19] Diagnose invalid break/continue in OpenACC/OpenMP

---
 clang/lib/Sema/JumpDiagnostics.cpp        | 42 +++++++++++++++++++++--
 clang/lib/Sema/SemaStmt.cpp               | 10 ++++++
 clang/test/OpenMP/for_loop_messages.cpp   | 20 +++++++++++
 clang/test/Sema/__try.c                   | 16 +++++++++
 clang/test/SemaOpenACC/no-branch-in-out.c | 23 +++++++++++++
 5 files changed, 108 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp
index efd99411ff8b3..b7e58c4170979 100644
--- a/clang/lib/Sema/JumpDiagnostics.cpp
+++ b/clang/lib/Sema/JumpDiagnostics.cpp
@@ -1029,6 +1029,27 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
                                  unsigned JumpDiagError,
                                  unsigned JumpDiagWarning,
                                  unsigned JumpDiagCompat) {
+  auto DiagnoseInvalidBreakInOpenACCComputeConstruct = [&](unsigned Scope) {
+    auto GetParent = [&](unsigned S) -> unsigned {
+      if (S >= Scopes.size()) return S;
+      return Scopes[S].ParentScope;
+    };
+
+    // For labeled break, check if we're inside an OpenACC construct; those
+    // form a separate scope around the loop, so we need to go up a few scopes
+    // from the target.
+    if (isa<BreakStmt>(From)) {
+      unsigned OpenACCScope = GetParent(GetParent(Scope));
+      if (OpenACCScope < Scopes.size() &&
+          Scopes[OpenACCScope].InDiag ==
+              diag::note_acc_branch_into_compute_construct) {
+        S.Diag(From->getBeginLoc(),
+               diag::err_acc_branch_in_out_compute_construct)
+            << /*branch*/ 0 << /*out of */ 0;
+      }
+    }
+  };
+
   if (CHECK_PERMISSIVE(!LabelAndGotoScopes.count(From)))
     return;
   if (CHECK_PERMISSIVE(!LabelAndGotoScopes.count(To)))
@@ -1037,14 +1058,18 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
   unsigned FromScope = LabelAndGotoScopes[From];
   unsigned ToScope = LabelAndGotoScopes[To];
 
-  // Common case: exactly the same scope, which is fine.
-  if (FromScope == ToScope) return;
+  // Common case: exactly the same scope, which is usually fine.
+  if (FromScope == ToScope) {
+    DiagnoseInvalidBreakInOpenACCComputeConstruct(ToScope);
+    return;
+  }
 
   // Warn on gotos out of __finally blocks.
   if (isa<GotoStmt, IndirectGotoStmt, LoopControlStmt>(From)) {
     // If FromScope > ToScope, FromScope is more nested and the jump goes to a
     // less nested scope.  Check if it crosses a __finally along the way.
-    for (unsigned I = FromScope; I > ToScope; I = Scopes[I].ParentScope) {
+    unsigned I = FromScope;
+    for (; I > ToScope; I = Scopes[I].ParentScope) {
       if (Scopes[I].InDiag == diag::note_protected_by_seh_finally) {
         S.Diag(From->getBeginLoc(), diag::warn_jump_out_of_seh_finally);
         break;
@@ -1055,11 +1080,22 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
         break;
       } else if (Scopes[I].InDiag ==
                  diag::note_acc_branch_into_compute_construct) {
+        // For consistency, emit the same diagnostic that ActOnBreakStmt() and
+        // ActOnContinueStmt() emit for non-labeled break/continue.
+        if (isa<LoopControlStmt>(From)) {
+          S.Diag(From->getBeginLoc(),
+                 diag::err_acc_branch_in_out_compute_construct)
+              << /*branch*/ 0 << /*out of */ 0;
+          return;
+        }
+
         S.Diag(From->getBeginLoc(), diag::err_goto_into_protected_scope);
         S.Diag(Scopes[I].Loc, diag::note_acc_branch_out_of_compute_construct);
         return;
       }
     }
+
+    DiagnoseInvalidBreakInOpenACCComputeConstruct(I);
   }
 
   unsigned CommonScope = GetDeepestCommonScope(FromScope, ToScope);
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index a8fddfb1fc0b5..f11e115689d5d 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -3331,6 +3331,16 @@ StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope,
     // C99 6.8.6.3p1: A break shall appear only in or as a switch/loop body.
     return StmtError(Diag(BreakLoc, diag::err_break_not_in_loop_or_switch));
   }
+
+  // FIXME: We currently omit this check for labeled 'break' statements; this
+  // is fine since trying to label an OpenMP loop causes an error because we
+  // expect a ForStmt, not a LabelStmt. Trying to branch out of a loop that
+  // contains the OpenMP loop also doesn't work because the former is outlined
+  // into a separate function, i.e. the target label and 'break' are not in
+  // the same function. What's not great is that we only print 'use of
+  // undeclared label', which is a bit confusing because to the user the label
+  // does in fact appear to be declared. It would be better to print a more
+  // helpful error message instead, but that seems complicated.
   if (S->isOpenMPLoopScope())
     return StmtError(Diag(BreakLoc, diag::err_omp_loop_cannot_use_stmt)
                      << "break");
diff --git a/clang/test/OpenMP/for_loop_messages.cpp b/clang/test/OpenMP/for_loop_messages.cpp
index e62ec07acc049..ac0b4f982e5d3 100644
--- a/clang/test/OpenMP/for_loop_messages.cpp
+++ b/clang/test/OpenMP/for_loop_messages.cpp
@@ -842,3 +842,23 @@ void test_static_data_member() {
     };
   }
 }
+
+// FIXME: The diagnostics here aren't exactly great; see Sema::ActOnBreakStmt() for more details.
+void test_labeled_break() {
+#pragma omp parallel
+#pragma omp for
+  a: // expected-error {{statement after '#pragma omp for' must be a for loop}}
+  for (int i = 0; i < 16; ++i) {
+    break a;
+    continue a;
+  }
+
+  b: c: while (1) {
+#pragma omp parallel
+#pragma omp for
+    for (int i = 0; i < 16; ++i) {
+      break b; // expected-error {{use of undeclared label 'b'}}
+      continue c; // expected-error {{use of undeclared label 'c'}}
+    }
+  }
+}
diff --git a/clang/test/Sema/__try.c b/clang/test/Sema/__try.c
index 9bfd914c013c1..6702cb9b0e19e 100644
--- a/clang/test/Sema/__try.c
+++ b/clang/test/Sema/__try.c
@@ -287,3 +287,19 @@ void test_typo_in_except(void) {
   } __except(undeclared_identifier) { // expected-error {{use of undeclared identifier 'undeclared_identifier'}} expected-error {{expected expression}}
   }
 }
+
+void test_jump_out_of___finally_labeled(void) {
+  a: while(1) {
+    __try {
+    } __finally {
+      continue a; // expected-warning{{jump out of __finally block has undefined behavior}}
+      break a; // expected-warning{{jump out of __finally block has undefined behavior}}
+      b: while (1) {
+        continue a; // expected-warning{{jump out of __finally block has undefined behavior}}
+        break a; // expected-warning{{jump out of __finally block has undefined behavior}}
+        continue b;
+        break b;
+      }
+    }
+  }
+}
diff --git a/clang/test/SemaOpenACC/no-branch-in-out.c b/clang/test/SemaOpenACC/no-branch-in-out.c
index 37126d8f2200e..3cd6c5af13aaf 100644
--- a/clang/test/SemaOpenACC/no-branch-in-out.c
+++ b/clang/test/SemaOpenACC/no-branch-in-out.c
@@ -687,3 +687,26 @@ void DuffsDeviceLoop() {
   }
   }
 }
+
+void LabeledBreakContinue() {
+  a: for (int i =0; i < 5; ++i) {
+#pragma acc parallel
+    {
+      continue a; // expected-error{{invalid branch out of OpenACC Compute/Combined Construct}}
+      break a; // expected-error{{invalid branch out of OpenACC Compute/Combined Construct}}
+    }
+  }
+
+#pragma acc parallel
+  b: c: for (int i =0; i < 5; ++i) {
+    switch(i) {
+    case 0: break; // leaves switch, not 'for'.
+    }
+
+    break b; // expected-error{{invalid branch out of OpenACC Compute/Combined Construct}}
+    break c; // expected-error{{invalid branch out of OpenACC Compute/Combined Construct}}
+    while (1) break b; // expected-error{{invalid branch out of OpenACC Compute/Combined Construct}}
+    while (1) break c; // expected-error{{invalid branch out of OpenACC Compute/Combined Construct}}
+    d: while (1) break d;
+  }
+}

>From db17a6709bec486e64a37c581d50f9aa479c0ebf Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 17:58:14 +0200
Subject: [PATCH 13/19] Update various AST dumpers

---
 clang/include/clang/AST/JSONNodeDumper.h      |   1 +
 clang/lib/AST/JSONNodeDumper.cpp              |   7 +
 clang/lib/AST/StmtPrinter.cpp                 |  13 +-
 .../ast-dump-labeled-break-continue-json.c    | 326 ++++++++++++++++++
 .../AST/ast-dump-labeled-break-continue.c     |  41 +++
 .../AST/ast-print-labeled-break-continue.c    |  29 ++
 6 files changed, 415 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/AST/ast-dump-labeled-break-continue-json.c
 create mode 100644 clang/test/AST/ast-dump-labeled-break-continue.c
 create mode 100644 clang/test/AST/ast-print-labeled-break-continue.c

diff --git a/clang/include/clang/AST/JSONNodeDumper.h b/clang/include/clang/AST/JSONNodeDumper.h
index 570662b58ccf0..1c0467a45b36a 100644
--- a/clang/include/clang/AST/JSONNodeDumper.h
+++ b/clang/include/clang/AST/JSONNodeDumper.h
@@ -334,6 +334,7 @@ class JSONNodeDumper
   void VisitStringLiteral(const StringLiteral *SL);
   void VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *BLE);
 
+  void VisitLoopControlStmt(const LoopControlStmt *LS);
   void VisitIfStmt(const IfStmt *IS);
   void VisitSwitchStmt(const SwitchStmt *SS);
   void VisitCaseStmt(const CaseStmt *CS);
diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp
index 64ddb1e739347..43a61849b30f4 100644
--- a/clang/lib/AST/JSONNodeDumper.cpp
+++ b/clang/lib/AST/JSONNodeDumper.cpp
@@ -1675,6 +1675,13 @@ void JSONNodeDumper::VisitLabelStmt(const LabelStmt *LS) {
   JOS.attribute("declId", createPointerRepresentation(LS->getDecl()));
   attributeOnlyIfTrue("sideEntry", LS->isSideEntry());
 }
+
+void JSONNodeDumper::VisitLoopControlStmt(const LoopControlStmt *LS) {
+  if (LS->isLabeled())
+    JOS.attribute("targetLabelDeclId",
+                  createPointerRepresentation(LS->getLabelDecl()));
+}
+
 void JSONNodeDumper::VisitGotoStmt(const GotoStmt *GS) {
   JOS.attribute("targetLabelDeclId",
                 createPointerRepresentation(GS->getLabel()));
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index 6ba5ec89964a9..410a415597ea3 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -476,12 +476,21 @@ void StmtPrinter::VisitIndirectGotoStmt(IndirectGotoStmt *Node) {
 }
 
 void StmtPrinter::VisitContinueStmt(ContinueStmt *Node) {
-  Indent() << "continue;";
+  Indent();
+  if (Node->isLabeled())
+    OS << "continue " << Node->getLabelDecl()->getIdentifier()->getName()
+       << ';';
+  else
+    OS << "continue;";
   if (Policy.IncludeNewlines) OS << NL;
 }
 
 void StmtPrinter::VisitBreakStmt(BreakStmt *Node) {
-  Indent() << "break;";
+  Indent();
+  if (Node->isLabeled())
+    OS << "break " << Node->getLabelDecl()->getIdentifier()->getName() << ';';
+  else
+    OS << "break;";
   if (Policy.IncludeNewlines) OS << NL;
 }
 
diff --git a/clang/test/AST/ast-dump-labeled-break-continue-json.c b/clang/test/AST/ast-dump-labeled-break-continue-json.c
new file mode 100644
index 0000000000000..5e04a5df2864e
--- /dev/null
+++ b/clang/test/AST/ast-dump-labeled-break-continue-json.c
@@ -0,0 +1,326 @@
+// RUN: %clang_cc1 -std=c2y -ast-dump=json -ast-dump-filter Test %s | FileCheck %s
+
+void TestLabeledBreakContinue() {
+  a: b: while (true) {
+    break a;
+    continue b;
+    c: for (;;) {
+      break a;
+      continue b;
+      break c;
+    }
+  }
+}
+
+// NOTE: CHECK lines have been autogenerated by gen_ast_dump_json_test.py
+// CHECK-NOT: {{^}}Dumping
+// CHECK:  "kind": "FunctionDecl",
+// CHECK-NEXT:  "loc": {
+// CHECK-NEXT:   "offset": 89,
+// CHECK-NEXT:   "file": "{{.*}}",
+// CHECK-NEXT:   "line": 3,
+// CHECK-NEXT:   "col": 6,
+// CHECK-NEXT:   "tokLen": 24
+// CHECK-NEXT:  },
+// CHECK-NEXT:  "range": {
+// CHECK-NEXT:   "begin": {
+// CHECK-NEXT:    "offset": 84,
+// CHECK-NEXT:    "col": 1,
+// CHECK-NEXT:    "tokLen": 4
+// CHECK-NEXT:   },
+// CHECK-NEXT:   "end": {
+// CHECK-NEXT:    "offset": 246,
+// CHECK-NEXT:    "line": 13,
+// CHECK-NEXT:    "col": 1,
+// CHECK-NEXT:    "tokLen": 1
+// CHECK-NEXT:   }
+// CHECK-NEXT:  },
+// CHECK-NEXT:  "name": "TestLabeledBreakContinue",
+// CHECK-NEXT:  "mangledName": "TestLabeledBreakContinue",
+// CHECK-NEXT:  "type": {
+// CHECK-NEXT:   "qualType": "void (void)"
+// CHECK-NEXT:  },
+// CHECK-NEXT:  "inner": [
+// CHECK-NEXT:   {
+// CHECK-NEXT:    "id": "0x{{.*}}",
+// CHECK-NEXT:    "kind": "CompoundStmt",
+// CHECK-NEXT:    "range": {
+// CHECK-NEXT:     "begin": {
+// CHECK-NEXT:      "offset": 116,
+// CHECK-NEXT:      "line": 3,
+// CHECK-NEXT:      "col": 33,
+// CHECK-NEXT:      "tokLen": 1
+// CHECK-NEXT:     },
+// CHECK-NEXT:     "end": {
+// CHECK-NEXT:      "offset": 246,
+// CHECK-NEXT:      "line": 13,
+// CHECK-NEXT:      "col": 1,
+// CHECK-NEXT:      "tokLen": 1
+// CHECK-NEXT:     }
+// CHECK-NEXT:    },
+// CHECK-NEXT:    "inner": [
+// CHECK-NEXT:     {
+// CHECK-NEXT:      "id": "0x{{.*}}",
+// CHECK-NEXT:      "kind": "LabelStmt",
+// CHECK-NEXT:      "range": {
+// CHECK-NEXT:       "begin": {
+// CHECK-NEXT:        "offset": 120,
+// CHECK-NEXT:        "line": 4,
+// CHECK-NEXT:        "col": 3,
+// CHECK-NEXT:        "tokLen": 1
+// CHECK-NEXT:       },
+// CHECK-NEXT:       "end": {
+// CHECK-NEXT:        "offset": 244,
+// CHECK-NEXT:        "line": 12,
+// CHECK-NEXT:        "col": 3,
+// CHECK-NEXT:        "tokLen": 1
+// CHECK-NEXT:       }
+// CHECK-NEXT:      },
+// CHECK-NEXT:      "name": "a",
+// CHECK-NEXT:      "declId": "0x{{.*}}",
+// CHECK-NEXT:      "inner": [
+// CHECK-NEXT:       {
+// CHECK-NEXT:        "id": "0x{{.*}}",
+// CHECK-NEXT:        "kind": "LabelStmt",
+// CHECK-NEXT:        "range": {
+// CHECK-NEXT:         "begin": {
+// CHECK-NEXT:          "offset": 123,
+// CHECK-NEXT:          "line": 4,
+// CHECK-NEXT:          "col": 6,
+// CHECK-NEXT:          "tokLen": 1
+// CHECK-NEXT:         },
+// CHECK-NEXT:         "end": {
+// CHECK-NEXT:          "offset": 244,
+// CHECK-NEXT:          "line": 12,
+// CHECK-NEXT:          "col": 3,
+// CHECK-NEXT:          "tokLen": 1
+// CHECK-NEXT:         }
+// CHECK-NEXT:        },
+// CHECK-NEXT:        "name": "b",
+// CHECK-NEXT:        "declId": "0x{{.*}}",
+// CHECK-NEXT:        "inner": [
+// CHECK-NEXT:         {
+// CHECK-NEXT:          "id": "0x{{.*}}",
+// CHECK-NEXT:          "kind": "WhileStmt",
+// CHECK-NEXT:          "range": {
+// CHECK-NEXT:           "begin": {
+// CHECK-NEXT:            "offset": 126,
+// CHECK-NEXT:            "line": 4,
+// CHECK-NEXT:            "col": 9,
+// CHECK-NEXT:            "tokLen": 5
+// CHECK-NEXT:           },
+// CHECK-NEXT:           "end": {
+// CHECK-NEXT:            "offset": 244,
+// CHECK-NEXT:            "line": 12,
+// CHECK-NEXT:            "col": 3,
+// CHECK-NEXT:            "tokLen": 1
+// CHECK-NEXT:           }
+// CHECK-NEXT:          },
+// CHECK-NEXT:          "inner": [
+// CHECK-NEXT:           {
+// CHECK-NEXT:            "id": "0x{{.*}}",
+// CHECK-NEXT:            "kind": "CXXBoolLiteralExpr",
+// CHECK-NEXT:            "range": {
+// CHECK-NEXT:             "begin": {
+// CHECK-NEXT:              "offset": 133,
+// CHECK-NEXT:              "line": 4,
+// CHECK-NEXT:              "col": 16,
+// CHECK-NEXT:              "tokLen": 4
+// CHECK-NEXT:             },
+// CHECK-NEXT:             "end": {
+// CHECK-NEXT:              "offset": 133,
+// CHECK-NEXT:              "col": 16,
+// CHECK-NEXT:              "tokLen": 4
+// CHECK-NEXT:             }
+// CHECK-NEXT:            },
+// CHECK-NEXT:            "type": {
+// CHECK-NEXT:             "qualType": "bool"
+// CHECK-NEXT:            },
+// CHECK-NEXT:            "valueCategory": "prvalue",
+// CHECK-NEXT:            "value": true
+// CHECK-NEXT:           },
+// CHECK-NEXT:           {
+// CHECK-NEXT:            "id": "0x{{.*}}",
+// CHECK-NEXT:            "kind": "CompoundStmt",
+// CHECK-NEXT:            "range": {
+// CHECK-NEXT:             "begin": {
+// CHECK-NEXT:              "offset": 139,
+// CHECK-NEXT:              "col": 22,
+// CHECK-NEXT:              "tokLen": 1
+// CHECK-NEXT:             },
+// CHECK-NEXT:             "end": {
+// CHECK-NEXT:              "offset": 244,
+// CHECK-NEXT:              "line": 12,
+// CHECK-NEXT:              "col": 3,
+// CHECK-NEXT:              "tokLen": 1
+// CHECK-NEXT:             }
+// CHECK-NEXT:            },
+// CHECK-NEXT:            "inner": [
+// CHECK-NEXT:             {
+// CHECK-NEXT:              "id": "0x{{.*}}",
+// CHECK-NEXT:              "kind": "BreakStmt",
+// CHECK-NEXT:              "range": {
+// CHECK-NEXT:               "begin": {
+// CHECK-NEXT:                "offset": 145,
+// CHECK-NEXT:                "line": 5,
+// CHECK-NEXT:                "col": 5,
+// CHECK-NEXT:                "tokLen": 5
+// CHECK-NEXT:               },
+// CHECK-NEXT:               "end": {
+// CHECK-NEXT:                "offset": 151,
+// CHECK-NEXT:                "col": 11,
+// CHECK-NEXT:                "tokLen": 1
+// CHECK-NEXT:               }
+// CHECK-NEXT:              },
+// CHECK-NEXT:              "targetLabelDeclId": "0x{{.*}}"
+// CHECK-NEXT:             },
+// CHECK-NEXT:             {
+// CHECK-NEXT:              "id": "0x{{.*}}",
+// CHECK-NEXT:              "kind": "ContinueStmt",
+// CHECK-NEXT:              "range": {
+// CHECK-NEXT:               "begin": {
+// CHECK-NEXT:                "offset": 158,
+// CHECK-NEXT:                "line": 6,
+// CHECK-NEXT:                "col": 5,
+// CHECK-NEXT:                "tokLen": 8
+// CHECK-NEXT:               },
+// CHECK-NEXT:               "end": {
+// CHECK-NEXT:                "offset": 167,
+// CHECK-NEXT:                "col": 14,
+// CHECK-NEXT:                "tokLen": 1
+// CHECK-NEXT:               }
+// CHECK-NEXT:              },
+// CHECK-NEXT:              "targetLabelDeclId": "0x{{.*}}"
+// CHECK-NEXT:             },
+// CHECK-NEXT:             {
+// CHECK-NEXT:              "id": "0x{{.*}}",
+// CHECK-NEXT:              "kind": "LabelStmt",
+// CHECK-NEXT:              "range": {
+// CHECK-NEXT:               "begin": {
+// CHECK-NEXT:                "offset": 174,
+// CHECK-NEXT:                "line": 7,
+// CHECK-NEXT:                "col": 5,
+// CHECK-NEXT:                "tokLen": 1
+// CHECK-NEXT:               },
+// CHECK-NEXT:               "end": {
+// CHECK-NEXT:                "offset": 240,
+// CHECK-NEXT:                "line": 11,
+// CHECK-NEXT:                "col": 5,
+// CHECK-NEXT:                "tokLen": 1
+// CHECK-NEXT:               }
+// CHECK-NEXT:              },
+// CHECK-NEXT:              "name": "c",
+// CHECK-NEXT:              "declId": "0x{{.*}}",
+// CHECK-NEXT:              "inner": [
+// CHECK-NEXT:               {
+// CHECK-NEXT:                "id": "0x{{.*}}",
+// CHECK-NEXT:                "kind": "ForStmt",
+// CHECK-NEXT:                "range": {
+// CHECK-NEXT:                 "begin": {
+// CHECK-NEXT:                  "offset": 177,
+// CHECK-NEXT:                  "line": 7,
+// CHECK-NEXT:                  "col": 8,
+// CHECK-NEXT:                  "tokLen": 3
+// CHECK-NEXT:                 },
+// CHECK-NEXT:                 "end": {
+// CHECK-NEXT:                  "offset": 240,
+// CHECK-NEXT:                  "line": 11,
+// CHECK-NEXT:                  "col": 5,
+// CHECK-NEXT:                  "tokLen": 1
+// CHECK-NEXT:                 }
+// CHECK-NEXT:                },
+// CHECK-NEXT:                "inner": [
+// CHECK-NEXT:                 {},
+// CHECK-NEXT:                 {},
+// CHECK-NEXT:                 {},
+// CHECK-NEXT:                 {},
+// CHECK-NEXT:                 {
+// CHECK-NEXT:                  "id": "0x{{.*}}",
+// CHECK-NEXT:                  "kind": "CompoundStmt",
+// CHECK-NEXT:                  "range": {
+// CHECK-NEXT:                   "begin": {
+// CHECK-NEXT:                    "offset": 186,
+// CHECK-NEXT:                    "line": 7,
+// CHECK-NEXT:                    "col": 17,
+// CHECK-NEXT:                    "tokLen": 1
+// CHECK-NEXT:                   },
+// CHECK-NEXT:                   "end": {
+// CHECK-NEXT:                    "offset": 240,
+// CHECK-NEXT:                    "line": 11,
+// CHECK-NEXT:                    "col": 5,
+// CHECK-NEXT:                    "tokLen": 1
+// CHECK-NEXT:                   }
+// CHECK-NEXT:                  },
+// CHECK-NEXT:                  "inner": [
+// CHECK-NEXT:                   {
+// CHECK-NEXT:                    "id": "0x{{.*}}",
+// CHECK-NEXT:                    "kind": "BreakStmt",
+// CHECK-NEXT:                    "range": {
+// CHECK-NEXT:                     "begin": {
+// CHECK-NEXT:                      "offset": 194,
+// CHECK-NEXT:                      "line": 8,
+// CHECK-NEXT:                      "col": 7,
+// CHECK-NEXT:                      "tokLen": 5
+// CHECK-NEXT:                     },
+// CHECK-NEXT:                     "end": {
+// CHECK-NEXT:                      "offset": 200,
+// CHECK-NEXT:                      "col": 13,
+// CHECK-NEXT:                      "tokLen": 1
+// CHECK-NEXT:                     }
+// CHECK-NEXT:                    },
+// CHECK-NEXT:                    "targetLabelDeclId": "0x{{.*}}"
+// CHECK-NEXT:                   },
+// CHECK-NEXT:                   {
+// CHECK-NEXT:                    "id": "0x{{.*}}",
+// CHECK-NEXT:                    "kind": "ContinueStmt",
+// CHECK-NEXT:                    "range": {
+// CHECK-NEXT:                     "begin": {
+// CHECK-NEXT:                      "offset": 209,
+// CHECK-NEXT:                      "line": 9,
+// CHECK-NEXT:                      "col": 7,
+// CHECK-NEXT:                      "tokLen": 8
+// CHECK-NEXT:                     },
+// CHECK-NEXT:                     "end": {
+// CHECK-NEXT:                      "offset": 218,
+// CHECK-NEXT:                      "col": 16,
+// CHECK-NEXT:                      "tokLen": 1
+// CHECK-NEXT:                     }
+// CHECK-NEXT:                    },
+// CHECK-NEXT:                    "targetLabelDeclId": "0x{{.*}}"
+// CHECK-NEXT:                   },
+// CHECK-NEXT:                   {
+// CHECK-NEXT:                    "id": "0x{{.*}}",
+// CHECK-NEXT:                    "kind": "BreakStmt",
+// CHECK-NEXT:                    "range": {
+// CHECK-NEXT:                     "begin": {
+// CHECK-NEXT:                      "offset": 227,
+// CHECK-NEXT:                      "line": 10,
+// CHECK-NEXT:                      "col": 7,
+// CHECK-NEXT:                      "tokLen": 5
+// CHECK-NEXT:                     },
+// CHECK-NEXT:                     "end": {
+// CHECK-NEXT:                      "offset": 233,
+// CHECK-NEXT:                      "col": 13,
+// CHECK-NEXT:                      "tokLen": 1
+// CHECK-NEXT:                     }
+// CHECK-NEXT:                    },
+// CHECK-NEXT:                    "targetLabelDeclId": "0x{{.*}}"
+// CHECK-NEXT:                   }
+// CHECK-NEXT:                  ]
+// CHECK-NEXT:                 }
+// CHECK-NEXT:                ]
+// CHECK-NEXT:               }
+// CHECK-NEXT:              ]
+// CHECK-NEXT:             }
+// CHECK-NEXT:            ]
+// CHECK-NEXT:           }
+// CHECK-NEXT:          ]
+// CHECK-NEXT:         }
+// CHECK-NEXT:        ]
+// CHECK-NEXT:       }
+// CHECK-NEXT:      ]
+// CHECK-NEXT:     }
+// CHECK-NEXT:    ]
+// CHECK-NEXT:   }
+// CHECK-NEXT:  ]
+// CHECK-NEXT: }
diff --git a/clang/test/AST/ast-dump-labeled-break-continue.c b/clang/test/AST/ast-dump-labeled-break-continue.c
new file mode 100644
index 0000000000000..7ef3c67460fe8
--- /dev/null
+++ b/clang/test/AST/ast-dump-labeled-break-continue.c
@@ -0,0 +1,41 @@
+// Test without serialization:
+// RUN: %clang_cc1 -std=c2y -ast-dump %s \
+// RUN: | FileCheck -strict-whitespace %s
+//
+// Test with serialization:
+// RUN: %clang_cc1 -std=c2y -emit-pch -o %t %s
+// RUN: %clang_cc1 -x c -std=c2y -include-pch %t -ast-dump-all /dev/null \
+// RUN: | sed -e "s/ <undeserialized declarations>//" -e "s/ imported//" \
+// RUN: | FileCheck -strict-whitespace %s
+
+void TestLabeledBreakContinue() {
+  a: b: while (true) {
+    break a;
+    continue b;
+    c: for (;;) {
+      break a;
+      continue b;
+      break c;
+    }
+  }
+}
+
+// CHECK-LABEL: `-FunctionDecl {{.*}} TestLabeledBreakContinue
+// CHECK-NEXT:   `-CompoundStmt {{.*}} <col:33, line:21:1>
+// CHECK-NEXT:     `-LabelStmt {{.*}} <line:12:3, line:20:3> 'a'
+// CHECK-NEXT:       `-LabelStmt {{.*}} <line:12:6, line:20:3> 'b'
+// CHECK-NEXT:         `-WhileStmt [[A:0x.*]] <line:12:9, line:20:3>
+// CHECK-NEXT:           |-CXXBoolLiteralExpr {{.*}} <line:12:16> 'bool' true
+// CHECK-NEXT:           `-CompoundStmt {{.*}} <col:22, line:20:3>
+// CHECK-NEXT:             |-BreakStmt {{.*}} <line:13:5, col:11> 'a' (WhileStmt [[A]])
+// CHECK-NEXT:             |-ContinueStmt {{.*}} <line:14:5, col:14> 'b' (WhileStmt [[A]])
+// CHECK-NEXT:             `-LabelStmt {{.*}} <line:15:5, line:19:5> 'c'
+// CHECK-NEXT:               `-ForStmt [[B:0x.*]] <line:15:8, line:19:5>
+// CHECK-NEXT:                 |-<<<NULL>>>
+// CHECK-NEXT:                 |-<<<NULL>>>
+// CHECK-NEXT:                 |-<<<NULL>>>
+// CHECK-NEXT:                 |-<<<NULL>>>
+// CHECK-NEXT:                 `-CompoundStmt {{.*}} <line:15:17, line:19:5>
+// CHECK-NEXT:                   |-BreakStmt {{.*}} <line:16:7, col:13> 'a' (WhileStmt [[A]])
+// CHECK-NEXT:                   |-ContinueStmt {{.*}} <line:17:7, col:16> 'b' (WhileStmt [[A]])
+// CHECK-NEXT:                   `-BreakStmt {{.*}} <line:18:7, col:13> 'c' (ForStmt [[B]])
diff --git a/clang/test/AST/ast-print-labeled-break-continue.c b/clang/test/AST/ast-print-labeled-break-continue.c
new file mode 100644
index 0000000000000..d6f5c42687c7c
--- /dev/null
+++ b/clang/test/AST/ast-print-labeled-break-continue.c
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -std=c2y -ast-print %s | FileCheck %s
+
+void TestLabeledBreakContinue() {
+  a: b: while (true) {
+    break a;
+    continue b;
+    c: for (;;) {
+      break a;
+      continue b;
+      break c;
+    }
+  }
+}
+
+// CHECK-LABEL: void TestLabeledBreakContinue(void) {
+// CHECK-NEXT:   a:
+// CHECK-NEXT:   b:
+// CHECK-NEXT:     while (true)
+// CHECK-NEXT:         {
+// CHECK-NEXT:             break a;
+// CHECK-NEXT:             continue b;
+// CHECK-NEXT:           c:
+// CHECK-NEXT:             for (;;) {
+// CHECK-NEXT:                 break a;
+// CHECK-NEXT:                 continue b;
+// CHECK-NEXT:                 break c;
+// CHECK-NEXT:             }
+// CHECK-NEXT:         }
+// CHECK-NEXT: }

>From 25fb4a3712593f3b66e5f202d06d57244884dcd3 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 18:00:34 +0200
Subject: [PATCH 14/19] clang-format

---
 clang/include/clang/AST/Stmt.h                    | 12 ++++++------
 clang/include/clang/Basic/DiagnosticParseKinds.td | 13 ++++++++-----
 clang/include/clang/Basic/DiagnosticSemaKinds.td  |  8 +++++---
 clang/include/clang/Sema/ScopeInfo.h              |  4 +---
 clang/lib/AST/ExprConstant.cpp                    |  5 ++---
 clang/lib/CodeGen/CGStmt.cpp                      |  5 +++--
 clang/lib/CodeGen/CodeGenFunction.h               |  2 +-
 clang/lib/Parse/ParseStmt.cpp                     |  3 +--
 clang/lib/Sema/JumpDiagnostics.cpp                | 15 +++++++++------
 clang/lib/Sema/SemaStmt.cpp                       |  3 +--
 clang/lib/Serialization/ASTReaderStmt.cpp         |  4 +---
 clang/lib/Tooling/Syntax/BuildTree.cpp            |  6 ++----
 12 files changed, 40 insertions(+), 40 deletions(-)

diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index b4c6752823eb0..45930eef4f91c 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -3049,7 +3049,7 @@ class IndirectGotoStmt : public Stmt {
 class LoopControlStmt : public Stmt {
   /// If this is a labeled break/continue, the label whose statement we're
   /// targeting.
-  LabelDecl* TargetLabel = nullptr;
+  LabelDecl *TargetLabel = nullptr;
 
   /// Location of the label, if any.
   SourceLocation Label;
@@ -3059,7 +3059,7 @@ class LoopControlStmt : public Stmt {
     setKwLoc(Loc);
   }
 
-  LoopControlStmt(StmtClass Class, EmptyShell ES): Stmt(Class, ES) {}
+  LoopControlStmt(StmtClass Class, EmptyShell ES) : Stmt(Class, ES) {}
 
 public:
   SourceLocation getKwLoc() const { return LoopControlStmtBits.KwLoc; }
@@ -3075,8 +3075,8 @@ class LoopControlStmt : public Stmt {
   SourceLocation getLabelLoc() const { return Label; }
   void setLabelLoc(SourceLocation L) { Label = L; }
 
-  LabelDecl* getLabelDecl() const { return TargetLabel; }
-  void setLabelDecl(LabelDecl* S) { TargetLabel = S; }
+  LabelDecl *getLabelDecl() const { return TargetLabel; }
+  void setLabelDecl(LabelDecl *S) { TargetLabel = S; }
 
   /// If this is a labeled break/continue, get the loop or switch statement
   /// that this targets.
@@ -3102,7 +3102,7 @@ class ContinueStmt : public LoopControlStmt {
 public:
   ContinueStmt(SourceLocation CL) : LoopControlStmt(ContinueStmtClass, CL) {}
   ContinueStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target)
-    : LoopControlStmt(ContinueStmtClass, CL) {
+      : LoopControlStmt(ContinueStmtClass, CL) {
     setLabelLoc(LabelLoc);
     setLabelDecl(Target);
   }
@@ -3121,7 +3121,7 @@ class BreakStmt : public LoopControlStmt {
 public:
   BreakStmt(SourceLocation BL) : LoopControlStmt(BreakStmtClass, BL) {}
   BreakStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target)
-    : LoopControlStmt(BreakStmtClass, CL) {
+      : LoopControlStmt(BreakStmtClass, CL) {
     setLabelLoc(LabelLoc);
     setLabelDecl(Target);
   }
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 8a124acfc771d..6f2498d3bc7c3 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -215,11 +215,14 @@ def warn_c23_compat_case_range : Warning<
   DefaultIgnore, InGroup<CPre2yCompat>;
 def ext_c2y_case_range : Extension<
   "case ranges are a C2y extension">, InGroup<C2y>;
-def warn_c2y_labeled_break_continue: Warning<
-  "labeled %select{'break'|'continue'}0 is incompatible with C standards before C2y">,
-  DefaultIgnore, InGroup<CPre2yCompat>;
-def ext_c2y_labeled_break_continue: Extension<
-  "labeled %select{'break'|'continue'}0 is a C2y extension">, InGroup<C2y>;
+def warn_c2y_labeled_break_continue
+    : Warning<"labeled %select{'break'|'continue'}0 is incompatible with C "
+              "standards before C2y">,
+      DefaultIgnore,
+      InGroup<CPre2yCompat>;
+def ext_c2y_labeled_break_continue
+    : Extension<"labeled %select{'break'|'continue'}0 is a C2y extension">,
+      InGroup<C2y>;
 
 // Generic errors.
 def err_expected_expression : Error<"expected expression">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 227849ca3d5a7..94647a033d497 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10796,9 +10796,11 @@ def err_continue_not_in_loop : Error<
   "'continue' statement not in loop statement">;
 def err_break_not_in_loop_or_switch : Error<
   "'break' statement not in loop or switch statement">;
-def err_break_continue_label_not_found: Error<
-    "'%select{continue|break}0' label does not name an enclosing %select{loop|loop or 'switch'}0">;
-def err_continue_switch: Error<"label of 'continue' refers to a switch statement">;
+def err_break_continue_label_not_found
+    : Error<"'%select{continue|break}0' label does not name an enclosing "
+            "%select{loop|loop or 'switch'}0">;
+def err_continue_switch
+    : Error<"label of 'continue' refers to a switch statement">;
 def warn_loop_ctrl_binds_to_inner : Warning<
   "'%0' is bound to current loop, GCC binds it to the enclosing loop">,
   InGroup<GccCompat>;
diff --git a/clang/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h
index 78f8de42c5f2b..2a46edc478591 100644
--- a/clang/include/clang/Sema/ScopeInfo.h
+++ b/clang/include/clang/Sema/ScopeInfo.h
@@ -440,9 +440,7 @@ class FunctionScopeInfo {
     HasBranchIntoScope = true;
   }
 
-  void setHasLabeledBreakOrContinue() {
-    HasLabeledBreakOrContinue = true;
-  }
+  void setHasLabeledBreakOrContinue() { HasLabeledBreakOrContinue = true; }
 
   void setHasBranchProtectedScope() {
     HasBranchProtectedScope = true;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 04594dd152b96..264153f7508d7 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5390,7 +5390,6 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
                                    const Stmt *S,
                                    const SwitchCase *SC = nullptr);
 
-
 /// Helper to implement labeled break/continue. Returns 'true' if the evaluation
 /// result should be propagated up. Otherwise, it sets the evaluation result
 /// to either Continue to continue the current loop, or Succeeded to break it.
@@ -5420,7 +5419,7 @@ static bool ShouldPropagateBreakContinue(EvalInfo &Info,
   }
 
   // We're not. Propagate the result up.
-  for (BlockScopeRAII* S : Scopes) {
+  for (BlockScopeRAII *S : Scopes) {
     if (!S->destroy()) {
       ESR = ESR_Failed;
       break;
@@ -5981,7 +5980,7 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
   case Stmt::BreakStmtClass: {
     auto *B = cast<LoopControlStmt>(S);
     Info.BreakContinueStack.push_back(B->isLabeled() ? B->getLabelTarget()
-                                                   : nullptr);
+                                                     : nullptr);
     return isa<ContinueStmt>(S) ? ESR_Continue : ESR_Break;
   }
 
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index ba1ed65f04063..70cb869b0f540 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -1732,13 +1732,14 @@ void CodeGenFunction::EmitDeclStmt(const DeclStmt &S) {
     EmitDecl(*I, /*EvaluateConditionDecl=*/true);
 }
 
-auto CodeGenFunction::GetDestForLoopControlStmt(const LoopControlStmt& S) -> const BreakContinue* {
+auto CodeGenFunction::GetDestForLoopControlStmt(const LoopControlStmt &S)
+    -> const BreakContinue * {
   if (!S.isLabeled())
     return &BreakContinueStack.back();
 
   Stmt *LoopOrSwitch = S.getLabelTarget();
   assert(LoopOrSwitch && "break/continue target not set?");
-  for (const BreakContinue& BC : llvm::reverse(BreakContinueStack))
+  for (const BreakContinue &BC : llvm::reverse(BreakContinueStack))
     if (BC.LoopOrSwitch == LoopOrSwitch)
       return &BC;
 
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index c16581d064048..86206b6042172 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3610,7 +3610,7 @@ class CodeGenFunction : public CodeGenTypeCache {
   void EmitCaseStmtRange(const CaseStmt &S, ArrayRef<const Attr *> Attrs);
   void EmitAsmStmt(const AsmStmt &S);
 
-  const BreakContinue *GetDestForLoopControlStmt(const LoopControlStmt& S);
+  const BreakContinue *GetDestForLoopControlStmt(const LoopControlStmt &S);
 
   void EmitObjCForCollectionStmt(const ObjCForCollectionStmt &S);
   void EmitObjCAtTryStmt(const ObjCAtTryStmt &S);
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 7f5599fbd577d..45b92b03ff3e1 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -2289,7 +2289,7 @@ StmtResult Parser::ParseGotoStatement() {
 }
 
 StmtResult Parser::ParseBreakOrContinueStatement(bool IsContinue) {
-  SourceLocation KwLoc = ConsumeToken();  // Eat the keyword.
+  SourceLocation KwLoc = ConsumeToken(); // Eat the keyword.
   SourceLocation LabelLoc;
   LabelDecl *Target = nullptr;
   if (Tok.is(tok::identifier)) {
@@ -2306,7 +2306,6 @@ StmtResult Parser::ParseBreakOrContinueStatement(bool IsContinue) {
   return Actions.ActOnBreakStmt(KwLoc, getCurScope(), Target, LabelLoc);
 }
 
-
 StmtResult Parser::ParseContinueStatement() {
   return ParseBreakOrContinueStatement(/*IsContinue=*/true);
 }
diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp
index b7e58c4170979..4fcdc4c084fb4 100644
--- a/clang/lib/Sema/JumpDiagnostics.cpp
+++ b/clang/lib/Sema/JumpDiagnostics.cpp
@@ -362,7 +362,8 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
     break;
 
   case Stmt::SwitchStmtClass: {
-    BuildScopeInformationForLoopOrSwitch(S, cast<SwitchStmt>(S)->getBody(), ParentScope);
+    BuildScopeInformationForLoopOrSwitch(S, cast<SwitchStmt>(S)->getBody(),
+                                         ParentScope);
     Jumps.push_back(S);
     return;
   }
@@ -382,7 +383,8 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
 
   case Stmt::BreakStmtClass:
   case Stmt::ContinueStmtClass:
-    if (cast<LoopControlStmt>(S)->isLabeled()) goto RecordJumpScope;
+    if (cast<LoopControlStmt>(S)->isLabeled())
+      goto RecordJumpScope;
     break;
 
   case Stmt::WhileStmtClass: {
@@ -683,7 +685,7 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
 
   for (Stmt *SubStmt : S->children()) {
     if (!SubStmt)
-        continue;
+      continue;
 
     // Cases, labels, attributes, and defaults aren't "scope parents".  It's also
     // important to handle these iteratively instead of recursively in
@@ -776,7 +778,7 @@ void JumpScopeChecker::VerifyJumps() {
       if (!isa<SwitchStmt, WhileStmt, ForStmt, DoStmt, CXXForRangeStmt,
                ObjCForCollectionStmt>(Target)) {
         S.Diag(L->getLabelLoc(), diag::err_break_continue_label_not_found)
-          << !IsContinue;
+            << !IsContinue;
         continue;
       }
 
@@ -1031,7 +1033,8 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
                                  unsigned JumpDiagCompat) {
   auto DiagnoseInvalidBreakInOpenACCComputeConstruct = [&](unsigned Scope) {
     auto GetParent = [&](unsigned S) -> unsigned {
-      if (S >= Scopes.size()) return S;
+      if (S >= Scopes.size())
+        return S;
       return Scopes[S].ParentScope;
     };
 
@@ -1106,7 +1109,7 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
   // Error if we're trying to break/continue out of a non-enclosing statement.
   if (auto L = dyn_cast<LoopControlStmt>(From)) {
     S.Diag(L->getLabelLoc(), diag::err_break_continue_label_not_found)
-          << isa<BreakStmt>(L);
+        << isa<BreakStmt>(L);
     return;
   }
 
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index f11e115689d5d..29b2ad3d5193c 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -3283,8 +3283,7 @@ static void CheckJumpOutOfSEHFinally(Sema &S, SourceLocation Loc,
 }
 
 StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
-                                   LabelDecl *Target,
-                                   SourceLocation LabelLoc) {
+                                   LabelDecl *Target, SourceLocation LabelLoc) {
   // We can only check this after we're done parsing label that this targets.
   if (Target) {
     getCurFunction()->setHasLabeledBreakOrContinue();
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 0e16619fa188e..76fdd4024b0b7 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -333,9 +333,7 @@ void ASTStmtReader::VisitContinueStmt(ContinueStmt *S) {
   VisitLoopControlStmt(S);
 }
 
-void ASTStmtReader::VisitBreakStmt(BreakStmt *S) {
-  VisitLoopControlStmt(S);
-}
+void ASTStmtReader::VisitBreakStmt(BreakStmt *S) { VisitLoopControlStmt(S); }
 
 void ASTStmtReader::VisitReturnStmt(ReturnStmt *S) {
   VisitStmt(S);
diff --git a/clang/lib/Tooling/Syntax/BuildTree.cpp b/clang/lib/Tooling/Syntax/BuildTree.cpp
index 7688e91dc09f1..0ebb6227db1a0 100644
--- a/clang/lib/Tooling/Syntax/BuildTree.cpp
+++ b/clang/lib/Tooling/Syntax/BuildTree.cpp
@@ -1483,16 +1483,14 @@ class BuildTreeVisitor : public RecursiveASTVisitor<BuildTreeVisitor> {
   }
 
   bool WalkUpFromContinueStmt(ContinueStmt *S) {
-    Builder.markChildToken(S->getKwLoc(),
-                           syntax::NodeRole::IntroducerKeyword);
+    Builder.markChildToken(S->getKwLoc(), syntax::NodeRole::IntroducerKeyword);
     Builder.foldNode(Builder.getStmtRange(S),
                      new (allocator()) syntax::ContinueStatement, S);
     return true;
   }
 
   bool WalkUpFromBreakStmt(BreakStmt *S) {
-    Builder.markChildToken(S->getKwLoc(),
-                           syntax::NodeRole::IntroducerKeyword);
+    Builder.markChildToken(S->getKwLoc(), syntax::NodeRole::IntroducerKeyword);
     Builder.foldNode(Builder.getStmtRange(S),
                      new (allocator()) syntax::BreakStatement, S);
     return true;

>From 7e418613f164c18fccc729b9ecdfe5de7e81dc0f Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 18:02:27 +0200
Subject: [PATCH 15/19] update grammar comment

---
 clang/include/clang/Parse/Parser.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 35e3f0c1917ec..7add07c79fc64 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7458,6 +7458,7 @@ class Parser : public CodeCompletionHandler {
   /// \verbatim
   ///       jump-statement:
   ///         'continue' ';'
+  /// [C2y]   'continue' identifier ';'
   /// \endverbatim
   ///
   /// Note: this lets the caller parse the end ';'.
@@ -7468,6 +7469,7 @@ class Parser : public CodeCompletionHandler {
   /// \verbatim
   ///       jump-statement:
   ///         'break' ';'
+  /// [C2y]   'break' identifier ';'
   /// \endverbatim
   ///
   /// Note: this lets the caller parse the end ';'.

>From 1f515b4bbff49bd7d7d1ad7eb934055b8fec5925 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 18:05:55 +0200
Subject: [PATCH 16/19] Add release note

---
 clang/docs/ReleaseNotes.rst | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 0e9fcaa5fac6a..4d8ea6a15e30b 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -103,6 +103,8 @@ C Language Changes
 
 C2y Feature Support
 ^^^^^^^^^^^^^^^^^^^
+- Clang now supports `N3355 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm>`_ Named Loops. This feature
+  is also available in earlier language modes and in C++ as an extension.
 
 C23 Feature Support
 ^^^^^^^^^^^^^^^^^^^

>From f8374d3767f59365c3e51ddeb5194df1adfbd0d4 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 18:14:04 +0200
Subject: [PATCH 17/19] Update docs

---
 clang/docs/LanguageExtensions.rst | 1 +
 clang/www/c_status.html           | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index b5bb198ca637a..7949a6adfb5f6 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1709,6 +1709,7 @@ Attributes (N2335)                                                             C
 ``#embed`` (N3017)                                                             C23           C89, C++
 Octal literals prefixed with ``0o`` or ``0O``                                  C2y           C89, C++
 ``_Countof`` (N3369, N3469)                                                    C2y           C89
+Named Loops (N3355)                                                            C2y           C89, C++
 ============================================= ================================ ============= =============
 
 Builtin type aliases
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index dcff2fc2b1a3e..e5597be9efcc7 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -252,7 +252,7 @@ <h2 id="c2y">C2y implementation status</h2>
     <tr>
       <td>Named loops, v3</td>
       <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm">N3355</a></td>
-      <td class="none" align="center">No</td>
+      <td class="unreleased" align="center">Clang 22</td>
     </tr>
     <!-- Graz Feb 2025 Papers -->
     <tr>

>From aaa117c3ad8a5f89323482b010048f4ea258d9b6 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 18:57:36 +0200
Subject: [PATCH 18/19] remove comment

---
 clang/lib/Sema/SemaStmt.cpp | 2 --
 1 file changed, 2 deletions(-)

diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 29b2ad3d5193c..253da543b0689 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -3284,7 +3284,6 @@ static void CheckJumpOutOfSEHFinally(Sema &S, SourceLocation Loc,
 
 StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
                                    LabelDecl *Target, SourceLocation LabelLoc) {
-  // We can only check this after we're done parsing label that this targets.
   if (Target) {
     getCurFunction()->setHasLabeledBreakOrContinue();
     return new (Context) ContinueStmt(ContinueLoc, LabelLoc, Target);
@@ -3318,7 +3317,6 @@ StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
 
 StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope,
                                 LabelDecl *Target, SourceLocation LabelLoc) {
-  // We can only check this after we're done parsing label that this targets.
   if (Target) {
     getCurFunction()->setHasLabeledBreakOrContinue();
     return new (Context) BreakStmt(BreakLoc, LabelLoc, Target);

>From 8b6feace8307eb4241ddccc5433d90afbba35bb0 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 9 Aug 2025 19:03:42 +0200
Subject: [PATCH 19/19] add another test case

---
 clang/test/Sema/labeled-break-continue.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/clang/test/Sema/labeled-break-continue.c b/clang/test/Sema/labeled-break-continue.c
index 27ddf1387a30e..16993af261f51 100644
--- a/clang/test/Sema/labeled-break-continue.c
+++ b/clang/test/Sema/labeled-break-continue.c
@@ -37,6 +37,10 @@ void f2() {
     break l2; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
     continue l2; // expected-error {{'continue' label does not name an enclosing loop}}
   }
+
+  break l3; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+  continue l3; // expected-error {{'continue' label does not name an enclosing loop}}
+  l3: while (true) {}
 }
 
 void f3() {



More information about the cfe-commits mailing list