[clang] [clang-tools-extra] [Clang] [C2y] Implement N3355 ‘NamedLoops’ (PR #152870)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Aug 14 08:42:12 PDT 2025
https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/152870
>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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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/23] 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() {
>From f33869c6604fb9c55fedd40d96b1929719f15ed1 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Thu, 14 Aug 2025 15:56:10 +0200
Subject: [PATCH 20/23] Move checks out of jump checker
---
clang/include/clang/Parse/Parser.h | 18 +--
clang/include/clang/Sema/Scope.h | 18 +++
clang/include/clang/Sema/ScopeInfo.h | 13 +-
clang/include/clang/Sema/Sema.h | 4 +
clang/lib/Parse/ParseStmt.cpp | 58 ++++++---
clang/lib/Sema/JumpDiagnostics.cpp | 166 ++++--------------------
clang/lib/Sema/SemaLookup.cpp | 24 ++--
clang/lib/Sema/SemaStmt.cpp | 62 ++++++---
clang/lib/Sema/TreeTransform.h | 8 +-
clang/test/OpenMP/for_loop_messages.cpp | 7 +-
10 files changed, 161 insertions(+), 217 deletions(-)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 7add07c79fc64..c5bfb24e88968 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7213,7 +7213,8 @@ class Parser : public CodeCompletionHandler {
/// 'while', or 'for').
StmtResult
ParseStatement(SourceLocation *TrailingElseLoc = nullptr,
- ParsedStmtContext StmtCtx = ParsedStmtContext::SubStmt);
+ ParsedStmtContext StmtCtx = ParsedStmtContext::SubStmt,
+ LabelDecl *Name = nullptr);
/// ParseStatementOrDeclaration - Read 'statement' or 'declaration'.
/// \verbatim
@@ -7268,12 +7269,13 @@ class Parser : public CodeCompletionHandler {
///
StmtResult
ParseStatementOrDeclaration(StmtVector &Stmts, ParsedStmtContext StmtCtx,
- SourceLocation *TrailingElseLoc = nullptr);
+ SourceLocation *TrailingElseLoc = nullptr,
+ LabelDecl *Name = nullptr);
StmtResult ParseStatementOrDeclarationAfterAttributes(
StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc, ParsedAttributes &DeclAttrs,
- ParsedAttributes &DeclSpecAttrs);
+ ParsedAttributes &DeclSpecAttrs, LabelDecl *Name);
/// Parse an expression statement.
StmtResult ParseExprStatement(ParsedStmtContext StmtCtx);
@@ -7398,7 +7400,7 @@ class Parser : public CodeCompletionHandler {
/// 'switch' '(' expression ')' statement
/// [C++] 'switch' '(' condition ')' statement
/// \endverbatim
- StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc);
+ StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc, LabelDecl *Name);
/// ParseWhileStatement
/// \verbatim
@@ -7406,7 +7408,7 @@ class Parser : public CodeCompletionHandler {
/// 'while' '(' expression ')' statement
/// [C++] 'while' '(' condition ')' statement
/// \endverbatim
- StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc);
+ StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc, LabelDecl *Name);
/// ParseDoStatement
/// \verbatim
@@ -7414,7 +7416,7 @@ class Parser : public CodeCompletionHandler {
/// 'do' statement 'while' '(' expression ')' ';'
/// \endverbatim
/// Note: this lets the caller parse the end ';'.
- StmtResult ParseDoStatement();
+ StmtResult ParseDoStatement(LabelDecl *Name);
/// ParseForStatement
/// \verbatim
@@ -7441,7 +7443,7 @@ class Parser : public CodeCompletionHandler {
/// [C++0x] expression
/// [C++0x] braced-init-list [TODO]
/// \endverbatim
- StmtResult ParseForStatement(SourceLocation *TrailingElseLoc);
+ StmtResult ParseForStatement(SourceLocation *TrailingElseLoc, LabelDecl *Name);
/// ParseGotoStatement
/// \verbatim
@@ -7490,7 +7492,7 @@ class Parser : public CodeCompletionHandler {
StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc,
- ParsedAttributes &Attrs);
+ ParsedAttributes &Attrs, LabelDecl *Name);
void ParseMicrosoftIfExistsStatement(StmtVector &Stmts);
diff --git a/clang/include/clang/Sema/Scope.h b/clang/include/clang/Sema/Scope.h
index 757f3dcc3fe8d..db9a8c1890ad3 100644
--- a/clang/include/clang/Sema/Scope.h
+++ b/clang/include/clang/Sema/Scope.h
@@ -255,6 +255,10 @@ class Scope {
/// available for this variable in the current scope.
llvm::SmallPtrSet<VarDecl *, 8> ReturnSlots;
+ /// If this scope belongs to a loop or switch statement, the label that names
+ /// it, if any.
+ LabelDecl *LoopOrSwitchName = nullptr;
+
void setFlags(Scope *Parent, unsigned F);
public:
@@ -268,6 +272,14 @@ class Scope {
void setFlags(unsigned F) { setFlags(getParent(), F); }
+ /// Get the loop name of this scope.
+ LabelDecl *getLoopOrSwitchName() const { return LoopOrSwitchName; }
+ void setLoopOrSwitchName(LabelDecl *Name) {
+ assert((Flags & BreakScope || Flags & ContinueScope) &&
+ "not a loop or switch");
+ LoopOrSwitchName = Name;
+ }
+
/// isBlockScope - Return true if this scope correspond to a closure.
bool isBlockScope() const { return Flags & BlockScope; }
@@ -583,6 +595,12 @@ class Scope {
return getFlags() & ScopeFlags::ContinueScope;
}
+ /// Determine whether this is a scope which can have 'break' or 'continue'
+ /// statements embedded into it.
+ bool isBreakOrContinueScope() const {
+ return getFlags() & (ContinueScope | BreakScope);
+ }
+
/// Determine whether this scope is a C++ 'try' block.
bool isTryScope() const { return getFlags() & Scope::TryScope; }
diff --git a/clang/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h
index 2a46edc478591..94b247a689c2d 100644
--- a/clang/include/clang/Sema/ScopeInfo.h
+++ b/clang/include/clang/Sema/ScopeInfo.h
@@ -124,9 +124,6 @@ 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;
@@ -394,8 +391,7 @@ class FunctionScopeInfo {
public:
FunctionScopeInfo(DiagnosticsEngine &Diag)
: Kind(SK_Function), HasBranchProtectedScope(false),
- HasBranchIntoScope(false), HasIndirectGoto(false),
- HasLabeledBreakOrContinue(false), HasMustTail(false),
+ HasBranchIntoScope(false), HasIndirectGoto(false), HasMustTail(false),
HasDroppedStmt(false), HasOMPDeclareReductionCombiner(false),
HasFallthroughStmt(false), UsesFPIntrin(false),
HasPotentialAvailabilityViolations(false), ObjCShouldCallSuper(false),
@@ -440,8 +436,6 @@ class FunctionScopeInfo {
HasBranchIntoScope = true;
}
- void setHasLabeledBreakOrContinue() { HasLabeledBreakOrContinue = true; }
-
void setHasBranchProtectedScope() {
HasBranchProtectedScope = true;
}
@@ -491,9 +485,8 @@ class FunctionScopeInfo {
}
bool NeedsScopeChecking() const {
- return !HasDroppedStmt &&
- (HasIndirectGoto || HasMustTail || HasLabeledBreakOrContinue ||
- (HasBranchProtectedScope && HasBranchIntoScope));
+ return !HasDroppedStmt && (HasIndirectGoto || HasMustTail ||
+ (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 7db36a64679d3..29d698b2fb748 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -9479,6 +9479,10 @@ class Sema final : public SemaBase {
LabelDecl *LookupOrCreateLabel(IdentifierInfo *II, SourceLocation IdentLoc,
SourceLocation GnuLabelLoc = SourceLocation());
+ /// Perform a name lookup for a label with the specified name; this does not
+ /// create a new label if the lookup fails.
+ LabelDecl *LookupExistingLabel(IdentifierInfo *II, SourceLocation IdentLoc);
+
/// Look up the constructors for the given class.
DeclContextLookupResult LookupConstructors(CXXRecordDecl *Class);
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 45b92b03ff3e1..1815c09a601f0 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -37,14 +37,14 @@ using namespace clang;
//===----------------------------------------------------------------------===//
StmtResult Parser::ParseStatement(SourceLocation *TrailingElseLoc,
- ParsedStmtContext StmtCtx) {
+ ParsedStmtContext StmtCtx, LabelDecl *Name) {
StmtResult Res;
// We may get back a null statement if we found a #pragma. Keep going until
// we get an actual statement.
StmtVector Stmts;
do {
- Res = ParseStatementOrDeclaration(Stmts, StmtCtx, TrailingElseLoc);
+ Res = ParseStatementOrDeclaration(Stmts, StmtCtx, TrailingElseLoc, Name);
} while (!Res.isInvalid() && !Res.get());
return Res;
@@ -53,7 +53,7 @@ StmtResult Parser::ParseStatement(SourceLocation *TrailingElseLoc,
StmtResult
Parser::ParseStatementOrDeclaration(StmtVector &Stmts,
ParsedStmtContext StmtCtx,
- SourceLocation *TrailingElseLoc) {
+ SourceLocation *TrailingElseLoc, LabelDecl *Name) {
ParenBraceBracketBalancer BalancerRAIIObj(*this);
@@ -73,7 +73,7 @@ Parser::ParseStatementOrDeclaration(StmtVector &Stmts,
MaybeParseMicrosoftAttributes(GNUOrMSAttrs);
StmtResult Res = ParseStatementOrDeclarationAfterAttributes(
- Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUOrMSAttrs);
+ Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUOrMSAttrs, Name);
MaybeDestroyTemplateIds();
takeAndConcatenateAttrs(CXX11Attrs, std::move(GNUOrMSAttrs));
@@ -130,7 +130,7 @@ class StatementFilterCCC final : public CorrectionCandidateCallback {
StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc, ParsedAttributes &CXX11Attrs,
- ParsedAttributes &GNUAttrs) {
+ ParsedAttributes &GNUAttrs, LabelDecl *Name) {
const char *SemiError = nullptr;
StmtResult Res;
SourceLocation GNUAttributeLoc;
@@ -278,16 +278,16 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
case tok::kw_if: // C99 6.8.4.1: if-statement
return ParseIfStatement(TrailingElseLoc);
case tok::kw_switch: // C99 6.8.4.2: switch-statement
- return ParseSwitchStatement(TrailingElseLoc);
+ return ParseSwitchStatement(TrailingElseLoc, Name);
case tok::kw_while: // C99 6.8.5.1: while-statement
- return ParseWhileStatement(TrailingElseLoc);
+ return ParseWhileStatement(TrailingElseLoc, Name);
case tok::kw_do: // C99 6.8.5.2: do-statement
- Res = ParseDoStatement();
+ Res = ParseDoStatement(Name);
SemiError = "do/while";
break;
case tok::kw_for: // C99 6.8.5.3: for-statement
- return ParseForStatement(TrailingElseLoc);
+ return ParseForStatement(TrailingElseLoc, Name);
case tok::kw_goto: // C99 6.8.6.1: goto-statement
Res = ParseGotoStatement();
@@ -483,7 +483,7 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
case tok::annot_pragma_loop_hint:
ProhibitAttributes(CXX11Attrs);
ProhibitAttributes(GNUAttrs);
- return ParsePragmaLoopHint(Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs);
+ return ParsePragmaLoopHint(Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, Name);
case tok::annot_pragma_dump:
ProhibitAttributes(CXX11Attrs);
@@ -697,6 +697,9 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
// identifier ':' statement
SourceLocation ColonLoc = ConsumeToken();
+ LabelDecl *LD = Actions.LookupOrCreateLabel(IdentTok.getIdentifierInfo(),
+ IdentTok.getLocation());
+
// Read label attributes, if present.
StmtResult SubStmt;
if (Tok.is(tok::kw___attribute)) {
@@ -716,7 +719,7 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
StmtVector Stmts;
ParsedAttributes EmptyCXX11Attrs(AttrFactory);
SubStmt = ParseStatementOrDeclarationAfterAttributes(
- Stmts, StmtCtx, nullptr, EmptyCXX11Attrs, TempAttrs);
+ Stmts, StmtCtx, nullptr, EmptyCXX11Attrs, TempAttrs, LD);
if (!TempAttrs.empty() && !SubStmt.isInvalid())
SubStmt = Actions.ActOnAttributedStmt(TempAttrs, SubStmt.get());
}
@@ -730,7 +733,7 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
// If we've not parsed a statement yet, parse one now.
if (SubStmt.isUnset())
- SubStmt = ParseStatement(nullptr, StmtCtx);
+ SubStmt = ParseStatement(nullptr, StmtCtx, LD);
// Broken substmt shouldn't prevent the label from being added to the AST.
if (SubStmt.isInvalid())
@@ -738,8 +741,6 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
DiagnoseLabelFollowedByDecl(*this, SubStmt.get());
- LabelDecl *LD = Actions.LookupOrCreateLabel(IdentTok.getIdentifierInfo(),
- IdentTok.getLocation());
Actions.ProcessDeclAttributeList(Actions.CurScope, LD, Attrs);
Attrs.clear();
@@ -1620,7 +1621,8 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) {
ThenStmt.get(), ElseLoc, ElseStmt.get());
}
-StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc) {
+StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc,
+ LabelDecl *Name) {
assert(Tok.is(tok::kw_switch) && "Not a switch stmt!");
SourceLocation SwitchLoc = ConsumeToken(); // eat the 'switch'.
@@ -1686,6 +1688,7 @@ StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc) {
// condition and a new scope for substatement in C++.
//
getCurScope()->AddFlags(Scope::BreakScope);
+ getCurScope()->setLoopOrSwitchName(Name);
ParseScope InnerScope(this, Scope::DeclScope, C99orCXX, Tok.is(tok::l_brace));
// We have incremented the mangling number for the SwitchScope and the
@@ -1703,7 +1706,7 @@ StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc) {
return Actions.ActOnFinishSwitchStmt(SwitchLoc, Switch.get(), Body.get());
}
-StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc) {
+StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, LabelDecl *Name) {
assert(Tok.is(tok::kw_while) && "Not a while stmt!");
SourceLocation WhileLoc = Tok.getLocation();
ConsumeToken(); // eat the 'while'.
@@ -1748,6 +1751,7 @@ StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc) {
// combinations, so diagnose that here in OpenACC mode.
SemaOpenACC::LoopInConstructRAII LCR{getActions().OpenACC()};
getActions().OpenACC().ActOnWhileStmt(WhileLoc);
+ getCurScope()->setLoopOrSwitchName(Name);
// C99 6.8.5p5 - In C99, the body of the while statement is a scope, even if
// there is no compound stmt. C90 does not have this clause. We only do this
@@ -1779,7 +1783,7 @@ StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc) {
return Actions.ActOnWhileStmt(WhileLoc, LParen, Cond, RParen, Body.get());
}
-StmtResult Parser::ParseDoStatement() {
+StmtResult Parser::ParseDoStatement(LabelDecl *Name) {
assert(Tok.is(tok::kw_do) && "Not a do stmt!");
SourceLocation DoLoc = ConsumeToken(); // eat the 'do'.
@@ -1797,6 +1801,7 @@ StmtResult Parser::ParseDoStatement() {
// combinations, so diagnose that here in OpenACC mode.
SemaOpenACC::LoopInConstructRAII LCR{getActions().OpenACC()};
getActions().OpenACC().ActOnDoStmt(DoLoc);
+ getCurScope()->setLoopOrSwitchName(Name);
// C99 6.8.5p5 - In C99, the body of the do statement is a scope, even if
// there is no compound stmt. C90 does not have this clause. We only do this
@@ -1815,6 +1820,9 @@ StmtResult Parser::ParseDoStatement() {
// Pop the body scope if needed.
InnerScope.Exit();
+ // Reset this to disallow break/continue out of the condition.
+ getCurScope()->setLoopOrSwitchName(nullptr);
+
if (Tok.isNot(tok::kw_while)) {
if (!Body.isInvalid()) {
Diag(Tok, diag::err_expected_while);
@@ -1876,7 +1884,7 @@ bool Parser::isForRangeIdentifier() {
return false;
}
-StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) {
+StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc, LabelDecl *Name) {
assert(Tok.is(tok::kw_for) && "Not a for stmt!");
SourceLocation ForLoc = ConsumeToken(); // eat the 'for'.
@@ -2208,6 +2216,10 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) {
getActions().OpenACC().ActOnForStmtBegin(
ForLoc, FirstPart.get(), SecondPart.get().second, ThirdPart.get());
+ // Set this only right before parsing the body to disallow break/continue in
+ // the other parts.
+ getCurScope()->setLoopOrSwitchName(Name);
+
// C99 6.8.5p5 - In C99, the body of the for statement is a scope, even if
// there is no compound stmt. C90 does not have this clause. We only do this
// if the body isn't a compound statement to avoid push/pop in common cases.
@@ -2294,11 +2306,15 @@ StmtResult Parser::ParseBreakOrContinueStatement(bool IsContinue) {
LabelDecl *Target = nullptr;
if (Tok.is(tok::identifier)) {
Target =
- Actions.LookupOrCreateLabel(Tok.getIdentifierInfo(), Tok.getLocation());
+ Actions.LookupExistingLabel(Tok.getIdentifierInfo(), Tok.getLocation());
LabelLoc = ConsumeToken();
Diag(LabelLoc, getLangOpts().C2y ? diag::warn_c2y_labeled_break_continue
: diag::ext_c2y_labeled_break_continue)
<< IsContinue;
+ if (!Target) {
+ Diag(LabelLoc, diag::err_break_continue_label_not_found) << !IsContinue;
+ return StmtError();
+ }
}
if (IsContinue)
@@ -2355,7 +2371,7 @@ StmtResult Parser::ParseReturnStatement() {
StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts,
ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc,
- ParsedAttributes &Attrs) {
+ ParsedAttributes &Attrs, LabelDecl *Name) {
// Create temporary attribute list.
ParsedAttributes TempAttrs(AttrFactory);
@@ -2379,7 +2395,7 @@ StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts,
ParsedAttributes EmptyDeclSpecAttrs(AttrFactory);
StmtResult S = ParseStatementOrDeclarationAfterAttributes(
- Stmts, StmtCtx, TrailingElseLoc, Attrs, EmptyDeclSpecAttrs);
+ Stmts, StmtCtx, TrailingElseLoc, Attrs, EmptyDeclSpecAttrs, Name);
Attrs.takeAllFrom(TempAttrs);
diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp
index 4fcdc4c084fb4..36704c3826dfd 100644
--- a/clang/lib/Sema/JumpDiagnostics.cpp
+++ b/clang/lib/Sema/JumpDiagnostics.cpp
@@ -84,9 +84,6 @@ class JumpScopeChecker {
unsigned &ParentScope);
void BuildScopeInformation(CompoundLiteralExpr *CLE, unsigned &ParentScope);
void BuildScopeInformation(Stmt *S, unsigned &origParentScope);
- void BuildScopeInformationForLoopOrSwitch(Stmt *S, Stmt *Body,
- unsigned &ParentScope,
- unsigned InDiag = 0);
void VerifyJumps();
void VerifyIndirectJumps();
@@ -299,28 +296,6 @@ 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, unsigned InDiag) {
- for (Stmt *Child : S->children()) {
- if (!Child || Child == Body)
- continue;
- BuildScopeInformation(Child, ParentScope);
- }
-
- unsigned NewParentScope = Scopes.size();
- Scopes.push_back(GotoScope(ParentScope, InDiag, 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
@@ -334,6 +309,8 @@ 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:
@@ -342,9 +319,10 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
case Stmt::ObjCForCollectionStmtClass: {
auto *CS = cast<ObjCForCollectionStmt>(S);
- BuildScopeInformationForLoopOrSwitch(
- S, CS->getBody(), ParentScope,
- diag::note_protected_by_objc_fast_enumeration);
+ 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);
return;
}
@@ -361,12 +339,18 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
IndirectJumps.push_back(S);
break;
- case Stmt::SwitchStmtClass: {
- BuildScopeInformationForLoopOrSwitch(S, cast<SwitchStmt>(S)->getBody(),
- ParentScope);
- Jumps.push_back(S);
- return;
- }
+ 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::GCCAsmStmtClass:
if (!cast<GCCAsmStmt>(S)->isAsmGoto())
@@ -381,36 +365,6 @@ 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::CXXForRangeStmtClass: {
- BuildScopeInformationForLoopOrSwitch(S, cast<CXXForRangeStmt>(S)->getBody(),
- ParentScope);
- return;
- }
-
case Stmt::IfStmtClass: {
IfStmt *IS = cast<IfStmt>(S);
if (!(IS->isConstexpr() || IS->isConsteval() ||
@@ -685,7 +639,11 @@ 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
@@ -763,34 +721,6 @@ 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()) {
@@ -1031,28 +961,6 @@ 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)))
@@ -1061,18 +969,14 @@ 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 usually fine.
- if (FromScope == ToScope) {
- DiagnoseInvalidBreakInOpenACCComputeConstruct(ToScope);
- return;
- }
+ // Common case: exactly the same scope, which is fine.
+ if (FromScope == ToScope) return;
// Warn on gotos out of __finally blocks.
- if (isa<GotoStmt, IndirectGotoStmt, LoopControlStmt>(From)) {
+ if (isa<GotoStmt>(From) || isa<IndirectGotoStmt>(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.
- unsigned I = FromScope;
- for (; I > ToScope; I = Scopes[I].ParentScope) {
+ for (unsigned I = FromScope; 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;
@@ -1083,22 +987,11 @@ 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);
@@ -1106,13 +999,6 @@ 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/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index dc73dedfb5598..d5dea3512c6d7 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -4455,26 +4455,28 @@ void Sema::LookupVisibleDecls(DeclContext *Ctx, LookupNameKind Kind,
H.lookupVisibleDecls(*this, Ctx, Kind, IncludeGlobalScope);
}
+LabelDecl *Sema::LookupExistingLabel(IdentifierInfo *II, SourceLocation Loc) {
+ NamedDecl *Res = LookupSingleName(CurScope, II, Loc, LookupLabel,
+ RedeclarationKind::NotForRedeclaration);
+ // If we found a label, check to see if it is in the same context as us.
+ // When in a Block, we don't want to reuse a label in an enclosing function.
+ if (!Res || Res->getDeclContext() != CurContext)
+ return nullptr;
+ return cast<LabelDecl>(Res);
+}
+
LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
SourceLocation GnuLabelLoc) {
- // Do a lookup to see if we have a label with this name already.
- NamedDecl *Res = nullptr;
-
if (GnuLabelLoc.isValid()) {
// Local label definitions always shadow existing labels.
- Res = LabelDecl::Create(Context, CurContext, Loc, II, GnuLabelLoc);
+ auto *Res = LabelDecl::Create(Context, CurContext, Loc, II, GnuLabelLoc);
Scope *S = CurScope;
PushOnScopeChains(Res, S, true);
return cast<LabelDecl>(Res);
}
// Not a GNU local label.
- Res = LookupSingleName(CurScope, II, Loc, LookupLabel,
- RedeclarationKind::NotForRedeclaration);
- // If we found a label, check to see if it is in the same context as us.
- // When in a Block, we don't want to reuse a label in an enclosing function.
- if (Res && Res->getDeclContext() != CurContext)
- Res = nullptr;
+ LabelDecl *Res = LookupExistingLabel(II, Loc);
if (!Res) {
// If not forward referenced or defined already, create the backing decl.
Res = LabelDecl::Create(Context, CurContext, Loc, II);
@@ -4482,7 +4484,7 @@ LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
assert(S && "Not in a function?");
PushOnScopeChains(Res, S, true);
}
- return cast<LabelDecl>(Res);
+ return Res;
}
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 253da543b0689..f286c40ee914c 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -3282,15 +3282,45 @@ static void CheckJumpOutOfSEHFinally(Sema &S, SourceLocation Loc,
}
}
+Scope *FindLabeledBreakContinueScope(Sema &S, Scope *CurScope,
+ LabelDecl *Target, SourceLocation LabelLoc,
+ bool IsBreak) {
+ for (Scope* Scope = CurScope; Scope; Scope = Scope->getParent()) {
+ if (Scope->isFunctionScope()) {
+ S.Diag(LabelLoc, diag::err_break_continue_label_not_found) << IsBreak;
+ return nullptr;
+ }
+
+ if (Scope->getLoopOrSwitchName() != Target)
+ continue;
+
+ if (!Scope->isBreakOrContinueScope()) {
+ S.Diag(LabelLoc, diag::err_break_continue_label_not_found) << IsBreak;
+ return nullptr;
+ }
+
+ if (!IsBreak && !Scope->isContinueScope()) {
+ S.Diag(LabelLoc, diag::err_continue_switch);
+ return nullptr;
+ }
+
+ return Scope;
+ }
+ return nullptr;
+}
+
StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
LabelDecl *Target, SourceLocation LabelLoc) {
+ Scope *S;
if (Target) {
- getCurFunction()->setHasLabeledBreakOrContinue();
- return new (Context) ContinueStmt(ContinueLoc, LabelLoc, Target);
+ S = FindLabeledBreakContinueScope(*this, CurScope, Target, LabelLoc,
+ /*IsBreak=*/false);
+ if (!S)
+ return StmtError();
+ } else {
+ S = CurScope->getContinueParent();
}
- 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.
return StmtError(Diag(ContinueLoc, diag::err_continue_not_in_loop));
@@ -3312,32 +3342,26 @@ StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
CheckJumpOutOfSEHFinally(*this, ContinueLoc, *S);
- return new (Context) ContinueStmt(ContinueLoc);
+ return new (Context) ContinueStmt(ContinueLoc, LabelLoc, Target);
}
StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope,
LabelDecl *Target, SourceLocation LabelLoc) {
+ Scope *S;
if (Target) {
- getCurFunction()->setHasLabeledBreakOrContinue();
- return new (Context) BreakStmt(BreakLoc, LabelLoc, Target);
+ S = FindLabeledBreakContinueScope(*this, CurScope, Target, LabelLoc,
+ /*IsBreak=*/true);
+ if (!S)
+ return StmtError();
+ } else {
+ S = CurScope->getBreakParent();
}
- 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.
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");
@@ -3358,7 +3382,7 @@ StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope,
CheckJumpOutOfSEHFinally(*this, BreakLoc, *S);
- return new (Context) BreakStmt(BreakLoc);
+ return new (Context) BreakStmt(BreakLoc, LabelLoc, Target);
}
Sema::NamedReturnInfo Sema::getNamedReturnInfo(Expr *&E,
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index cc01f32a7e724..1326c652b2828 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -8561,8 +8561,8 @@ TreeTransform<Derived>::TransformContinueStmt(ContinueStmt *S) {
if (!LD)
return StmtError();
- return SemaRef.ActOnContinueStmt(S->getKwLoc(), /*CurScope=*/nullptr,
- cast<LabelDecl>(LD), S->getLabelLoc());
+ return new (SemaRef.Context)
+ ContinueStmt(S->getKwLoc(), S->getLabelLoc(), cast<LabelDecl>(LD));
}
template<typename Derived>
@@ -8576,8 +8576,8 @@ TreeTransform<Derived>::TransformBreakStmt(BreakStmt *S) {
if (!LD)
return StmtError();
- return SemaRef.ActOnBreakStmt(S->getKwLoc(), /*CurScope=*/nullptr,
- cast<LabelDecl>(LD), S->getLabelLoc());
+ return new (SemaRef.Context)
+ BreakStmt(S->getKwLoc(), S->getLabelLoc(), cast<LabelDecl>(LD));
}
template<typename Derived>
diff --git a/clang/test/OpenMP/for_loop_messages.cpp b/clang/test/OpenMP/for_loop_messages.cpp
index ac0b4f982e5d3..42513a72cc04b 100644
--- a/clang/test/OpenMP/for_loop_messages.cpp
+++ b/clang/test/OpenMP/for_loop_messages.cpp
@@ -843,13 +843,12 @@ 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;
+ break a; // expected-error {{'break' statement cannot be used in OpenMP for loop}}
continue a;
}
@@ -857,8 +856,8 @@ void test_labeled_break() {
#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'}}
+ break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+ continue c; // expected-error {{'continue' label does not name an enclosing loop}}
}
}
}
>From da3b554630694ac04d5fd315dba9dd4528aabd49 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Thu, 14 Aug 2025 16:36:15 +0200
Subject: [PATCH 21/23] Support multiple labels
---
clang/include/clang/Parse/Parser.h | 18 +++---
clang/include/clang/Sema/Scope.h | 11 ++--
clang/lib/Parse/ParseStmt.cpp | 63 +++++++++++--------
clang/lib/Sema/Scope.cpp | 1 +
clang/lib/Sema/SemaStmt.cpp | 36 ++++++-----
clang/test/Sema/labeled-break-continue.c | 10 +--
clang/test/SemaCXX/labeled-break-continue.cpp | 4 +-
clang/test/SemaObjC/labeled-break-continue.m | 4 +-
8 files changed, 83 insertions(+), 64 deletions(-)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index c5bfb24e88968..4e49b35c4d02a 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7214,7 +7214,7 @@ class Parser : public CodeCompletionHandler {
StmtResult
ParseStatement(SourceLocation *TrailingElseLoc = nullptr,
ParsedStmtContext StmtCtx = ParsedStmtContext::SubStmt,
- LabelDecl *Name = nullptr);
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames = nullptr);
/// ParseStatementOrDeclaration - Read 'statement' or 'declaration'.
/// \verbatim
@@ -7270,12 +7270,12 @@ class Parser : public CodeCompletionHandler {
StmtResult
ParseStatementOrDeclaration(StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc = nullptr,
- LabelDecl *Name = nullptr);
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames = nullptr);
StmtResult ParseStatementOrDeclarationAfterAttributes(
StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc, ParsedAttributes &DeclAttrs,
- ParsedAttributes &DeclSpecAttrs, LabelDecl *Name);
+ ParsedAttributes &DeclSpecAttrs, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
/// Parse an expression statement.
StmtResult ParseExprStatement(ParsedStmtContext StmtCtx);
@@ -7292,7 +7292,7 @@ class Parser : public CodeCompletionHandler {
/// \endverbatim
///
StmtResult ParseLabeledStatement(ParsedAttributes &Attrs,
- ParsedStmtContext StmtCtx);
+ ParsedStmtContext StmtCtx, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
/// ParseCaseStatement
/// \verbatim
@@ -7400,7 +7400,7 @@ class Parser : public CodeCompletionHandler {
/// 'switch' '(' expression ')' statement
/// [C++] 'switch' '(' condition ')' statement
/// \endverbatim
- StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc, LabelDecl *Name);
+ StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
/// ParseWhileStatement
/// \verbatim
@@ -7408,7 +7408,7 @@ class Parser : public CodeCompletionHandler {
/// 'while' '(' expression ')' statement
/// [C++] 'while' '(' condition ')' statement
/// \endverbatim
- StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc, LabelDecl *Name);
+ StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
/// ParseDoStatement
/// \verbatim
@@ -7416,7 +7416,7 @@ class Parser : public CodeCompletionHandler {
/// 'do' statement 'while' '(' expression ')' ';'
/// \endverbatim
/// Note: this lets the caller parse the end ';'.
- StmtResult ParseDoStatement(LabelDecl *Name);
+ StmtResult ParseDoStatement(SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
/// ParseForStatement
/// \verbatim
@@ -7443,7 +7443,7 @@ class Parser : public CodeCompletionHandler {
/// [C++0x] expression
/// [C++0x] braced-init-list [TODO]
/// \endverbatim
- StmtResult ParseForStatement(SourceLocation *TrailingElseLoc, LabelDecl *Name);
+ StmtResult ParseForStatement(SourceLocation *TrailingElseLoc, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
/// ParseGotoStatement
/// \verbatim
@@ -7492,7 +7492,7 @@ class Parser : public CodeCompletionHandler {
StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc,
- ParsedAttributes &Attrs, LabelDecl *Name);
+ ParsedAttributes &Attrs, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
void ParseMicrosoftIfExistsStatement(StmtVector &Stmts);
diff --git a/clang/include/clang/Sema/Scope.h b/clang/include/clang/Sema/Scope.h
index db9a8c1890ad3..67de11f043676 100644
--- a/clang/include/clang/Sema/Scope.h
+++ b/clang/include/clang/Sema/Scope.h
@@ -257,7 +257,7 @@ class Scope {
/// If this scope belongs to a loop or switch statement, the label that names
/// it, if any.
- LabelDecl *LoopOrSwitchName = nullptr;
+ ArrayRef<LabelDecl *> LoopOrSwitchNames;
void setFlags(Scope *Parent, unsigned F);
@@ -273,11 +273,14 @@ class Scope {
void setFlags(unsigned F) { setFlags(getParent(), F); }
/// Get the loop name of this scope.
- LabelDecl *getLoopOrSwitchName() const { return LoopOrSwitchName; }
- void setLoopOrSwitchName(LabelDecl *Name) {
+ ArrayRef<LabelDecl *> getLoopOrSwitchNames() const {
+ return LoopOrSwitchNames;
+ }
+
+ void setLoopOrSwitchNames(ArrayRef<LabelDecl *> Names) {
assert((Flags & BreakScope || Flags & ContinueScope) &&
"not a loop or switch");
- LoopOrSwitchName = Name;
+ LoopOrSwitchNames = Names;
}
/// isBlockScope - Return true if this scope correspond to a closure.
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 1815c09a601f0..de70ed7a96ede 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -37,14 +37,14 @@ using namespace clang;
//===----------------------------------------------------------------------===//
StmtResult Parser::ParseStatement(SourceLocation *TrailingElseLoc,
- ParsedStmtContext StmtCtx, LabelDecl *Name) {
+ ParsedStmtContext StmtCtx, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
StmtResult Res;
// We may get back a null statement if we found a #pragma. Keep going until
// we get an actual statement.
StmtVector Stmts;
do {
- Res = ParseStatementOrDeclaration(Stmts, StmtCtx, TrailingElseLoc, Name);
+ Res = ParseStatementOrDeclaration(Stmts, StmtCtx, TrailingElseLoc, LoopOrSwitchNames);
} while (!Res.isInvalid() && !Res.get());
return Res;
@@ -53,7 +53,7 @@ StmtResult Parser::ParseStatement(SourceLocation *TrailingElseLoc,
StmtResult
Parser::ParseStatementOrDeclaration(StmtVector &Stmts,
ParsedStmtContext StmtCtx,
- SourceLocation *TrailingElseLoc, LabelDecl *Name) {
+ SourceLocation *TrailingElseLoc, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
ParenBraceBracketBalancer BalancerRAIIObj(*this);
@@ -73,7 +73,7 @@ Parser::ParseStatementOrDeclaration(StmtVector &Stmts,
MaybeParseMicrosoftAttributes(GNUOrMSAttrs);
StmtResult Res = ParseStatementOrDeclarationAfterAttributes(
- Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUOrMSAttrs, Name);
+ Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUOrMSAttrs, LoopOrSwitchNames);
MaybeDestroyTemplateIds();
takeAndConcatenateAttrs(CXX11Attrs, std::move(GNUOrMSAttrs));
@@ -130,7 +130,7 @@ class StatementFilterCCC final : public CorrectionCandidateCallback {
StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc, ParsedAttributes &CXX11Attrs,
- ParsedAttributes &GNUAttrs, LabelDecl *Name) {
+ ParsedAttributes &GNUAttrs, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
const char *SemiError = nullptr;
StmtResult Res;
SourceLocation GNUAttributeLoc;
@@ -164,7 +164,7 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
takeAndConcatenateAttrs(CXX11Attrs, std::move(GNUAttrs));
// identifier ':' statement
- return ParseLabeledStatement(CXX11Attrs, StmtCtx);
+ return ParseLabeledStatement(CXX11Attrs, StmtCtx, LoopOrSwitchNames);
}
// Look up the identifier, and typo-correct it to a keyword if it's not
@@ -278,16 +278,16 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
case tok::kw_if: // C99 6.8.4.1: if-statement
return ParseIfStatement(TrailingElseLoc);
case tok::kw_switch: // C99 6.8.4.2: switch-statement
- return ParseSwitchStatement(TrailingElseLoc, Name);
+ return ParseSwitchStatement(TrailingElseLoc, LoopOrSwitchNames);
case tok::kw_while: // C99 6.8.5.1: while-statement
- return ParseWhileStatement(TrailingElseLoc, Name);
+ return ParseWhileStatement(TrailingElseLoc, LoopOrSwitchNames);
case tok::kw_do: // C99 6.8.5.2: do-statement
- Res = ParseDoStatement(Name);
+ Res = ParseDoStatement(LoopOrSwitchNames);
SemiError = "do/while";
break;
case tok::kw_for: // C99 6.8.5.3: for-statement
- return ParseForStatement(TrailingElseLoc, Name);
+ return ParseForStatement(TrailingElseLoc, LoopOrSwitchNames);
case tok::kw_goto: // C99 6.8.6.1: goto-statement
Res = ParseGotoStatement();
@@ -483,7 +483,7 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
case tok::annot_pragma_loop_hint:
ProhibitAttributes(CXX11Attrs);
ProhibitAttributes(GNUAttrs);
- return ParsePragmaLoopHint(Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, Name);
+ return ParsePragmaLoopHint(Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, LoopOrSwitchNames);
case tok::annot_pragma_dump:
ProhibitAttributes(CXX11Attrs);
@@ -679,11 +679,17 @@ static void DiagnoseLabelFollowedByDecl(Parser &P, const Stmt *SubStmt) {
}
}
-StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
- ParsedStmtContext StmtCtx) {
+StmtResult
+Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
+ ParsedStmtContext StmtCtx,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
assert(Tok.is(tok::identifier) && Tok.getIdentifierInfo() &&
"Not an identifier!");
+ SmallVector<LabelDecl *, 1> LoopOrSwitchNamesStorage;
+ if (!LoopOrSwitchNames)
+ LoopOrSwitchNames = &LoopOrSwitchNamesStorage;
+
// [OpenMP 5.1] 2.1.3: A stand-alone directive may not be used in place of a
// substatement in a selection statement, in place of the loop body in an
// iteration statement, or in place of the statement that follows a label.
@@ -699,6 +705,7 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
LabelDecl *LD = Actions.LookupOrCreateLabel(IdentTok.getIdentifierInfo(),
IdentTok.getLocation());
+ LoopOrSwitchNames->push_back(LD);
// Read label attributes, if present.
StmtResult SubStmt;
@@ -719,7 +726,7 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
StmtVector Stmts;
ParsedAttributes EmptyCXX11Attrs(AttrFactory);
SubStmt = ParseStatementOrDeclarationAfterAttributes(
- Stmts, StmtCtx, nullptr, EmptyCXX11Attrs, TempAttrs, LD);
+ Stmts, StmtCtx, nullptr, EmptyCXX11Attrs, TempAttrs, LoopOrSwitchNames);
if (!TempAttrs.empty() && !SubStmt.isInvalid())
SubStmt = Actions.ActOnAttributedStmt(TempAttrs, SubStmt.get());
}
@@ -733,7 +740,7 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
// If we've not parsed a statement yet, parse one now.
if (SubStmt.isUnset())
- SubStmt = ParseStatement(nullptr, StmtCtx, LD);
+ SubStmt = ParseStatement(nullptr, StmtCtx, LoopOrSwitchNames);
// Broken substmt shouldn't prevent the label from being added to the AST.
if (SubStmt.isInvalid())
@@ -1622,7 +1629,7 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) {
}
StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc,
- LabelDecl *Name) {
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
assert(Tok.is(tok::kw_switch) && "Not a switch stmt!");
SourceLocation SwitchLoc = ConsumeToken(); // eat the 'switch'.
@@ -1688,7 +1695,8 @@ StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc,
// condition and a new scope for substatement in C++.
//
getCurScope()->AddFlags(Scope::BreakScope);
- getCurScope()->setLoopOrSwitchName(Name);
+ if (LoopOrSwitchNames)
+ getCurScope()->setLoopOrSwitchNames(*LoopOrSwitchNames);
ParseScope InnerScope(this, Scope::DeclScope, C99orCXX, Tok.is(tok::l_brace));
// We have incremented the mangling number for the SwitchScope and the
@@ -1706,7 +1714,7 @@ StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc,
return Actions.ActOnFinishSwitchStmt(SwitchLoc, Switch.get(), Body.get());
}
-StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, LabelDecl *Name) {
+StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
assert(Tok.is(tok::kw_while) && "Not a while stmt!");
SourceLocation WhileLoc = Tok.getLocation();
ConsumeToken(); // eat the 'while'.
@@ -1751,7 +1759,8 @@ StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, LabelDec
// combinations, so diagnose that here in OpenACC mode.
SemaOpenACC::LoopInConstructRAII LCR{getActions().OpenACC()};
getActions().OpenACC().ActOnWhileStmt(WhileLoc);
- getCurScope()->setLoopOrSwitchName(Name);
+ if (LoopOrSwitchNames)
+ getCurScope()->setLoopOrSwitchNames(*LoopOrSwitchNames);
// C99 6.8.5p5 - In C99, the body of the while statement is a scope, even if
// there is no compound stmt. C90 does not have this clause. We only do this
@@ -1783,7 +1792,7 @@ StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, LabelDec
return Actions.ActOnWhileStmt(WhileLoc, LParen, Cond, RParen, Body.get());
}
-StmtResult Parser::ParseDoStatement(LabelDecl *Name) {
+StmtResult Parser::ParseDoStatement(SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
assert(Tok.is(tok::kw_do) && "Not a do stmt!");
SourceLocation DoLoc = ConsumeToken(); // eat the 'do'.
@@ -1801,7 +1810,8 @@ StmtResult Parser::ParseDoStatement(LabelDecl *Name) {
// combinations, so diagnose that here in OpenACC mode.
SemaOpenACC::LoopInConstructRAII LCR{getActions().OpenACC()};
getActions().OpenACC().ActOnDoStmt(DoLoc);
- getCurScope()->setLoopOrSwitchName(Name);
+ if (LoopOrSwitchNames)
+ getCurScope()->setLoopOrSwitchNames(*LoopOrSwitchNames);
// C99 6.8.5p5 - In C99, the body of the do statement is a scope, even if
// there is no compound stmt. C90 does not have this clause. We only do this
@@ -1821,7 +1831,7 @@ StmtResult Parser::ParseDoStatement(LabelDecl *Name) {
InnerScope.Exit();
// Reset this to disallow break/continue out of the condition.
- getCurScope()->setLoopOrSwitchName(nullptr);
+ getCurScope()->setLoopOrSwitchNames(ArrayRef<LabelDecl *>{});
if (Tok.isNot(tok::kw_while)) {
if (!Body.isInvalid()) {
@@ -1884,7 +1894,7 @@ bool Parser::isForRangeIdentifier() {
return false;
}
-StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc, LabelDecl *Name) {
+StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
assert(Tok.is(tok::kw_for) && "Not a for stmt!");
SourceLocation ForLoc = ConsumeToken(); // eat the 'for'.
@@ -2218,7 +2228,8 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc, LabelDecl
// Set this only right before parsing the body to disallow break/continue in
// the other parts.
- getCurScope()->setLoopOrSwitchName(Name);
+ if (LoopOrSwitchNames)
+ getCurScope()->setLoopOrSwitchNames(*LoopOrSwitchNames);
// C99 6.8.5p5 - In C99, the body of the for statement is a scope, even if
// there is no compound stmt. C90 does not have this clause. We only do this
@@ -2371,7 +2382,7 @@ StmtResult Parser::ParseReturnStatement() {
StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts,
ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc,
- ParsedAttributes &Attrs, LabelDecl *Name) {
+ ParsedAttributes &Attrs, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
// Create temporary attribute list.
ParsedAttributes TempAttrs(AttrFactory);
@@ -2395,7 +2406,7 @@ StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts,
ParsedAttributes EmptyDeclSpecAttrs(AttrFactory);
StmtResult S = ParseStatementOrDeclarationAfterAttributes(
- Stmts, StmtCtx, TrailingElseLoc, Attrs, EmptyDeclSpecAttrs, Name);
+ Stmts, StmtCtx, TrailingElseLoc, Attrs, EmptyDeclSpecAttrs, LoopOrSwitchNames);
Attrs.takeAllFrom(TempAttrs);
diff --git a/clang/lib/Sema/Scope.cpp b/clang/lib/Sema/Scope.cpp
index ab04fe554be82..3993d5b230202 100644
--- a/clang/lib/Sema/Scope.cpp
+++ b/clang/lib/Sema/Scope.cpp
@@ -99,6 +99,7 @@ void Scope::Init(Scope *parent, unsigned flags) {
UsingDirectives.clear();
Entity = nullptr;
ErrorTrap.reset();
+ LoopOrSwitchNames = {};
NRVO = std::nullopt;
}
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index f286c40ee914c..c97905c923e8d 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -3283,29 +3283,33 @@ static void CheckJumpOutOfSEHFinally(Sema &S, SourceLocation Loc,
}
Scope *FindLabeledBreakContinueScope(Sema &S, Scope *CurScope,
- LabelDecl *Target, SourceLocation LabelLoc,
- bool IsBreak) {
+ SourceLocation KWLoc, LabelDecl *Target,
+ SourceLocation LabelLoc, bool IsBreak) {
+ Scope* Found = nullptr;
for (Scope* Scope = CurScope; Scope; Scope = Scope->getParent()) {
- if (Scope->isFunctionScope()) {
- S.Diag(LabelLoc, diag::err_break_continue_label_not_found) << IsBreak;
+ if (Scope->isFunctionScope())
+ break;
+ if (Scope->isOpenACCComputeConstructScope()) {
+ S.Diag(KWLoc, diag::err_acc_branch_in_out_compute_construct)
+ << /*branch*/ 0 << /*out of */ 0;
return nullptr;
}
-
- if (Scope->getLoopOrSwitchName() != Target)
+ if (!llvm::is_contained(Scope->getLoopOrSwitchNames(), Target))
continue;
+ if (Scope->isBreakOrContinueScope())
+ Found = Scope;
+ break;
+ }
- if (!Scope->isBreakOrContinueScope()) {
- S.Diag(LabelLoc, diag::err_break_continue_label_not_found) << IsBreak;
- return nullptr;
- }
-
- if (!IsBreak && !Scope->isContinueScope()) {
+ if (Found) {
+ if (!IsBreak && !Found->isContinueScope()) {
S.Diag(LabelLoc, diag::err_continue_switch);
return nullptr;
}
-
- return Scope;
+ return Found;
}
+
+ S.Diag(LabelLoc, diag::err_break_continue_label_not_found) << IsBreak;
return nullptr;
}
@@ -3313,7 +3317,7 @@ StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
LabelDecl *Target, SourceLocation LabelLoc) {
Scope *S;
if (Target) {
- S = FindLabeledBreakContinueScope(*this, CurScope, Target, LabelLoc,
+ S = FindLabeledBreakContinueScope(*this, CurScope, ContinueLoc, Target, LabelLoc,
/*IsBreak=*/false);
if (!S)
return StmtError();
@@ -3349,7 +3353,7 @@ StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope,
LabelDecl *Target, SourceLocation LabelLoc) {
Scope *S;
if (Target) {
- S = FindLabeledBreakContinueScope(*this, CurScope, Target, LabelLoc,
+ S = FindLabeledBreakContinueScope(*this, CurScope, BreakLoc, Target, LabelLoc,
/*IsBreak=*/true);
if (!S)
return StmtError();
diff --git a/clang/test/Sema/labeled-break-continue.c b/clang/test/Sema/labeled-break-continue.c
index 16993af261f51..d60b1dd06bcc6 100644
--- a/clang/test/Sema/labeled-break-continue.c
+++ b/clang/test/Sema/labeled-break-continue.c
@@ -126,7 +126,7 @@ void f6() {
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}}
+ continue d; // expected-error {{'continue' label does not name an enclosing loop}}
1;
})) { case 1:; }
}
@@ -134,13 +134,13 @@ void f6() {
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'}}
+ break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+ continue b; // expected-error {{'continue' label does not name an enclosing loop}}
};
}
while (true) {
- break c; // expected-error {{use of undeclared label 'c'}}
- continue d; // expected-error {{use of undeclared label 'd'}}
+ break c; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+ continue d; // expected-error {{'continue' label does not name an enclosing loop}}
}
}
diff --git a/clang/test/SemaCXX/labeled-break-continue.cpp b/clang/test/SemaCXX/labeled-break-continue.cpp
index 45608b872589a..146b6a9533081 100644
--- a/clang/test/SemaCXX/labeled-break-continue.cpp
+++ b/clang/test/SemaCXX/labeled-break-continue.cpp
@@ -44,8 +44,8 @@ void f2() {
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'}}
+ break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+ continue b; // expected-error {{'continue' label does not name an enclosing loop}}
};
}
}
diff --git a/clang/test/SemaObjC/labeled-break-continue.m b/clang/test/SemaObjC/labeled-break-continue.m
index 72cf07912ed3c..b4f525e86f010 100644
--- a/clang/test/SemaObjC/labeled-break-continue.m
+++ b/clang/test/SemaObjC/labeled-break-continue.m
@@ -32,8 +32,8 @@ void f2(id 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'}}
+ break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}}
+ continue b; // expected-error {{'continue' label does not name an enclosing loop}}
};
}
}
>From 093fb04c720dae7db8f33b42df284fd4d328051f Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Thu, 14 Aug 2025 16:37:29 +0200
Subject: [PATCH 22/23] clang-format
---
clang/include/clang/Parse/Parser.h | 34 +++++++++++-------
clang/lib/Parse/ParseStmt.cpp | 57 +++++++++++++++++++-----------
clang/lib/Sema/SemaStmt.cpp | 12 ++++---
3 files changed, 64 insertions(+), 39 deletions(-)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 4e49b35c4d02a..b7ccaa39da528 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7267,15 +7267,16 @@ class Parser : public CodeCompletionHandler {
/// [OBC] '@' 'throw' ';'
/// \endverbatim
///
- StmtResult
- ParseStatementOrDeclaration(StmtVector &Stmts, ParsedStmtContext StmtCtx,
- SourceLocation *TrailingElseLoc = nullptr,
- SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames = nullptr);
+ StmtResult ParseStatementOrDeclaration(
+ StmtVector &Stmts, ParsedStmtContext StmtCtx,
+ SourceLocation *TrailingElseLoc = nullptr,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames = nullptr);
StmtResult ParseStatementOrDeclarationAfterAttributes(
StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc, ParsedAttributes &DeclAttrs,
- ParsedAttributes &DeclSpecAttrs, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
+ ParsedAttributes &DeclSpecAttrs,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
/// Parse an expression statement.
StmtResult ParseExprStatement(ParsedStmtContext StmtCtx);
@@ -7291,8 +7292,9 @@ class Parser : public CodeCompletionHandler {
/// label statement
/// \endverbatim
///
- StmtResult ParseLabeledStatement(ParsedAttributes &Attrs,
- ParsedStmtContext StmtCtx, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
+ StmtResult
+ ParseLabeledStatement(ParsedAttributes &Attrs, ParsedStmtContext StmtCtx,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
/// ParseCaseStatement
/// \verbatim
@@ -7400,7 +7402,9 @@ class Parser : public CodeCompletionHandler {
/// 'switch' '(' expression ')' statement
/// [C++] 'switch' '(' condition ')' statement
/// \endverbatim
- StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
+ StmtResult
+ ParseSwitchStatement(SourceLocation *TrailingElseLoc,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
/// ParseWhileStatement
/// \verbatim
@@ -7408,7 +7412,9 @@ class Parser : public CodeCompletionHandler {
/// 'while' '(' expression ')' statement
/// [C++] 'while' '(' condition ')' statement
/// \endverbatim
- StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
+ StmtResult
+ ParseWhileStatement(SourceLocation *TrailingElseLoc,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
/// ParseDoStatement
/// \verbatim
@@ -7443,7 +7449,8 @@ class Parser : public CodeCompletionHandler {
/// [C++0x] expression
/// [C++0x] braced-init-list [TODO]
/// \endverbatim
- StmtResult ParseForStatement(SourceLocation *TrailingElseLoc, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
+ StmtResult ParseForStatement(SourceLocation *TrailingElseLoc,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
/// ParseGotoStatement
/// \verbatim
@@ -7490,9 +7497,10 @@ class Parser : public CodeCompletionHandler {
StmtResult ParseBreakOrContinueStatement(bool IsContinue);
- StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx,
- SourceLocation *TrailingElseLoc,
- ParsedAttributes &Attrs, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
+ StmtResult
+ ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx,
+ SourceLocation *TrailingElseLoc, ParsedAttributes &Attrs,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames);
void ParseMicrosoftIfExistsStatement(StmtVector &Stmts);
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index de70ed7a96ede..aaf101839472a 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -36,24 +36,27 @@ using namespace clang;
// C99 6.8: Statements and Blocks.
//===----------------------------------------------------------------------===//
-StmtResult Parser::ParseStatement(SourceLocation *TrailingElseLoc,
- ParsedStmtContext StmtCtx, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
+StmtResult
+Parser::ParseStatement(SourceLocation *TrailingElseLoc,
+ ParsedStmtContext StmtCtx,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
StmtResult Res;
// We may get back a null statement if we found a #pragma. Keep going until
// we get an actual statement.
StmtVector Stmts;
do {
- Res = ParseStatementOrDeclaration(Stmts, StmtCtx, TrailingElseLoc, LoopOrSwitchNames);
+ Res = ParseStatementOrDeclaration(Stmts, StmtCtx, TrailingElseLoc,
+ LoopOrSwitchNames);
} while (!Res.isInvalid() && !Res.get());
return Res;
}
-StmtResult
-Parser::ParseStatementOrDeclaration(StmtVector &Stmts,
- ParsedStmtContext StmtCtx,
- SourceLocation *TrailingElseLoc, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
+StmtResult Parser::ParseStatementOrDeclaration(
+ StmtVector &Stmts, ParsedStmtContext StmtCtx,
+ SourceLocation *TrailingElseLoc,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
ParenBraceBracketBalancer BalancerRAIIObj(*this);
@@ -73,7 +76,8 @@ Parser::ParseStatementOrDeclaration(StmtVector &Stmts,
MaybeParseMicrosoftAttributes(GNUOrMSAttrs);
StmtResult Res = ParseStatementOrDeclarationAfterAttributes(
- Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUOrMSAttrs, LoopOrSwitchNames);
+ Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUOrMSAttrs,
+ LoopOrSwitchNames);
MaybeDestroyTemplateIds();
takeAndConcatenateAttrs(CXX11Attrs, std::move(GNUOrMSAttrs));
@@ -130,7 +134,8 @@ class StatementFilterCCC final : public CorrectionCandidateCallback {
StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
StmtVector &Stmts, ParsedStmtContext StmtCtx,
SourceLocation *TrailingElseLoc, ParsedAttributes &CXX11Attrs,
- ParsedAttributes &GNUAttrs, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
+ ParsedAttributes &GNUAttrs,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
const char *SemiError = nullptr;
StmtResult Res;
SourceLocation GNUAttributeLoc;
@@ -483,7 +488,8 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
case tok::annot_pragma_loop_hint:
ProhibitAttributes(CXX11Attrs);
ProhibitAttributes(GNUAttrs);
- return ParsePragmaLoopHint(Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, LoopOrSwitchNames);
+ return ParsePragmaLoopHint(Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs,
+ LoopOrSwitchNames);
case tok::annot_pragma_dump:
ProhibitAttributes(CXX11Attrs);
@@ -726,7 +732,8 @@ Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
StmtVector Stmts;
ParsedAttributes EmptyCXX11Attrs(AttrFactory);
SubStmt = ParseStatementOrDeclarationAfterAttributes(
- Stmts, StmtCtx, nullptr, EmptyCXX11Attrs, TempAttrs, LoopOrSwitchNames);
+ Stmts, StmtCtx, nullptr, EmptyCXX11Attrs, TempAttrs,
+ LoopOrSwitchNames);
if (!TempAttrs.empty() && !SubStmt.isInvalid())
SubStmt = Actions.ActOnAttributedStmt(TempAttrs, SubStmt.get());
}
@@ -1628,8 +1635,9 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) {
ThenStmt.get(), ElseLoc, ElseStmt.get());
}
-StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc,
- SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
+StmtResult
+Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
assert(Tok.is(tok::kw_switch) && "Not a switch stmt!");
SourceLocation SwitchLoc = ConsumeToken(); // eat the 'switch'.
@@ -1714,7 +1722,9 @@ StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc,
return Actions.ActOnFinishSwitchStmt(SwitchLoc, Switch.get(), Body.get());
}
-StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
+StmtResult
+Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
assert(Tok.is(tok::kw_while) && "Not a while stmt!");
SourceLocation WhileLoc = Tok.getLocation();
ConsumeToken(); // eat the 'while'.
@@ -1792,7 +1802,8 @@ StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, SmallVec
return Actions.ActOnWhileStmt(WhileLoc, LParen, Cond, RParen, Body.get());
}
-StmtResult Parser::ParseDoStatement(SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
+StmtResult
+Parser::ParseDoStatement(SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
assert(Tok.is(tok::kw_do) && "Not a do stmt!");
SourceLocation DoLoc = ConsumeToken(); // eat the 'do'.
@@ -1894,7 +1905,9 @@ bool Parser::isForRangeIdentifier() {
return false;
}
-StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
+StmtResult
+Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
assert(Tok.is(tok::kw_for) && "Not a for stmt!");
SourceLocation ForLoc = ConsumeToken(); // eat the 'for'.
@@ -2379,10 +2392,11 @@ StmtResult Parser::ParseReturnStatement() {
return Actions.ActOnReturnStmt(ReturnLoc, R.get(), getCurScope());
}
-StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts,
- ParsedStmtContext StmtCtx,
- SourceLocation *TrailingElseLoc,
- ParsedAttributes &Attrs, SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
+StmtResult
+Parser::ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx,
+ SourceLocation *TrailingElseLoc,
+ ParsedAttributes &Attrs,
+ SmallVectorImpl<LabelDecl *> *LoopOrSwitchNames) {
// Create temporary attribute list.
ParsedAttributes TempAttrs(AttrFactory);
@@ -2406,7 +2420,8 @@ StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts,
ParsedAttributes EmptyDeclSpecAttrs(AttrFactory);
StmtResult S = ParseStatementOrDeclarationAfterAttributes(
- Stmts, StmtCtx, TrailingElseLoc, Attrs, EmptyDeclSpecAttrs, LoopOrSwitchNames);
+ Stmts, StmtCtx, TrailingElseLoc, Attrs, EmptyDeclSpecAttrs,
+ LoopOrSwitchNames);
Attrs.takeAllFrom(TempAttrs);
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index c97905c923e8d..cb9e313972d0d 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -3285,13 +3285,13 @@ static void CheckJumpOutOfSEHFinally(Sema &S, SourceLocation Loc,
Scope *FindLabeledBreakContinueScope(Sema &S, Scope *CurScope,
SourceLocation KWLoc, LabelDecl *Target,
SourceLocation LabelLoc, bool IsBreak) {
- Scope* Found = nullptr;
- for (Scope* Scope = CurScope; Scope; Scope = Scope->getParent()) {
+ Scope *Found = nullptr;
+ for (Scope *Scope = CurScope; Scope; Scope = Scope->getParent()) {
if (Scope->isFunctionScope())
break;
if (Scope->isOpenACCComputeConstructScope()) {
S.Diag(KWLoc, diag::err_acc_branch_in_out_compute_construct)
- << /*branch*/ 0 << /*out of */ 0;
+ << /*branch*/ 0 << /*out of */ 0;
return nullptr;
}
if (!llvm::is_contained(Scope->getLoopOrSwitchNames(), Target))
@@ -3317,7 +3317,8 @@ StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope,
LabelDecl *Target, SourceLocation LabelLoc) {
Scope *S;
if (Target) {
- S = FindLabeledBreakContinueScope(*this, CurScope, ContinueLoc, Target, LabelLoc,
+ S = FindLabeledBreakContinueScope(*this, CurScope, ContinueLoc, Target,
+ LabelLoc,
/*IsBreak=*/false);
if (!S)
return StmtError();
@@ -3353,7 +3354,8 @@ StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope,
LabelDecl *Target, SourceLocation LabelLoc) {
Scope *S;
if (Target) {
- S = FindLabeledBreakContinueScope(*this, CurScope, BreakLoc, Target, LabelLoc,
+ S = FindLabeledBreakContinueScope(*this, CurScope, BreakLoc, Target,
+ LabelLoc,
/*IsBreak=*/true);
if (!S)
return StmtError();
>From c9de06b8257944c36306a9f1e18a793c9641cb29 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Thu, 14 Aug 2025 17:41:14 +0200
Subject: [PATCH 23/23] Add a cc1 option and disable the feature in every
language mode other than c2y
---
clang/include/clang/Basic/DiagnosticParseKinds.td | 10 ++--------
clang/include/clang/Basic/LangOptions.def | 1 +
clang/include/clang/Driver/Options.td | 11 +++++++++++
clang/lib/Basic/LangOptions.cpp | 1 +
clang/lib/Frontend/CompilerInvocation.cpp | 3 +++
clang/lib/Parse/ParseStmt.cpp | 7 ++++---
clang/test/CodeGenCXX/labeled-break-continue.cpp | 2 +-
clang/test/CodeGenObjC/labeled-break-continue.m | 2 +-
clang/test/OpenMP/for_loop_messages.cpp | 8 ++++----
clang/test/Parser/labeled-break-continue.c | 12 +++++++-----
clang/test/Sema/__try.c | 4 ++--
clang/test/Sema/labeled-break-continue.c | 3 ++-
.../SemaCXX/labeled-break-continue-constexpr.cpp | 2 +-
clang/test/SemaCXX/labeled-break-continue.cpp | 2 +-
clang/test/SemaObjC/labeled-break-continue.m | 2 +-
clang/test/SemaOpenACC/no-branch-in-out.c | 2 +-
16 files changed, 43 insertions(+), 29 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 6f2498d3bc7c3..f64dfd7560b7e 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -215,14 +215,8 @@ 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 err_c2y_labeled_break_continue
+ : Error<"labeled %select{'break'|'continue'}0 is only supported in C2y">;
// Generic errors.
def err_expected_expression : Error<"expected expression">;
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 08d98a77e0252..874ee4cc18af1 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -191,6 +191,7 @@ LANGOPT(NoHonorInfs , 1, 0, Benign, "Permit Floating Point optimization wi
LANGOPT(NoSignedZero , 1, 0, Benign, "Permit Floating Point optimization without regard to signed zeros")
LANGOPT(AllowRecip , 1, 0, Benign, "Permit Floating Point reciprocal")
LANGOPT(ApproxFunc , 1, 0, Benign, "Permit Floating Point approximation")
+LANGOPT(NamedLoops , 1, 0, Benign, "Permit labeled break/continue")
ENUM_LANGOPT(ComplexRange, ComplexRangeKind, 3, CX_None, NotCompatible, "Enable use of range reduction for complex arithmetics.")
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 6aab43c9ed57f..2867dca6f7ddb 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -1642,6 +1642,17 @@ defm auto_import : BoolFOption<"auto-import",
def offload_EQ : CommaJoined<["--"], "offload=">, Flags<[NoXarchOption]>, Alias<offload_targets_EQ>,
HelpText<"Specify comma-separated list of offloading target triples (CUDA and HIP only)">;
+// This flag is only here so we can test the named loops implementation
+// in C++ mode and C language modes before C2y to make sure it actually
+// works; it should be removed once the syntax of the feature is stable
+// enough to backport it to earlier language modes (and to C++ if it ever
+// gets standardised as a C++ feature).
+defm named_loops
+ : BoolFOption<
+ "named-loops", LangOpts<"NamedLoops">, DefaultFalse,
+ PosFlag<SetTrue, [], [CC1Option], "Enable support for named loops">,
+ NegFlag<SetFalse>>;
+
// C++ Coroutines
defm coroutines : BoolFOption<"coroutines",
LangOpts<"Coroutines">, Default<cpp20.KeyPath>,
diff --git a/clang/lib/Basic/LangOptions.cpp b/clang/lib/Basic/LangOptions.cpp
index 9c14a25699f89..f034514466d3f 100644
--- a/clang/lib/Basic/LangOptions.cpp
+++ b/clang/lib/Basic/LangOptions.cpp
@@ -128,6 +128,7 @@ void LangOptions::setLangDefaults(LangOptions &Opts, Language Lang,
Opts.WChar = Std.isCPlusPlus();
Opts.Digraphs = Std.hasDigraphs();
Opts.RawStringLiterals = Std.hasRawStringLiterals();
+ Opts.NamedLoops = Std.isC2y();
Opts.HLSL = Lang == Language::HLSL;
if (Opts.HLSL && Opts.IncludeDefaultHeader)
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 9f77e621a5e68..04583af6a4b0a 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -623,6 +623,9 @@ static bool FixupInvocation(CompilerInvocation &Invocation,
LangOpts.RawStringLiterals = true;
}
+ LangOpts.NamedLoops =
+ Args.hasFlag(OPT_fnamed_loops, OPT_fno_named_loops, LangOpts.C2y);
+
// Prevent the user from specifying both -fsycl-is-device and -fsycl-is-host.
if (LangOpts.SYCLIsDevice && LangOpts.SYCLIsHost)
Diags.Report(diag::err_drv_argument_not_allowed_with) << "-fsycl-is-device"
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index aaf101839472a..afd3dc2451870 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -2332,9 +2332,10 @@ StmtResult Parser::ParseBreakOrContinueStatement(bool IsContinue) {
Target =
Actions.LookupExistingLabel(Tok.getIdentifierInfo(), Tok.getLocation());
LabelLoc = ConsumeToken();
- Diag(LabelLoc, getLangOpts().C2y ? diag::warn_c2y_labeled_break_continue
- : diag::ext_c2y_labeled_break_continue)
- << IsContinue;
+ if (!getLangOpts().NamedLoops)
+ // TODO: Make this a compatibility/extension warning instead once the
+ // syntax of this feature is finalised.
+ Diag(LabelLoc, diag::err_c2y_labeled_break_continue) << IsContinue;
if (!Target) {
Diag(LabelLoc, diag::err_break_continue_label_not_found) << !IsContinue;
return StmtError();
diff --git a/clang/test/CodeGenCXX/labeled-break-continue.cpp b/clang/test/CodeGenCXX/labeled-break-continue.cpp
index bf1b6d520efc4..4bdb5369aa8f1 100644
--- a/clang/test/CodeGenCXX/labeled-break-continue.cpp
+++ b/clang/test/CodeGenCXX/labeled-break-continue.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -triple x86_64-unknown-linux -std=c++20 -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -fnamed-loops -triple x86_64-unknown-linux -std=c++20 -emit-llvm -o - %s | FileCheck %s
static int a[10]{};
struct NonTrivialDestructor {
diff --git a/clang/test/CodeGenObjC/labeled-break-continue.m b/clang/test/CodeGenObjC/labeled-break-continue.m
index 2ab9aab88f294..e9979fe437b61 100644
--- a/clang/test/CodeGenObjC/labeled-break-continue.m
+++ b/clang/test/CodeGenObjC/labeled-break-continue.m
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -triple x86_64-apple-darwin -Wno-objc-root-class -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -std=c2y -triple x86_64-apple-darwin -Wno-objc-root-class -emit-llvm -o - %s | FileCheck %s
int g(id x);
diff --git a/clang/test/OpenMP/for_loop_messages.cpp b/clang/test/OpenMP/for_loop_messages.cpp
index 42513a72cc04b..5dc80d095c5f6 100644
--- a/clang/test/OpenMP/for_loop_messages.cpp
+++ b/clang/test/OpenMP/for_loop_messages.cpp
@@ -1,8 +1,8 @@
-// RUN: %clang_cc1 -fsyntax-only -fopenmp -fopenmp-version=45 -x c++ -std=c++11 -fexceptions -fcxx-exceptions -verify=expected,omp4 %s -Wuninitialized
-// RUN: %clang_cc1 -fsyntax-only -fopenmp -x c++ -std=c++11 -fexceptions -fcxx-exceptions -verify=expected,omp5 %s -Wuninitialized
+// RUN: %clang_cc1 -fsyntax-only -fopenmp -fopenmp-version=45 -x c++ -std=c++11 -fexceptions -fcxx-exceptions -fnamed-loops -verify=expected,omp4 %s -Wuninitialized
+// RUN: %clang_cc1 -fsyntax-only -fopenmp -x c++ -std=c++11 -fexceptions -fcxx-exceptions -fnamed-loops -verify=expected,omp5 %s -Wuninitialized
-// RUN: %clang_cc1 -fsyntax-only -fopenmp-simd -fopenmp-version=45 -x c++ -std=c++11 -fexceptions -fcxx-exceptions -verify=expected,omp4 %s -Wuninitialized
-// RUN: %clang_cc1 -fsyntax-only -fopenmp-simd -x c++ -std=c++11 -fexceptions -fcxx-exceptions -verify=expected,omp5 %s -Wuninitialized
+// RUN: %clang_cc1 -fsyntax-only -fopenmp-simd -fopenmp-version=45 -x c++ -std=c++11 -fexceptions -fcxx-exceptions -fnamed-loops -verify=expected,omp4 %s -Wuninitialized
+// RUN: %clang_cc1 -fsyntax-only -fopenmp-simd -x c++ -std=c++11 -fexceptions -fcxx-exceptions -fnamed-loops -verify=expected,omp5 %s -Wuninitialized
class S {
int a;
diff --git a/clang/test/Parser/labeled-break-continue.c b/clang/test/Parser/labeled-break-continue.c
index 4d6ce83c2dcae..ab78ead2348d2 100644
--- a/clang/test/Parser/labeled-break-continue.c
+++ b/clang/test/Parser/labeled-break-continue.c
@@ -1,10 +1,12 @@
// 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
+// RUN: %clang_cc1 -fsyntax-only -verify -fnamed-loops -std=c23 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -fnamed-loops -x c++ %s
+// RUN: %clang_cc1 -fsyntax-only -verify=disabled -std=c23 %s
+// RUN: %clang_cc1 -fsyntax-only -verify=disabled -x c++ %s
+// RUN: %clang_cc1 -fsyntax-only -verify=disabled -std=c23 -pedantic %s
+// RUN: %clang_cc1 -fsyntax-only -verify=disabled -x c++ -pedantic %s
// expected-no-diagnostics
void f() {
- x: while (1) break x; // pedantic-warning {{labeled 'break' is a C2y extension}}
+ x: while (1) break x; // disabled-error {{labeled 'break' is only supported in C2y}}
}
diff --git a/clang/test/Sema/__try.c b/clang/test/Sema/__try.c
index 6702cb9b0e19e..06360cb0a5dcf 100644
--- a/clang/test/Sema/__try.c
+++ b/clang/test/Sema/__try.c
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -triple x86_64-windows -fborland-extensions -DBORLAND -fsyntax-only -verify -fblocks %s
-// RUN: %clang_cc1 -triple x86_64-windows -fms-extensions -fsyntax-only -verify -fblocks %s
+// RUN: %clang_cc1 -triple x86_64-windows -fborland-extensions -DBORLAND -fsyntax-only -verify -fblocks -fnamed-loops %s
+// RUN: %clang_cc1 -triple x86_64-windows -fms-extensions -fsyntax-only -verify -fblocks -fnamed-loops %s
#define JOIN2(x,y) x ## y
#define JOIN(x,y) JOIN2(x,y)
diff --git a/clang/test/Sema/labeled-break-continue.c b/clang/test/Sema/labeled-break-continue.c
index d60b1dd06bcc6..d045c2f4a96f4 100644
--- a/clang/test/Sema/labeled-break-continue.c
+++ b/clang/test/Sema/labeled-break-continue.c
@@ -1,5 +1,6 @@
// RUN: %clang_cc1 -std=c2y -verify -fsyntax-only -fblocks %s
-// RUN: %clang_cc1 -x c++ -verify -fsyntax-only -fblocks %s
+// RUN: %clang_cc1 -std=c23 -verify -fsyntax-only -fblocks -fnamed-loops %s
+// RUN: %clang_cc1 -x c++ -verify -fsyntax-only -fblocks -fnamed-loops %s
void f1() {
l1: while (true) {
diff --git a/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
index fe58004ca6ff0..bec6c582a1f0d 100644
--- a/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
+++ b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fnamed-loops -std=c++23 -fsyntax-only -verify %s
// expected-no-diagnostics
struct Tracker {
diff --git a/clang/test/SemaCXX/labeled-break-continue.cpp b/clang/test/SemaCXX/labeled-break-continue.cpp
index 146b6a9533081..22b47913a7e7b 100644
--- a/clang/test/SemaCXX/labeled-break-continue.cpp
+++ b/clang/test/SemaCXX/labeled-break-continue.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -std=c++20 -verify -fsyntax-only %s
+// RUN: %clang_cc1 -std=c++20 -verify -fsyntax-only -fnamed-loops %s
int a[10]{};
struct S {
diff --git a/clang/test/SemaObjC/labeled-break-continue.m b/clang/test/SemaObjC/labeled-break-continue.m
index b4f525e86f010..2791474a579b7 100644
--- a/clang/test/SemaObjC/labeled-break-continue.m
+++ b/clang/test/SemaObjC/labeled-break-continue.m
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify -fblocks %s
+// RUN: %clang_cc1 -std=c2y -fsyntax-only -verify -fblocks %s
void f1(id y) {
l1: for (id x in y) {
diff --git a/clang/test/SemaOpenACC/no-branch-in-out.c b/clang/test/SemaOpenACC/no-branch-in-out.c
index 3cd6c5af13aaf..370722b52ab19 100644
--- a/clang/test/SemaOpenACC/no-branch-in-out.c
+++ b/clang/test/SemaOpenACC/no-branch-in-out.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 %s -verify -fopenacc
+// RUN: %clang_cc1 %s -verify -fopenacc -fnamed-loops
void BreakContinue() {
More information about the cfe-commits
mailing list