[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