From 56a2eff96699b3e11404cc16cc4249367a8bbcf5 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Sat, 9 Aug 2025 06:10:05 +0200 Subject: [PATCH 01/40] 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; 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; def ForStmt : StmtNode; def GotoStmt : StmtNode; def IndirectGotoStmt : StmtNode; -def ContinueStmt : StmtNode; -def BreakStmt : StmtNode; def ReturnStmt : StmtNode; def DeclStmt : StmtNode; def SwitchCase : StmtNode; @@ -26,6 +24,11 @@ def DefaultStmt : StmtNode; def CapturedStmt : StmtNode; def SYCLKernelCallStmt : StmtNode; +// Break/continue. +def LoopControlStmt : StmtNode; +def ContinueStmt : StmtNode; +def BreakStmt : StmtNode; + // Statements that might produce a value (for example, as the last non-null // statement in a GNU statement-expression). def ValueStmt : StmtNode; 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 +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 { } 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 { } 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 Date: Sat, 9 Aug 2025 06:10:38 +0200 Subject: [PATCH 02/40] 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 { 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 Date: Sat, 9 Aug 2025 06:13:13 +0200 Subject: [PATCH 03/40] 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 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 Date: Sat, 9 Aug 2025 10:47:18 +0200 Subject: [PATCH 04/40] 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(Target)) + Target = cast(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 << "<<>>"; + } 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(S)->getInit()) { - BuildScopeInformation(Init, ParentScope); - ++StmtsToSkip; - } - if (VarDecl *Var = cast(S)->getConditionVariable()) { - BuildScopeInformation(Var, ParentScope); - ++StmtsToSkip; - } - goto RecordJumpScope; + case Stmt::SwitchStmtClass: { + BuildScopeInformationForLoopOrSwitch(S, cast(S)->getBody(), ParentScope); + Jumps.push_back(S); + return; + } case Stmt::GCCAsmStmtClass: if (!cast(S)->isAsmGoto()) @@ -365,6 +381,29 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S, Jumps.push_back(S); break; + case Stmt::BreakStmtClass: + case Stmt::ContinueStmtClass: + if (cast(S)->isLabeled()) goto RecordJumpScope; + break; + + case Stmt::WhileStmtClass: { + BuildScopeInformationForLoopOrSwitch(S, cast(S)->getBody(), + ParentScope); + return; + } + + case Stmt::DoStmtClass: { + BuildScopeInformationForLoopOrSwitch(S, cast(S)->getBody(), + ParentScope); + return; + } + + case Stmt::ForStmtClass: { + BuildScopeInformationForLoopOrSwitch(S, cast(S)->getBody(), + ParentScope); + return; + } + case Stmt::IfStmtClass: { IfStmt *IS = cast(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(Jump)) { + assert(L->isLabeled() && "expected labeled break/continue"); + bool IsContinue = isa(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(Target)) { + S.Diag(L->getLabelLoc(), diag::err_break_continue_label_not_found) + << !IsContinue; + continue; + } + + if (IsContinue && isa(Target)) { + S.Diag(L->getLabelLoc(), diag::err_continue_switch); + continue; + } + + CheckJump(L, Target, L->getKwLoc(), 0, 0, 0); + continue; + } + SwitchStmt *SS = cast(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(From) || isa(From)) { + if (isa(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(From)) { + S.Diag(L->getLabelLoc(), diag::err_break_continue_label_not_found) + << isa(L); + return; + } + // Pull out (and reverse) any scopes we might need to diagnose skipping. SmallVector ToScopesCompat; SmallVector 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()); 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 Date: Sat, 9 Aug 2025 10:47:49 +0200 Subject: [PATCH 05/40] 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(S) && !isa(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 Date: Sat, 9 Aug 2025 11:32:44 +0200 Subject: [PATCH 06/40] 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 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 Date: Sat, 9 Aug 2025 12:08:17 +0200 Subject: [PATCH 07/40] 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; def ext_c2y_case_range : Extension< "case ranges are a C2y extension">, InGroup; +def warn_c2y_labeled_break_continue: Warning< + "labeled %select{'break'|'continue'}0 is incompatible with C standards before C2y">, + DefaultIgnore, InGroup; +def ext_c2y_labeled_break_continue: Extension< + "labeled %select{'break'|'continue'}0 is a C2y extension">, InGroup; // 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 Date: Sat, 9 Aug 2025 12:39:05 +0200 Subject: [PATCH 08/40] 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(S)->getBody(), + ParentScope); + return; + } + case Stmt::IfStmtClass: { IfStmt *IS = cast(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 Date: Sat, 9 Aug 2025 12:53:10 +0200 Subject: [PATCH 09/40] 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(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 Date: Sat, 9 Aug 2025 14:36:23 +0200 Subject: [PATCH 10/40] 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 BreakContinueStack; + /// Set of objects that are currently being constructed. llvm::DenseMap 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 Scopes, + EvalStmtResult &ESR) { + bool IsSwitch = isa(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(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(S)); case Stmt::ContinueStmtClass: - return ESR_Continue; - - case Stmt::BreakStmtClass: - return ESR_Break; + case Stmt::BreakStmtClass: { + auto *B = cast(S); + Info.BreakContinueStack.push_back(B->isLabeled() ? B->getLabelTarget() + : nullptr); + return isa(S) ? ESR_Continue : ESR_Break; + } case Stmt::LabelStmtClass: return EvaluateStmt(Result, Info, cast(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 Date: Sat, 9 Aug 2025 15:49:32 +0200 Subject: [PATCH 11/40] 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::TransformIndirectGotoStmt(IndirectGotoStmt *S) { template StmtResult TreeTransform::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(LD), S->getLabelLoc()); } template StmtResult TreeTransform::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(LD), S->getLabelLoc()); } template 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 +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(); + +// 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(); 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 +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() == 93); +static_assert(f12() == 93u); From e05b8b130d622f7709413fb64c5c2702e78c0566 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Sat, 9 Aug 2025 16:57:00 +0200 Subject: [PATCH 12/40] 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(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(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(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 Date: Sat, 9 Aug 2025 17:58:14 +0200 Subject: [PATCH 13/40] 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/ //" -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 {{.*}} +// CHECK-NEXT: `-LabelStmt {{.*}} 'a' +// CHECK-NEXT: `-LabelStmt {{.*}} 'b' +// CHECK-NEXT: `-WhileStmt [[A:0x.*]] +// CHECK-NEXT: |-CXXBoolLiteralExpr {{.*}} 'bool' true +// CHECK-NEXT: `-CompoundStmt {{.*}} +// CHECK-NEXT: |-BreakStmt {{.*}} 'a' (WhileStmt [[A]]) +// CHECK-NEXT: |-ContinueStmt {{.*}} 'b' (WhileStmt [[A]]) +// CHECK-NEXT: `-LabelStmt {{.*}} 'c' +// CHECK-NEXT: `-ForStmt [[B:0x.*]] +// CHECK-NEXT: |-<<>> +// CHECK-NEXT: |-<<>> +// CHECK-NEXT: |-<<>> +// CHECK-NEXT: |-<<>> +// CHECK-NEXT: `-CompoundStmt {{.*}} +// CHECK-NEXT: |-BreakStmt {{.*}} 'a' (WhileStmt [[A]]) +// CHECK-NEXT: |-ContinueStmt {{.*}} 'b' (WhileStmt [[A]]) +// CHECK-NEXT: `-BreakStmt {{.*}} '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 Date: Sat, 9 Aug 2025 18:00:34 +0200 Subject: [PATCH 14/40] 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; def ext_c2y_case_range : Extension< "case ranges are a C2y extension">, InGroup; -def warn_c2y_labeled_break_continue: Warning< - "labeled %select{'break'|'continue'}0 is incompatible with C standards before C2y">, - DefaultIgnore, InGroup; -def ext_c2y_labeled_break_continue: Extension< - "labeled %select{'break'|'continue'}0 is a C2y extension">, InGroup; +def warn_c2y_labeled_break_continue + : Warning<"labeled %select{'break'|'continue'}0 is incompatible with C " + "standards before C2y">, + DefaultIgnore, + InGroup; +def ext_c2y_labeled_break_continue + : Extension<"labeled %select{'break'|'continue'}0 is a C2y extension">, + InGroup; // 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; 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(S); Info.BreakContinueStack.push_back(B->isLabeled() ? B->getLabelTarget() - : nullptr); + : nullptr); return isa(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 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(S)->getBody(), ParentScope); + BuildScopeInformationForLoopOrSwitch(S, cast(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(S)->isLabeled()) goto RecordJumpScope; + if (cast(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(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(From)) { S.Diag(L->getLabelLoc(), diag::err_break_continue_label_not_found) - << isa(L); + << isa(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 { } 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 Date: Sat, 9 Aug 2025 18:02:27 +0200 Subject: [PATCH 15/40] 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 Date: Sat, 9 Aug 2025 18:05:55 +0200 Subject: [PATCH 16/40] 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 `_ 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 Date: Sat, 9 Aug 2025 18:14:04 +0200 Subject: [PATCH 17/40] 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 @@

C2y implementation status

Named loops, v3 N3355 - No + Clang 22 From aaa117c3ad8a5f89323482b010048f4ea258d9b6 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Sat, 9 Aug 2025 18:57:36 +0200 Subject: [PATCH 18/40] 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 Date: Sat, 9 Aug 2025 19:03:42 +0200 Subject: [PATCH 19/40] 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 Date: Thu, 14 Aug 2025 15:56:10 +0200 Subject: [PATCH 20/40] 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 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(S) && !isa(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(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(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(S)->getInit()) { + BuildScopeInformation(Init, ParentScope); + ++StmtsToSkip; + } + if (VarDecl *Var = cast(S)->getConditionVariable()) { + BuildScopeInformation(Var, ParentScope); + ++StmtsToSkip; + } + goto RecordJumpScope; case Stmt::GCCAsmStmtClass: if (!cast(S)->isAsmGoto()) @@ -381,36 +365,6 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S, Jumps.push_back(S); break; - case Stmt::BreakStmtClass: - case Stmt::ContinueStmtClass: - if (cast(S)->isLabeled()) - goto RecordJumpScope; - break; - - case Stmt::WhileStmtClass: { - BuildScopeInformationForLoopOrSwitch(S, cast(S)->getBody(), - ParentScope); - return; - } - - case Stmt::DoStmtClass: { - BuildScopeInformationForLoopOrSwitch(S, cast(S)->getBody(), - ParentScope); - return; - } - - case Stmt::ForStmtClass: { - BuildScopeInformationForLoopOrSwitch(S, cast(S)->getBody(), - ParentScope); - return; - } - - case Stmt::CXXForRangeStmtClass: { - BuildScopeInformationForLoopOrSwitch(S, cast(S)->getBody(), - ParentScope); - return; - } - case Stmt::IfStmtClass: { IfStmt *IS = cast(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(Jump)) { - assert(L->isLabeled() && "expected labeled break/continue"); - bool IsContinue = isa(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(Target)) { - S.Diag(L->getLabelLoc(), diag::err_break_continue_label_not_found) - << !IsContinue; - continue; - } - - if (IsContinue && isa(Target)) { - S.Diag(L->getLabelLoc(), diag::err_continue_switch); - continue; - } - - CheckJump(L, Target, L->getKwLoc(), 0, 0, 0); - continue; - } - SwitchStmt *SS = cast(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(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(From)) { + if (isa(From) || isa(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(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(From)) { - S.Diag(L->getLabelLoc(), diag::err_break_continue_label_not_found) - << isa(L); - return; - } - // Pull out (and reverse) any scopes we might need to diagnose skipping. SmallVector ToScopesCompat; SmallVector 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(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(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(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::TransformContinueStmt(ContinueStmt *S) { if (!LD) return StmtError(); - return SemaRef.ActOnContinueStmt(S->getKwLoc(), /*CurScope=*/nullptr, - cast(LD), S->getLabelLoc()); + return new (SemaRef.Context) + ContinueStmt(S->getKwLoc(), S->getLabelLoc(), cast(LD)); } template @@ -8576,8 +8576,8 @@ TreeTransform::TransformBreakStmt(BreakStmt *S) { if (!LD) return StmtError(); - return SemaRef.ActOnBreakStmt(S->getKwLoc(), /*CurScope=*/nullptr, - cast(LD), S->getLabelLoc()); + return new (SemaRef.Context) + BreakStmt(S->getKwLoc(), S->getLabelLoc(), cast(LD)); } template 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 Date: Thu, 14 Aug 2025 16:36:15 +0200 Subject: [PATCH 21/40] 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 *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 *LoopOrSwitchNames = nullptr); StmtResult ParseStatementOrDeclarationAfterAttributes( StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, ParsedAttributes &DeclAttrs, - ParsedAttributes &DeclSpecAttrs, LabelDecl *Name); + ParsedAttributes &DeclSpecAttrs, SmallVectorImpl *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 *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 *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 *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 *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 *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 *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 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 getLoopOrSwitchNames() const { + return LoopOrSwitchNames; + } + + void setLoopOrSwitchNames(ArrayRef 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 *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 *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 *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 *LoopOrSwitchNames) { assert(Tok.is(tok::identifier) && Tok.getIdentifierInfo() && "Not an identifier!"); + SmallVector 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 *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 *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 *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{}); 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 *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 *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 Date: Thu, 14 Aug 2025 16:37:29 +0200 Subject: [PATCH 22/40] 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 *LoopOrSwitchNames = nullptr); + StmtResult ParseStatementOrDeclaration( + StmtVector &Stmts, ParsedStmtContext StmtCtx, + SourceLocation *TrailingElseLoc = nullptr, + SmallVectorImpl *LoopOrSwitchNames = nullptr); StmtResult ParseStatementOrDeclarationAfterAttributes( StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, ParsedAttributes &DeclAttrs, - ParsedAttributes &DeclSpecAttrs, SmallVectorImpl *LoopOrSwitchNames); + ParsedAttributes &DeclSpecAttrs, + SmallVectorImpl *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 *LoopOrSwitchNames); + StmtResult + ParseLabeledStatement(ParsedAttributes &Attrs, ParsedStmtContext StmtCtx, + SmallVectorImpl *LoopOrSwitchNames); /// ParseCaseStatement /// \verbatim @@ -7400,7 +7402,9 @@ class Parser : public CodeCompletionHandler { /// 'switch' '(' expression ')' statement /// [C++] 'switch' '(' condition ')' statement /// \endverbatim - StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc, SmallVectorImpl *LoopOrSwitchNames); + StmtResult + ParseSwitchStatement(SourceLocation *TrailingElseLoc, + SmallVectorImpl *LoopOrSwitchNames); /// ParseWhileStatement /// \verbatim @@ -7408,7 +7412,9 @@ class Parser : public CodeCompletionHandler { /// 'while' '(' expression ')' statement /// [C++] 'while' '(' condition ')' statement /// \endverbatim - StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc, SmallVectorImpl *LoopOrSwitchNames); + StmtResult + ParseWhileStatement(SourceLocation *TrailingElseLoc, + SmallVectorImpl *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 *LoopOrSwitchNames); + StmtResult ParseForStatement(SourceLocation *TrailingElseLoc, + SmallVectorImpl *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 *LoopOrSwitchNames); + StmtResult + ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, + SourceLocation *TrailingElseLoc, ParsedAttributes &Attrs, + SmallVectorImpl *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 *LoopOrSwitchNames) { +StmtResult +Parser::ParseStatement(SourceLocation *TrailingElseLoc, + ParsedStmtContext StmtCtx, + SmallVectorImpl *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 *LoopOrSwitchNames) { +StmtResult Parser::ParseStatementOrDeclaration( + StmtVector &Stmts, ParsedStmtContext StmtCtx, + SourceLocation *TrailingElseLoc, + SmallVectorImpl *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 *LoopOrSwitchNames) { + ParsedAttributes &GNUAttrs, + SmallVectorImpl *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 *LoopOrSwitchNames) { +StmtResult +Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc, + SmallVectorImpl *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 *LoopOrSwitchNames) { +StmtResult +Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, + SmallVectorImpl *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 *LoopOrSwitchNames) { +StmtResult +Parser::ParseDoStatement(SmallVectorImpl *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 *LoopOrSwitchNames) { +StmtResult +Parser::ParseForStatement(SourceLocation *TrailingElseLoc, + SmallVectorImpl *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 *LoopOrSwitchNames) { +StmtResult +Parser::ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, + SourceLocation *TrailingElseLoc, + ParsedAttributes &Attrs, + SmallVectorImpl *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 Date: Thu, 14 Aug 2025 17:41:14 +0200 Subject: [PATCH 23/40] 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; def ext_c2y_case_range : Extension< "case ranges are a C2y extension">, InGroup; -def warn_c2y_labeled_break_continue - : Warning<"labeled %select{'break'|'continue'}0 is incompatible with C " - "standards before C2y">, - DefaultIgnore, - InGroup; -def ext_c2y_labeled_break_continue - : Extension<"labeled %select{'break'|'continue'}0 is a C2y extension">, - InGroup; +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, 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, + NegFlag>; + // C++ Coroutines defm coroutines : BoolFOption<"coroutines", LangOpts<"Coroutines">, Default, 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() { From 8ac7910152bf3a0ee8457cb59768722d85d899ef Mon Sep 17 00:00:00 2001 From: Sirraide Date: Thu, 14 Aug 2025 17:43:17 +0200 Subject: [PATCH 24/40] Update docs --- clang/docs/LanguageExtensions.rst | 1 - clang/docs/ReleaseNotes.rst | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index 7949a6adfb5f6..b5bb198ca637a 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -1709,7 +1709,6 @@ 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/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 4d8ea6a15e30b..831b2a47bca4e 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -103,8 +103,7 @@ C Language Changes C2y Feature Support ^^^^^^^^^^^^^^^^^^^ -- Clang now supports `N3355 `_ Named Loops. This feature - is also available in earlier language modes and in C++ as an extension. +- Clang now supports `N3355 `_ Named Loops. C23 Feature Support ^^^^^^^^^^^^^^^^^^^ From 9377a808fa661c3bed1bfce1db88f6efac3ce3a0 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Wed, 20 Aug 2025 06:42:47 +0200 Subject: [PATCH 25/40] Address review comments --- clang/include/clang/AST/Stmt.h | 19 ++++++++++--------- clang/test/Parser/labeled-break-continue.c | 3 ++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 45930eef4f91c..d07017c29457a 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -3059,6 +3059,13 @@ class LoopControlStmt : public Stmt { setKwLoc(Loc); } + LoopControlStmt(StmtClass Class, SourceLocation Loc, SourceLocation LabelLoc, + LabelDecl *Target) + : LoopControlStmt(Class, Loc) { + setLabelLoc(LabelLoc); + setLabelDecl(Target); + } + LoopControlStmt(StmtClass Class, EmptyShell ES) : Stmt(Class, ES) {} public: @@ -3070,7 +3077,7 @@ class LoopControlStmt : public Stmt { return isLabeled() ? getLabelLoc() : getKwLoc(); } - bool isLabeled() const { return TargetLabel; } + bool isLabeled() const { return TargetLabel != nullptr; } SourceLocation getLabelLoc() const { return Label; } void setLabelLoc(SourceLocation L) { Label = L; } @@ -3102,10 +3109,7 @@ class ContinueStmt : public LoopControlStmt { public: ContinueStmt(SourceLocation CL) : LoopControlStmt(ContinueStmtClass, CL) {} ContinueStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target) - : LoopControlStmt(ContinueStmtClass, CL) { - setLabelLoc(LabelLoc); - setLabelDecl(Target); - } + : LoopControlStmt(ContinueStmtClass, CL, LabelLoc, Target) {} /// Build an empty continue statement. explicit ContinueStmt(EmptyShell Empty) @@ -3121,10 +3125,7 @@ class BreakStmt : public LoopControlStmt { public: BreakStmt(SourceLocation BL) : LoopControlStmt(BreakStmtClass, BL) {} BreakStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target) - : LoopControlStmt(BreakStmtClass, CL) { - setLabelLoc(LabelLoc); - setLabelDecl(Target); - } + : LoopControlStmt(BreakStmtClass, CL, LabelLoc, Target) {} /// Build an empty break statement. explicit BreakStmt(EmptyShell Empty) diff --git a/clang/test/Parser/labeled-break-continue.c b/clang/test/Parser/labeled-break-continue.c index ab78ead2348d2..f02db35a40dde 100644 --- a/clang/test/Parser/labeled-break-continue.c +++ b/clang/test/Parser/labeled-break-continue.c @@ -8,5 +8,6 @@ // expected-no-diagnostics void f() { - x: while (1) break x; // disabled-error {{labeled 'break' is only supported in C2y}} + x1: while (1) break x1; // disabled-error {{labeled 'break' is only supported in C2y}} + x2: while (1) continue x2; // disabled-error {{labeled 'continue' is only supported in C2y}} } From 8fad2cbed9ed758aa2c2a39b8545da5b0e1d6a81 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Wed, 20 Aug 2025 07:03:01 +0200 Subject: [PATCH 26/40] Some more minor fixes --- .../include/clang/Basic/DiagnosticSemaKinds.td | 4 ++-- clang/lib/Parse/ParseStmt.cpp | 6 +++--- clang/lib/Sema/SemaStmt.cpp | 18 ++++++++++-------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 94647a033d497..f7bb176dffe18 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10797,8 +10797,8 @@ def err_continue_not_in_loop : Error< 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">; + : Error<"'%select{break|continue}0' label does not name an enclosing " + "%select{loop or 'switch'|loop}0">; def err_continue_switch : Error<"label of 'continue' refers to a switch statement">; def warn_loop_ctrl_binds_to_inner : Warning< diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index afd3dc2451870..194982c599ffb 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -732,8 +732,8 @@ Parser::ParseLabeledStatement(ParsedAttributes &Attrs, StmtVector Stmts; ParsedAttributes EmptyCXX11Attrs(AttrFactory); SubStmt = ParseStatementOrDeclarationAfterAttributes( - Stmts, StmtCtx, nullptr, EmptyCXX11Attrs, TempAttrs, - LoopOrSwitchNames); + Stmts, StmtCtx, /*TrailingElseLoc=*/nullptr, EmptyCXX11Attrs, + TempAttrs, LoopOrSwitchNames); if (!TempAttrs.empty() && !SubStmt.isInvalid()) SubStmt = Actions.ActOnAttributedStmt(TempAttrs, SubStmt.get()); } @@ -2337,7 +2337,7 @@ StmtResult Parser::ParseBreakOrContinueStatement(bool IsContinue) { // 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; + Diag(LabelLoc, diag::err_break_continue_label_not_found) << IsContinue; return StmtError(); } } diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index cb9e313972d0d..a7ac135074ade 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -3282,16 +3282,18 @@ static void CheckJumpOutOfSEHFinally(Sema &S, SourceLocation Loc, } } -Scope *FindLabeledBreakContinueScope(Sema &S, Scope *CurScope, - SourceLocation KWLoc, LabelDecl *Target, - SourceLocation LabelLoc, bool IsBreak) { +static Scope *FindLabeledBreakContinueScope(Sema &S, Scope *CurScope, + SourceLocation KWLoc, + LabelDecl *Target, + SourceLocation LabelLoc, + bool IsContinue) { 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)) @@ -3302,14 +3304,14 @@ Scope *FindLabeledBreakContinueScope(Sema &S, Scope *CurScope, } if (Found) { - if (!IsBreak && !Found->isContinueScope()) { + if (IsContinue && !Found->isContinueScope()) { S.Diag(LabelLoc, diag::err_continue_switch); return nullptr; } return Found; } - S.Diag(LabelLoc, diag::err_break_continue_label_not_found) << IsBreak; + S.Diag(LabelLoc, diag::err_break_continue_label_not_found) << IsContinue; return nullptr; } @@ -3319,7 +3321,7 @@ StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope, if (Target) { S = FindLabeledBreakContinueScope(*this, CurScope, ContinueLoc, Target, LabelLoc, - /*IsBreak=*/false); + /*IsContinue=*/true); if (!S) return StmtError(); } else { @@ -3356,7 +3358,7 @@ StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope, if (Target) { S = FindLabeledBreakContinueScope(*this, CurScope, BreakLoc, Target, LabelLoc, - /*IsBreak=*/true); + /*IsContinue=*/false); if (!S) return StmtError(); } else { From 1a1540152432decc30fa78dde0bdeb9164b1b2db Mon Sep 17 00:00:00 2001 From: Balazs Benics Date: Fri, 22 Aug 2025 11:47:26 +0200 Subject: [PATCH 27/40] [analyzer] Add test for labeled break/continue N3355 --- clang/test/Analysis/cfg.c | 144 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/clang/test/Analysis/cfg.c b/clang/test/Analysis/cfg.c index e21f6109dbd59..0db82ef2f3d70 100644 --- a/clang/test/Analysis/cfg.c +++ b/clang/test/Analysis/cfg.c @@ -1,6 +1,9 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -Wno-error=invalid-gnu-asm-cast %s > %t 2>&1 // RUN: FileCheck --input-file=%t --check-prefix=CHECK %s +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -std=c2y -Wno-error=invalid-gnu-asm-cast %s > %t 2>&1 +// RUN: FileCheck --input-file=%t --check-prefixes=CHECK,SINCE-C26 %s + // This file is the C version of cfg.cpp. // Tests that are C-specific should go into this file. @@ -118,3 +121,144 @@ void vla_type_indirect(int x) { // Do not evaluate x void (*fp_vla)(int[x]); } + +#if __STDC_VERSION__ >= 202400L // If C26 or above +// SINCE-C26: int labeled_break_and_continue(int x) +// SINCE-C26-NEXT: [B17 (ENTRY)] +// SINCE-C26-NEXT: Succs (1): B2 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B1] +// SINCE-C26-NEXT: 1: 0 +// SINCE-C26-NEXT: 2: return [B1.1]; +// SINCE-C26-NEXT: Preds (1): B9 +// SINCE-C26-NEXT: Succs (1): B0 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B2] +// SINCE-C26-NEXT: a: +// SINCE-C26-NEXT: 1: x +// SINCE-C26-NEXT: 2: [B2.1] (ImplicitCastExpr, LValueToRValue, int) +// SINCE-C26-NEXT: T: switch [B2.2] +// SINCE-C26-NEXT: Preds (1): B17 +// SINCE-C26-NEXT: Succs (3): B9 B16 B8 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B3] +// SINCE-C26-NEXT: 1: x +// SINCE-C26-NEXT: 2: [B3.1] (ImplicitCastExpr, LValueToRValue, int) +// SINCE-C26-NEXT: 3: 2 +// SINCE-C26-NEXT: 4: [B3.2] + [B3.3] +// SINCE-C26-NEXT: 5: return [B3.4]; +// SINCE-C26-NEXT: Preds (3): B6 B7 B4 +// SINCE-C26-NEXT: Succs (1): B0 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B4] +// SINCE-C26-NEXT: c: +// SINCE-C26-NEXT: 1: x +// SINCE-C26-NEXT: 2: [B4.1] (ImplicitCastExpr, LValueToRValue, int) +// SINCE-C26-NEXT: T: switch [B4.2] +// SINCE-C26-NEXT: Preds (1): B8 +// SINCE-C26-NEXT: Succs (3): B6 B7 B3 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B5] +// SINCE-C26-NEXT: 1: x +// SINCE-C26-NEXT: 2: [B5.1] (ImplicitCastExpr, LValueToRValue, int) +// SINCE-C26-NEXT: 3: 3 +// SINCE-C26-NEXT: 4: [B5.2] + [B5.3] +// SINCE-C26-NEXT: 5: return [B5.4]; +// SINCE-C26-NEXT: Succs (1): B0 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B6] +// SINCE-C26-NEXT: case 30: +// SINCE-C26-NEXT: T: break c; +// SINCE-C26-NEXT: Preds (1): B4 +// SINCE-C26-NEXT: Succs (1): B3 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B7] +// SINCE-C26-NEXT: case 10: +// SINCE-C26-NEXT: T: break a; +// SINCE-C26-NEXT: Preds (1): B4 +// SINCE-C26-NEXT: Succs (1): B3 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B8] +// SINCE-C26-NEXT: default: +// SINCE-C26-NEXT: Preds (1): B2 +// SINCE-C26-NEXT: Succs (1): B4 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B9] +// SINCE-C26-NEXT: case 2: +// SINCE-C26-NEXT: T: break a; +// SINCE-C26-NEXT: Preds (2): B2 B11 +// SINCE-C26-NEXT: Succs (1): B1 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B10] +// SINCE-C26-NEXT: 1: 1 +// SINCE-C26-NEXT: T: do ... while [B10.1] +// SINCE-C26-NEXT: Preds (1): B12 +// SINCE-C26-NEXT: Succs (2): B14 NULL +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B11] +// SINCE-C26-NEXT: T: break b; +// SINCE-C26-NEXT: Preds (1): B13 +// SINCE-C26-NEXT: Succs (1): B9 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B12] +// SINCE-C26-NEXT: 1: x +// SINCE-C26-NEXT: 2: ++[B12.1] +// SINCE-C26-NEXT: T: continue b; +// SINCE-C26-NEXT: Preds (1): B13 +// SINCE-C26-NEXT: Succs (1): B10 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B13] +// SINCE-C26-NEXT: 1: x +// SINCE-C26-NEXT: 2: [B13.1] (ImplicitCastExpr, LValueToRValue, int) +// SINCE-C26-NEXT: 3: x +// SINCE-C26-NEXT: 4: [B13.3] (ImplicitCastExpr, LValueToRValue, int) +// SINCE-C26-NEXT: 5: [B13.2] * [B13.4] +// SINCE-C26-NEXT: 6: 100 +// SINCE-C26-NEXT: 7: [B13.5] > [B13.6] +// SINCE-C26-NEXT: T: if [B13.7] +// SINCE-C26-NEXT: Preds (2): B14 B15 +// SINCE-C26-NEXT: Succs (2): B12 B11 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B14] +// SINCE-C26-NEXT: Preds (1): B10 +// SINCE-C26-NEXT: Succs (1): B13 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B15] +// SINCE-C26-NEXT: b: +// SINCE-C26-NEXT: Preds (1): B16 +// SINCE-C26-NEXT: Succs (1): B13 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B16] +// SINCE-C26-NEXT: case 1: +// SINCE-C26-NEXT: Preds (1): B2 +// SINCE-C26-NEXT: Succs (1): B15 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B0 (EXIT)] +// SINCE-C26-NEXT: Preds (3): B1 B3 B5 +int labeled_break_and_continue(int x) { + a: switch (x) { + case 1: + b: do { + if (x * x > 100) { + ++x; + continue b; + } + break b; + } while (1); + case 2: + break a; + default: + c: switch (x) { + case 10: + break a; + case 30: + break c; + return x + 3; // dead code + } + return x + 2; + } + + return 0; +} + +#endif // __STDC_VERSION__ >= 202400L // If C26 or above From 54629786844f391e672ab2c9760755ccfafccde1 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Fri, 22 Aug 2025 12:37:21 +0200 Subject: [PATCH 28/40] Address review comments --- clang/include/clang/AST/Stmt.h | 3 ++- .../clang/Basic/DiagnosticParseKinds.td | 4 ++-- .../clang/Basic/DiagnosticSemaKinds.td | 10 ++++----- clang/test/Sema/labeled-break-continue.c | 22 +++++++++++++++---- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index d07017c29457a..a86d5548b39f1 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -3082,7 +3082,8 @@ class LoopControlStmt : public Stmt { SourceLocation getLabelLoc() const { return Label; } void setLabelLoc(SourceLocation L) { Label = L; } - LabelDecl *getLabelDecl() const { return TargetLabel; } + LabelDecl *getLabelDecl() { return TargetLabel; } + const LabelDecl *getLabelDecl() const { return TargetLabel; } void setLabelDecl(LabelDecl *S) { TargetLabel = S; } /// If this is a labeled break/continue, get the loop or switch statement diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index f64dfd7560b7e..3506f24caf2c3 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -215,8 +215,8 @@ def warn_c23_compat_case_range : Warning< DefaultIgnore, InGroup; def ext_c2y_case_range : Extension< "case ranges are a C2y extension">, InGroup; -def err_c2y_labeled_break_continue - : Error<"labeled %select{'break'|'continue'}0 is only supported in 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/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index f7bb176dffe18..46e20ad2ece3a 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10796,11 +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{break|continue}0' label does not name an enclosing " - "%select{loop or 'switch'|loop}0">; -def err_continue_switch - : Error<"label of 'continue' refers to a switch statement">; +def err_break_continue_label_not_found : Error< + "'%select{break|continue}0' label does not name an enclosing " + "%select{loop or 'switch'|loop}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; diff --git a/clang/test/Sema/labeled-break-continue.c b/clang/test/Sema/labeled-break-continue.c index d045c2f4a96f4..3d61c31e15282 100644 --- a/clang/test/Sema/labeled-break-continue.c +++ b/clang/test/Sema/labeled-break-continue.c @@ -99,7 +99,10 @@ void f6() { 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; - })) {} + })) { + ({ break a; }); + ({ continue a; }); + } b: for ( int x = ({ @@ -117,9 +120,15 @@ void f6() { continue b; // expected-error {{'continue' label does not name an enclosing loop}} 1; }) - ) {} + ) { + ({ break b; }); + ({ continue b; }); + } - c: do {} while (({ + c: do { + ({ break c; }); + ({ continue c; }); + } 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; @@ -129,7 +138,12 @@ void f6() { break d; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} continue d; // expected-error {{'continue' label does not name an enclosing loop}} 1; - })) { case 1:; } + })) { + case 1: { + ({ break d; }); + ({ continue d; }); + } + } } void f7() { From b2abfa55975b87fc3bb4ac81a8545a7017b360e1 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Fri, 22 Aug 2025 13:04:02 +0200 Subject: [PATCH 29/40] Only track a single preceding label --- clang/include/clang/Parse/Parser.h | 19 +- clang/include/clang/Sema/Scope.h | 17 +- clang/lib/Parse/ParseStmt.cpp | 60 ++-- clang/lib/Sema/Scope.cpp | 2 +- clang/lib/Sema/SemaStmt.cpp | 11 +- .../ast-dump-labeled-break-continue-json.c | 272 ++++++++---------- .../AST/ast-dump-labeled-break-continue.c | 29 +- .../AST/ast-print-labeled-break-continue.c | 11 +- clang/test/OpenMP/for_loop_messages.cpp | 4 +- clang/test/Sema/labeled-break-continue.c | 30 +- clang/test/SemaCXX/labeled-break-continue.cpp | 4 +- 11 files changed, 213 insertions(+), 246 deletions(-) diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index b7ccaa39da528..497b546a332a5 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, - SmallVectorImpl *LoopOrSwitchNames = nullptr); + LabelDecl *PrecedingLabel = nullptr); /// ParseStatementOrDeclaration - Read 'statement' or 'declaration'. /// \verbatim @@ -7270,13 +7270,13 @@ class Parser : public CodeCompletionHandler { StmtResult ParseStatementOrDeclaration( StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc = nullptr, - SmallVectorImpl *LoopOrSwitchNames = nullptr); + LabelDecl *PrecedingLabel = nullptr); StmtResult ParseStatementOrDeclarationAfterAttributes( StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, ParsedAttributes &DeclAttrs, ParsedAttributes &DeclSpecAttrs, - SmallVectorImpl *LoopOrSwitchNames); + LabelDecl *PrecedingLabel); /// Parse an expression statement. StmtResult ParseExprStatement(ParsedStmtContext StmtCtx); @@ -7293,8 +7293,7 @@ class Parser : public CodeCompletionHandler { /// \endverbatim /// StmtResult - ParseLabeledStatement(ParsedAttributes &Attrs, ParsedStmtContext StmtCtx, - SmallVectorImpl *LoopOrSwitchNames); + ParseLabeledStatement(ParsedAttributes &Attrs, ParsedStmtContext StmtCtx); /// ParseCaseStatement /// \verbatim @@ -7404,7 +7403,7 @@ class Parser : public CodeCompletionHandler { /// \endverbatim StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc, - SmallVectorImpl *LoopOrSwitchNames); + LabelDecl *PrecedingLabel); /// ParseWhileStatement /// \verbatim @@ -7414,7 +7413,7 @@ class Parser : public CodeCompletionHandler { /// \endverbatim StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc, - SmallVectorImpl *LoopOrSwitchNames); + LabelDecl *PrecedingLabel); /// ParseDoStatement /// \verbatim @@ -7422,7 +7421,7 @@ class Parser : public CodeCompletionHandler { /// 'do' statement 'while' '(' expression ')' ';' /// \endverbatim /// Note: this lets the caller parse the end ';'. - StmtResult ParseDoStatement(SmallVectorImpl *LoopOrSwitchNames); + StmtResult ParseDoStatement(LabelDecl *PrecedingLabel); /// ParseForStatement /// \verbatim @@ -7450,7 +7449,7 @@ class Parser : public CodeCompletionHandler { /// [C++0x] braced-init-list [TODO] /// \endverbatim StmtResult ParseForStatement(SourceLocation *TrailingElseLoc, - SmallVectorImpl *LoopOrSwitchNames); + LabelDecl *PrecedingLabel); /// ParseGotoStatement /// \verbatim @@ -7500,7 +7499,7 @@ class Parser : public CodeCompletionHandler { StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, ParsedAttributes &Attrs, - SmallVectorImpl *LoopOrSwitchNames); + LabelDecl *PrecedingLabel); void ParseMicrosoftIfExistsStatement(StmtVector &Stmts); diff --git a/clang/include/clang/Sema/Scope.h b/clang/include/clang/Sema/Scope.h index 67de11f043676..0d1c0ff6a1e91 100644 --- a/clang/include/clang/Sema/Scope.h +++ b/clang/include/clang/Sema/Scope.h @@ -255,9 +255,9 @@ class Scope { /// available for this variable in the current scope. llvm::SmallPtrSet ReturnSlots; - /// If this scope belongs to a loop or switch statement, the label that names - /// it, if any. - ArrayRef LoopOrSwitchNames; + /// If this scope belongs to a loop or switch statement, the label that + /// directly precedes it, if any. + LabelDecl *PrecedingLabel; void setFlags(Scope *Parent, unsigned F); @@ -272,15 +272,12 @@ class Scope { void setFlags(unsigned F) { setFlags(getParent(), F); } - /// Get the loop name of this scope. - ArrayRef getLoopOrSwitchNames() const { - return LoopOrSwitchNames; - } - - void setLoopOrSwitchNames(ArrayRef Names) { + /// Get the label that precedes this scope. + LabelDecl *getPrecedingLabel() const { return PrecedingLabel; } + void setPrecedingLabel(LabelDecl *LD) { assert((Flags & BreakScope || Flags & ContinueScope) && "not a loop or switch"); - LoopOrSwitchNames = Names; + PrecedingLabel = LD; } /// 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 194982c599ffb..36ebae09dc42f 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -39,7 +39,7 @@ using namespace clang; StmtResult Parser::ParseStatement(SourceLocation *TrailingElseLoc, ParsedStmtContext StmtCtx, - SmallVectorImpl *LoopOrSwitchNames) { + LabelDecl *PrecedingLabel) { StmtResult Res; // We may get back a null statement if we found a #pragma. Keep going until @@ -47,7 +47,7 @@ Parser::ParseStatement(SourceLocation *TrailingElseLoc, StmtVector Stmts; do { Res = ParseStatementOrDeclaration(Stmts, StmtCtx, TrailingElseLoc, - LoopOrSwitchNames); + PrecedingLabel); } while (!Res.isInvalid() && !Res.get()); return Res; @@ -56,7 +56,7 @@ Parser::ParseStatement(SourceLocation *TrailingElseLoc, StmtResult Parser::ParseStatementOrDeclaration( StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, - SmallVectorImpl *LoopOrSwitchNames) { + LabelDecl *PrecedingLabel) { ParenBraceBracketBalancer BalancerRAIIObj(*this); @@ -77,7 +77,7 @@ StmtResult Parser::ParseStatementOrDeclaration( StmtResult Res = ParseStatementOrDeclarationAfterAttributes( Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUOrMSAttrs, - LoopOrSwitchNames); + PrecedingLabel); MaybeDestroyTemplateIds(); takeAndConcatenateAttrs(CXX11Attrs, std::move(GNUOrMSAttrs)); @@ -135,7 +135,7 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, ParsedAttributes &CXX11Attrs, ParsedAttributes &GNUAttrs, - SmallVectorImpl *LoopOrSwitchNames) { + LabelDecl *PrecedingLabel) { const char *SemiError = nullptr; StmtResult Res; SourceLocation GNUAttributeLoc; @@ -169,7 +169,7 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( takeAndConcatenateAttrs(CXX11Attrs, std::move(GNUAttrs)); // identifier ':' statement - return ParseLabeledStatement(CXX11Attrs, StmtCtx, LoopOrSwitchNames); + return ParseLabeledStatement(CXX11Attrs, StmtCtx); } // Look up the identifier, and typo-correct it to a keyword if it's not @@ -283,16 +283,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, LoopOrSwitchNames); + return ParseSwitchStatement(TrailingElseLoc, PrecedingLabel); case tok::kw_while: // C99 6.8.5.1: while-statement - return ParseWhileStatement(TrailingElseLoc, LoopOrSwitchNames); + return ParseWhileStatement(TrailingElseLoc, PrecedingLabel); case tok::kw_do: // C99 6.8.5.2: do-statement - Res = ParseDoStatement(LoopOrSwitchNames); + Res = ParseDoStatement(PrecedingLabel); SemiError = "do/while"; break; case tok::kw_for: // C99 6.8.5.3: for-statement - return ParseForStatement(TrailingElseLoc, LoopOrSwitchNames); + return ParseForStatement(TrailingElseLoc, PrecedingLabel); case tok::kw_goto: // C99 6.8.6.1: goto-statement Res = ParseGotoStatement(); @@ -489,7 +489,7 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( ProhibitAttributes(CXX11Attrs); ProhibitAttributes(GNUAttrs); return ParsePragmaLoopHint(Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, - LoopOrSwitchNames); + PrecedingLabel); case tok::annot_pragma_dump: ProhibitAttributes(CXX11Attrs); @@ -687,15 +687,10 @@ static void DiagnoseLabelFollowedByDecl(Parser &P, const Stmt *SubStmt) { StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs, - ParsedStmtContext StmtCtx, - SmallVectorImpl *LoopOrSwitchNames) { + ParsedStmtContext StmtCtx) { assert(Tok.is(tok::identifier) && Tok.getIdentifierInfo() && "Not an identifier!"); - SmallVector 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. @@ -711,7 +706,6 @@ Parser::ParseLabeledStatement(ParsedAttributes &Attrs, LabelDecl *LD = Actions.LookupOrCreateLabel(IdentTok.getIdentifierInfo(), IdentTok.getLocation()); - LoopOrSwitchNames->push_back(LD); // Read label attributes, if present. StmtResult SubStmt; @@ -733,7 +727,7 @@ Parser::ParseLabeledStatement(ParsedAttributes &Attrs, ParsedAttributes EmptyCXX11Attrs(AttrFactory); SubStmt = ParseStatementOrDeclarationAfterAttributes( Stmts, StmtCtx, /*TrailingElseLoc=*/nullptr, EmptyCXX11Attrs, - TempAttrs, LoopOrSwitchNames); + TempAttrs, LD); if (!TempAttrs.empty() && !SubStmt.isInvalid()) SubStmt = Actions.ActOnAttributedStmt(TempAttrs, SubStmt.get()); } @@ -747,7 +741,7 @@ Parser::ParseLabeledStatement(ParsedAttributes &Attrs, // If we've not parsed a statement yet, parse one now. if (SubStmt.isUnset()) - SubStmt = ParseStatement(nullptr, StmtCtx, LoopOrSwitchNames); + SubStmt = ParseStatement(nullptr, StmtCtx, LD); // Broken substmt shouldn't prevent the label from being added to the AST. if (SubStmt.isInvalid()) @@ -1637,7 +1631,7 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) { StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc, - SmallVectorImpl *LoopOrSwitchNames) { + LabelDecl *PrecedingLabel) { assert(Tok.is(tok::kw_switch) && "Not a switch stmt!"); SourceLocation SwitchLoc = ConsumeToken(); // eat the 'switch'. @@ -1703,8 +1697,7 @@ Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc, // condition and a new scope for substatement in C++. // getCurScope()->AddFlags(Scope::BreakScope); - if (LoopOrSwitchNames) - getCurScope()->setLoopOrSwitchNames(*LoopOrSwitchNames); + getCurScope()->setPrecedingLabel(PrecedingLabel); ParseScope InnerScope(this, Scope::DeclScope, C99orCXX, Tok.is(tok::l_brace)); // We have incremented the mangling number for the SwitchScope and the @@ -1724,7 +1717,7 @@ Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc, StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, - SmallVectorImpl *LoopOrSwitchNames) { + LabelDecl *PrecedingLabel) { assert(Tok.is(tok::kw_while) && "Not a while stmt!"); SourceLocation WhileLoc = Tok.getLocation(); ConsumeToken(); // eat the 'while'. @@ -1769,8 +1762,7 @@ Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, // combinations, so diagnose that here in OpenACC mode. SemaOpenACC::LoopInConstructRAII LCR{getActions().OpenACC()}; getActions().OpenACC().ActOnWhileStmt(WhileLoc); - if (LoopOrSwitchNames) - getCurScope()->setLoopOrSwitchNames(*LoopOrSwitchNames); + getCurScope()->setPrecedingLabel(PrecedingLabel); // 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 @@ -1803,7 +1795,7 @@ Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, } StmtResult -Parser::ParseDoStatement(SmallVectorImpl *LoopOrSwitchNames) { +Parser::ParseDoStatement(LabelDecl *PrecedingLabel) { assert(Tok.is(tok::kw_do) && "Not a do stmt!"); SourceLocation DoLoc = ConsumeToken(); // eat the 'do'. @@ -1821,8 +1813,7 @@ Parser::ParseDoStatement(SmallVectorImpl *LoopOrSwitchNames) { // combinations, so diagnose that here in OpenACC mode. SemaOpenACC::LoopInConstructRAII LCR{getActions().OpenACC()}; getActions().OpenACC().ActOnDoStmt(DoLoc); - if (LoopOrSwitchNames) - getCurScope()->setLoopOrSwitchNames(*LoopOrSwitchNames); + getCurScope()->setPrecedingLabel(PrecedingLabel); // 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 @@ -1842,7 +1833,7 @@ Parser::ParseDoStatement(SmallVectorImpl *LoopOrSwitchNames) { InnerScope.Exit(); // Reset this to disallow break/continue out of the condition. - getCurScope()->setLoopOrSwitchNames(ArrayRef{}); + getCurScope()->setPrecedingLabel(nullptr); if (Tok.isNot(tok::kw_while)) { if (!Body.isInvalid()) { @@ -1907,7 +1898,7 @@ bool Parser::isForRangeIdentifier() { StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc, - SmallVectorImpl *LoopOrSwitchNames) { + LabelDecl *PrecedingLabel) { assert(Tok.is(tok::kw_for) && "Not a for stmt!"); SourceLocation ForLoc = ConsumeToken(); // eat the 'for'. @@ -2241,8 +2232,7 @@ Parser::ParseForStatement(SourceLocation *TrailingElseLoc, // Set this only right before parsing the body to disallow break/continue in // the other parts. - if (LoopOrSwitchNames) - getCurScope()->setLoopOrSwitchNames(*LoopOrSwitchNames); + getCurScope()->setPrecedingLabel(PrecedingLabel); // 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 @@ -2397,7 +2387,7 @@ StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, ParsedAttributes &Attrs, - SmallVectorImpl *LoopOrSwitchNames) { + LabelDecl *PrecedingLabel) { // Create temporary attribute list. ParsedAttributes TempAttrs(AttrFactory); @@ -2422,7 +2412,7 @@ Parser::ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, ParsedAttributes EmptyDeclSpecAttrs(AttrFactory); StmtResult S = ParseStatementOrDeclarationAfterAttributes( Stmts, StmtCtx, TrailingElseLoc, Attrs, EmptyDeclSpecAttrs, - LoopOrSwitchNames); + PrecedingLabel); Attrs.takeAllFrom(TempAttrs); diff --git a/clang/lib/Sema/Scope.cpp b/clang/lib/Sema/Scope.cpp index 3993d5b230202..e66cce255230b 100644 --- a/clang/lib/Sema/Scope.cpp +++ b/clang/lib/Sema/Scope.cpp @@ -99,7 +99,7 @@ void Scope::Init(Scope *parent, unsigned flags) { UsingDirectives.clear(); Entity = nullptr; ErrorTrap.reset(); - LoopOrSwitchNames = {}; + PrecedingLabel = nullptr; NRVO = std::nullopt; } diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index a7ac135074ade..4a7e7bd730a99 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -3287,20 +3287,23 @@ static Scope *FindLabeledBreakContinueScope(Sema &S, Scope *CurScope, LabelDecl *Target, SourceLocation LabelLoc, bool IsContinue) { + assert(Target && "not a labeled break/continue?"); 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; return nullptr; } - if (!llvm::is_contained(Scope->getLoopOrSwitchNames(), Target)) - continue; - if (Scope->isBreakOrContinueScope()) + + if (Scope->isBreakOrContinueScope() && + Scope->getPrecedingLabel() == Target) { Found = Scope; - break; + break; + } } if (Found) { diff --git a/clang/test/AST/ast-dump-labeled-break-continue-json.c b/clang/test/AST/ast-dump-labeled-break-continue-json.c index 5e04a5df2864e..19f8ff300a187 100644 --- a/clang/test/AST/ast-dump-labeled-break-continue-json.c +++ b/clang/test/AST/ast-dump-labeled-break-continue-json.c @@ -1,18 +1,20 @@ // RUN: %clang_cc1 -std=c2y -ast-dump=json -ast-dump-filter Test %s | FileCheck %s void TestLabeledBreakContinue() { - a: b: while (true) { + a: while (true) { break a; - continue b; + continue a; c: for (;;) { break a; - continue b; + continue a; break c; } } } // NOTE: CHECK lines have been autogenerated by gen_ast_dump_json_test.py + + // CHECK-NOT: {{^}}Dumping // CHECK: "kind": "FunctionDecl", // CHECK-NEXT: "loc": { @@ -29,7 +31,7 @@ void TestLabeledBreakContinue() { // CHECK-NEXT: "tokLen": 4 // CHECK-NEXT: }, // CHECK-NEXT: "end": { -// CHECK-NEXT: "offset": 246, +// CHECK-NEXT: "offset": 243, // CHECK-NEXT: "line": 13, // CHECK-NEXT: "col": 1, // CHECK-NEXT: "tokLen": 1 @@ -52,7 +54,7 @@ void TestLabeledBreakContinue() { // CHECK-NEXT: "tokLen": 1 // CHECK-NEXT: }, // CHECK-NEXT: "end": { -// CHECK-NEXT: "offset": 246, +// CHECK-NEXT: "offset": 243, // CHECK-NEXT: "line": 13, // CHECK-NEXT: "col": 1, // CHECK-NEXT: "tokLen": 1 @@ -70,7 +72,7 @@ void TestLabeledBreakContinue() { // CHECK-NEXT: "tokLen": 1 // CHECK-NEXT: }, // CHECK-NEXT: "end": { -// CHECK-NEXT: "offset": 244, +// CHECK-NEXT: "offset": 241, // CHECK-NEXT: "line": 12, // CHECK-NEXT: "col": 3, // CHECK-NEXT: "tokLen": 1 @@ -81,36 +83,55 @@ void TestLabeledBreakContinue() { // CHECK-NEXT: "inner": [ // CHECK-NEXT: { // CHECK-NEXT: "id": "0x{{.*}}", -// CHECK-NEXT: "kind": "LabelStmt", +// CHECK-NEXT: "kind": "WhileStmt", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { // CHECK-NEXT: "offset": 123, // CHECK-NEXT: "line": 4, // CHECK-NEXT: "col": 6, -// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: "tokLen": 5 // CHECK-NEXT: }, // CHECK-NEXT: "end": { -// CHECK-NEXT: "offset": 244, +// CHECK-NEXT: "offset": 241, // 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: "kind": "CXXBoolLiteralExpr", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { -// CHECK-NEXT: "offset": 126, +// CHECK-NEXT: "offset": 130, // CHECK-NEXT: "line": 4, -// CHECK-NEXT: "col": 9, -// CHECK-NEXT: "tokLen": 5 +// CHECK-NEXT: "col": 13, +// CHECK-NEXT: "tokLen": 4 // CHECK-NEXT: }, // CHECK-NEXT: "end": { -// CHECK-NEXT: "offset": 244, +// CHECK-NEXT: "offset": 130, +// CHECK-NEXT: "col": 13, +// 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": 136, +// CHECK-NEXT: "col": 19, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 241, // CHECK-NEXT: "line": 12, // CHECK-NEXT: "col": 3, // CHECK-NEXT: "tokLen": 1 @@ -119,194 +140,153 @@ void TestLabeledBreakContinue() { // CHECK-NEXT: "inner": [ // CHECK-NEXT: { // CHECK-NEXT: "id": "0x{{.*}}", -// CHECK-NEXT: "kind": "CXXBoolLiteralExpr", +// CHECK-NEXT: "kind": "BreakStmt", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { -// CHECK-NEXT: "offset": 133, -// CHECK-NEXT: "line": 4, -// CHECK-NEXT: "col": 16, -// CHECK-NEXT: "tokLen": 4 +// CHECK-NEXT: "offset": 142, +// CHECK-NEXT: "line": 5, +// CHECK-NEXT: "col": 5, +// CHECK-NEXT: "tokLen": 5 // CHECK-NEXT: }, // CHECK-NEXT: "end": { -// CHECK-NEXT: "offset": 133, -// CHECK-NEXT: "col": 16, -// CHECK-NEXT: "tokLen": 4 +// CHECK-NEXT: "offset": 148, +// CHECK-NEXT: "col": 11, +// CHECK-NEXT: "tokLen": 1 // CHECK-NEXT: } // CHECK-NEXT: }, -// CHECK-NEXT: "type": { -// CHECK-NEXT: "qualType": "bool" +// 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": 155, +// CHECK-NEXT: "line": 6, +// CHECK-NEXT: "col": 5, +// CHECK-NEXT: "tokLen": 8 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 164, +// CHECK-NEXT: "col": 14, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } // CHECK-NEXT: }, -// CHECK-NEXT: "valueCategory": "prvalue", -// CHECK-NEXT: "value": true +// CHECK-NEXT: "targetLabelDeclId": "0x{{.*}}" // CHECK-NEXT: }, // CHECK-NEXT: { // CHECK-NEXT: "id": "0x{{.*}}", -// CHECK-NEXT: "kind": "CompoundStmt", +// CHECK-NEXT: "kind": "LabelStmt", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { -// CHECK-NEXT: "offset": 139, -// CHECK-NEXT: "col": 22, +// CHECK-NEXT: "offset": 171, +// CHECK-NEXT: "line": 7, +// CHECK-NEXT: "col": 5, // CHECK-NEXT: "tokLen": 1 // CHECK-NEXT: }, // CHECK-NEXT: "end": { -// CHECK-NEXT: "offset": 244, -// CHECK-NEXT: "line": 12, -// CHECK-NEXT: "col": 3, +// CHECK-NEXT: "offset": 237, +// 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": "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: "kind": "ForStmt", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { // CHECK-NEXT: "offset": 174, // CHECK-NEXT: "line": 7, -// CHECK-NEXT: "col": 5, -// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: "col": 8, +// CHECK-NEXT: "tokLen": 3 // CHECK-NEXT: }, // CHECK-NEXT: "end": { -// CHECK-NEXT: "offset": 240, +// CHECK-NEXT: "offset": 237, // 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: {}, +// CHECK-NEXT: {}, +// CHECK-NEXT: {}, // CHECK-NEXT: { // CHECK-NEXT: "id": "0x{{.*}}", -// CHECK-NEXT: "kind": "ForStmt", +// CHECK-NEXT: "kind": "CompoundStmt", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { -// CHECK-NEXT: "offset": 177, +// CHECK-NEXT: "offset": 183, // CHECK-NEXT: "line": 7, -// CHECK-NEXT: "col": 8, -// CHECK-NEXT: "tokLen": 3 +// CHECK-NEXT: "col": 17, +// CHECK-NEXT: "tokLen": 1 // CHECK-NEXT: }, // CHECK-NEXT: "end": { -// CHECK-NEXT: "offset": 240, +// CHECK-NEXT: "offset": 237, // 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: "kind": "BreakStmt", // CHECK-NEXT: "range": { // CHECK-NEXT: "begin": { -// CHECK-NEXT: "offset": 186, -// CHECK-NEXT: "line": 7, -// CHECK-NEXT: "col": 17, -// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: "offset": 191, +// CHECK-NEXT: "line": 8, +// CHECK-NEXT: "col": 7, +// CHECK-NEXT: "tokLen": 5 // CHECK-NEXT: }, // CHECK-NEXT: "end": { -// CHECK-NEXT: "offset": 240, -// CHECK-NEXT: "line": 11, -// CHECK-NEXT: "col": 5, +// CHECK-NEXT: "offset": 197, +// CHECK-NEXT: "col": 13, // 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: "targetLabelDeclId": "0x{{.*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ContinueStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 206, +// CHECK-NEXT: "line": 9, +// CHECK-NEXT: "col": 7, +// CHECK-NEXT: "tokLen": 8 // 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: "end": { +// CHECK-NEXT: "offset": 215, +// 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": 224, +// CHECK-NEXT: "line": 10, +// CHECK-NEXT: "col": 7, +// CHECK-NEXT: "tokLen": 5 // 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: "end": { +// CHECK-NEXT: "offset": 230, +// CHECK-NEXT: "col": 13, +// CHECK-NEXT: "tokLen": 1 // CHECK-NEXT: } -// CHECK-NEXT: ] +// CHECK-NEXT: }, +// CHECK-NEXT: "targetLabelDeclId": "0x{{.*}}" // 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 index 7ef3c67460fe8..a1ec812017557 100644 --- a/clang/test/AST/ast-dump-labeled-break-continue.c +++ b/clang/test/AST/ast-dump-labeled-break-continue.c @@ -9,33 +9,32 @@ // RUN: | FileCheck -strict-whitespace %s void TestLabeledBreakContinue() { - a: b: while (true) { + a: while (true) { break a; - continue b; + continue a; c: for (;;) { break a; - continue b; + continue a; break c; } } } // CHECK-LABEL: `-FunctionDecl {{.*}} TestLabeledBreakContinue -// CHECK-NEXT: `-CompoundStmt {{.*}} -// CHECK-NEXT: `-LabelStmt {{.*}} 'a' -// CHECK-NEXT: `-LabelStmt {{.*}} 'b' -// CHECK-NEXT: `-WhileStmt [[A:0x.*]] -// CHECK-NEXT: |-CXXBoolLiteralExpr {{.*}} 'bool' true -// CHECK-NEXT: `-CompoundStmt {{.*}} -// CHECK-NEXT: |-BreakStmt {{.*}} 'a' (WhileStmt [[A]]) -// CHECK-NEXT: |-ContinueStmt {{.*}} 'b' (WhileStmt [[A]]) +// CHECK-NEXT: `-CompoundStmt {{.*}} +// CHECK-NEXT: `-LabelStmt {{.*}} 'a' +// CHECK-NEXT: `-WhileStmt {{.*}} +// CHECK-NEXT: |-CXXBoolLiteralExpr {{.*}} 'bool' true +// CHECK-NEXT: `-CompoundStmt {{.*}} +// CHECK-NEXT: |-BreakStmt {{.*}} 'a' (WhileStmt {{.*}}) +// CHECK-NEXT: |-ContinueStmt {{.*}} 'a' (WhileStmt {{.*}}) // CHECK-NEXT: `-LabelStmt {{.*}} 'c' -// CHECK-NEXT: `-ForStmt [[B:0x.*]] +// CHECK-NEXT: `-ForStmt {{.*}} // CHECK-NEXT: |-<<>> // CHECK-NEXT: |-<<>> // CHECK-NEXT: |-<<>> // CHECK-NEXT: |-<<>> // CHECK-NEXT: `-CompoundStmt {{.*}} -// CHECK-NEXT: |-BreakStmt {{.*}} 'a' (WhileStmt [[A]]) -// CHECK-NEXT: |-ContinueStmt {{.*}} 'b' (WhileStmt [[A]]) -// CHECK-NEXT: `-BreakStmt {{.*}} 'c' (ForStmt [[B]]) +// CHECK-NEXT: |-BreakStmt {{.*}} 'a' (WhileStmt {{.*}}) +// CHECK-NEXT: |-ContinueStmt {{.*}} 'a' (WhileStmt {{.*}}) +// CHECK-NEXT: `-BreakStmt {{.*}} 'c' (ForStmt {{.*}}) diff --git a/clang/test/AST/ast-print-labeled-break-continue.c b/clang/test/AST/ast-print-labeled-break-continue.c index d6f5c42687c7c..163bb759aa59e 100644 --- a/clang/test/AST/ast-print-labeled-break-continue.c +++ b/clang/test/AST/ast-print-labeled-break-continue.c @@ -1,12 +1,12 @@ // RUN: %clang_cc1 -std=c2y -ast-print %s | FileCheck %s void TestLabeledBreakContinue() { - a: b: while (true) { + a: while (true) { break a; - continue b; + continue a; c: for (;;) { break a; - continue b; + continue a; break c; } } @@ -14,15 +14,14 @@ void TestLabeledBreakContinue() { // 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: continue a; // CHECK-NEXT: c: // CHECK-NEXT: for (;;) { // CHECK-NEXT: break a; -// CHECK-NEXT: continue b; +// CHECK-NEXT: continue a; // CHECK-NEXT: break c; // CHECK-NEXT: } // CHECK-NEXT: } diff --git a/clang/test/OpenMP/for_loop_messages.cpp b/clang/test/OpenMP/for_loop_messages.cpp index 5dc80d095c5f6..5f6f9c9a3fbc9 100644 --- a/clang/test/OpenMP/for_loop_messages.cpp +++ b/clang/test/OpenMP/for_loop_messages.cpp @@ -852,12 +852,12 @@ void test_labeled_break() { continue a; } - b: c: while (1) { + b: while (1) { #pragma omp parallel #pragma omp for for (int i = 0; i < 16; ++i) { 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}} + continue b; // expected-error {{'continue' label does not name an enclosing loop}} } } } diff --git a/clang/test/Sema/labeled-break-continue.c b/clang/test/Sema/labeled-break-continue.c index 3d61c31e15282..78f81c484c3d5 100644 --- a/clang/test/Sema/labeled-break-continue.c +++ b/clang/test/Sema/labeled-break-continue.c @@ -46,26 +46,26 @@ void f2() { void f3() { a: b: c: d: while (true) { - break a; - break b; - break c; + break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + break c; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} break d; - continue a; - continue b; - continue c; + continue a; // expected-error {{'continue' label does not name an enclosing loop}} + continue b; // expected-error {{'continue' label does not name an enclosing loop}} + continue c; // expected-error {{'continue' label does not name an enclosing loop}} continue d; e: while (true) { - break a; - break b; - break c; + break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + break c; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} break d; break e; - continue a; - continue b; - continue c; + continue a; // expected-error {{'continue' label does not name an enclosing loop}} + continue b; // expected-error {{'continue' label does not name an enclosing loop}} + continue c; // expected-error {{'continue' label does not name an enclosing loop}} continue d; continue e; } @@ -141,16 +141,16 @@ void f6() { })) { case 1: { ({ break d; }); - ({ continue d; }); + ({ continue d; }); // expected-error {{label of 'continue' refers to a switch statement}} } } } void f7() { - a: b: while (true) { + a: while (true) { (void) ^{ 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}} + continue a; // 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 22b47913a7e7b..3d34211ed745a 100644 --- a/clang/test/SemaCXX/labeled-break-continue.cpp +++ b/clang/test/SemaCXX/labeled-break-continue.cpp @@ -42,10 +42,10 @@ void f2() { } void f3() { - a: b: while (true) { + a: while (true) { (void) []{ 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}} + continue a; // expected-error {{'continue' label does not name an enclosing loop}} }; } } From 1fe9d0df0c75cdb9d83249e058346e1be0492a32 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Fri, 22 Aug 2025 13:04:36 +0200 Subject: [PATCH 30/40] clang-format --- clang/include/clang/Parse/Parser.h | 33 +++++++++---------- clang/lib/Parse/ParseStmt.cpp | 51 +++++++++++++----------------- 2 files changed, 37 insertions(+), 47 deletions(-) diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 497b546a332a5..a9a87fb586fc2 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -7267,16 +7267,15 @@ class Parser : public CodeCompletionHandler { /// [OBC] '@' 'throw' ';' /// \endverbatim /// - StmtResult ParseStatementOrDeclaration( - StmtVector &Stmts, ParsedStmtContext StmtCtx, - SourceLocation *TrailingElseLoc = nullptr, - LabelDecl *PrecedingLabel = nullptr); + StmtResult + ParseStatementOrDeclaration(StmtVector &Stmts, ParsedStmtContext StmtCtx, + SourceLocation *TrailingElseLoc = nullptr, + LabelDecl *PrecedingLabel = nullptr); StmtResult ParseStatementOrDeclarationAfterAttributes( StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, ParsedAttributes &DeclAttrs, - ParsedAttributes &DeclSpecAttrs, - LabelDecl *PrecedingLabel); + ParsedAttributes &DeclSpecAttrs, LabelDecl *PrecedingLabel); /// Parse an expression statement. StmtResult ParseExprStatement(ParsedStmtContext StmtCtx); @@ -7292,8 +7291,8 @@ class Parser : public CodeCompletionHandler { /// label statement /// \endverbatim /// - StmtResult - ParseLabeledStatement(ParsedAttributes &Attrs, ParsedStmtContext StmtCtx); + StmtResult ParseLabeledStatement(ParsedAttributes &Attrs, + ParsedStmtContext StmtCtx); /// ParseCaseStatement /// \verbatim @@ -7401,9 +7400,8 @@ class Parser : public CodeCompletionHandler { /// 'switch' '(' expression ')' statement /// [C++] 'switch' '(' condition ')' statement /// \endverbatim - StmtResult - ParseSwitchStatement(SourceLocation *TrailingElseLoc, - LabelDecl *PrecedingLabel); + StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel); /// ParseWhileStatement /// \verbatim @@ -7411,9 +7409,8 @@ class Parser : public CodeCompletionHandler { /// 'while' '(' expression ')' statement /// [C++] 'while' '(' condition ')' statement /// \endverbatim - StmtResult - ParseWhileStatement(SourceLocation *TrailingElseLoc, - LabelDecl *PrecedingLabel); + StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel); /// ParseDoStatement /// \verbatim @@ -7496,10 +7493,10 @@ class Parser : public CodeCompletionHandler { StmtResult ParseBreakOrContinueStatement(bool IsContinue); - StmtResult - ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, - SourceLocation *TrailingElseLoc, ParsedAttributes &Attrs, - LabelDecl *PrecedingLabel); + StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, + SourceLocation *TrailingElseLoc, + ParsedAttributes &Attrs, + LabelDecl *PrecedingLabel); void ParseMicrosoftIfExistsStatement(StmtVector &Stmts); diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 36ebae09dc42f..62361c066a3f3 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -36,10 +36,9 @@ using namespace clang; // C99 6.8: Statements and Blocks. //===----------------------------------------------------------------------===// -StmtResult -Parser::ParseStatement(SourceLocation *TrailingElseLoc, - ParsedStmtContext StmtCtx, - LabelDecl *PrecedingLabel) { +StmtResult Parser::ParseStatement(SourceLocation *TrailingElseLoc, + ParsedStmtContext StmtCtx, + LabelDecl *PrecedingLabel) { StmtResult Res; // We may get back a null statement if we found a #pragma. Keep going until @@ -53,10 +52,10 @@ Parser::ParseStatement(SourceLocation *TrailingElseLoc, return Res; } -StmtResult Parser::ParseStatementOrDeclaration( - StmtVector &Stmts, ParsedStmtContext StmtCtx, - SourceLocation *TrailingElseLoc, - LabelDecl *PrecedingLabel) { +StmtResult Parser::ParseStatementOrDeclaration(StmtVector &Stmts, + ParsedStmtContext StmtCtx, + SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel) { ParenBraceBracketBalancer BalancerRAIIObj(*this); @@ -134,8 +133,7 @@ class StatementFilterCCC final : public CorrectionCandidateCallback { StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, ParsedAttributes &CXX11Attrs, - ParsedAttributes &GNUAttrs, - LabelDecl *PrecedingLabel) { + ParsedAttributes &GNUAttrs, LabelDecl *PrecedingLabel) { const char *SemiError = nullptr; StmtResult Res; SourceLocation GNUAttributeLoc; @@ -685,9 +683,8 @@ static void DiagnoseLabelFollowedByDecl(Parser &P, const Stmt *SubStmt) { } } -StmtResult -Parser::ParseLabeledStatement(ParsedAttributes &Attrs, - ParsedStmtContext StmtCtx) { +StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs, + ParsedStmtContext StmtCtx) { assert(Tok.is(tok::identifier) && Tok.getIdentifierInfo() && "Not an identifier!"); @@ -1629,9 +1626,8 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) { ThenStmt.get(), ElseLoc, ElseStmt.get()); } -StmtResult -Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc, - LabelDecl *PrecedingLabel) { +StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel) { assert(Tok.is(tok::kw_switch) && "Not a switch stmt!"); SourceLocation SwitchLoc = ConsumeToken(); // eat the 'switch'. @@ -1715,9 +1711,8 @@ Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc, return Actions.ActOnFinishSwitchStmt(SwitchLoc, Switch.get(), Body.get()); } -StmtResult -Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, - LabelDecl *PrecedingLabel) { +StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel) { assert(Tok.is(tok::kw_while) && "Not a while stmt!"); SourceLocation WhileLoc = Tok.getLocation(); ConsumeToken(); // eat the 'while'. @@ -1794,8 +1789,7 @@ Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, return Actions.ActOnWhileStmt(WhileLoc, LParen, Cond, RParen, Body.get()); } -StmtResult -Parser::ParseDoStatement(LabelDecl *PrecedingLabel) { +StmtResult Parser::ParseDoStatement(LabelDecl *PrecedingLabel) { assert(Tok.is(tok::kw_do) && "Not a do stmt!"); SourceLocation DoLoc = ConsumeToken(); // eat the 'do'. @@ -1896,9 +1890,8 @@ bool Parser::isForRangeIdentifier() { return false; } -StmtResult -Parser::ParseForStatement(SourceLocation *TrailingElseLoc, - LabelDecl *PrecedingLabel) { +StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel) { assert(Tok.is(tok::kw_for) && "Not a for stmt!"); SourceLocation ForLoc = ConsumeToken(); // eat the 'for'. @@ -2383,11 +2376,11 @@ StmtResult Parser::ParseReturnStatement() { return Actions.ActOnReturnStmt(ReturnLoc, R.get(), getCurScope()); } -StmtResult -Parser::ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, - SourceLocation *TrailingElseLoc, - ParsedAttributes &Attrs, - LabelDecl *PrecedingLabel) { +StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts, + ParsedStmtContext StmtCtx, + SourceLocation *TrailingElseLoc, + ParsedAttributes &Attrs, + LabelDecl *PrecedingLabel) { // Create temporary attribute list. ParsedAttributes TempAttrs(AttrFactory); From 537b35f1a89709966d3d2efe7d90749159e21e33 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Tue, 2 Sep 2025 16:49:23 +0200 Subject: [PATCH 31/40] address review comments --- clang/include/clang/AST/Stmt.h | 24 +++++++++++++++++------ clang/lib/AST/ASTImporter.cpp | 4 ++-- clang/lib/AST/ExprConstant.cpp | 5 ++--- clang/lib/AST/JSONNodeDumper.cpp | 2 +- clang/lib/AST/Stmt.cpp | 14 ++++++++----- clang/lib/AST/StmtPrinter.cpp | 4 ++-- clang/lib/AST/TextNodeDumper.cpp | 4 ++-- clang/lib/CodeGen/CGStmt.cpp | 4 ++-- clang/lib/Sema/TreeTransform.h | 4 ++-- clang/lib/Serialization/ASTWriterStmt.cpp | 4 ++-- 10 files changed, 42 insertions(+), 27 deletions(-) diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index a86d5548b39f1..90d986f677a05 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -2173,6 +2173,14 @@ class LabelStmt : public ValueStmt { SourceLocation getBeginLoc() const { return getIdentLoc(); } SourceLocation getEndLoc() const LLVM_READONLY { return SubStmt->getEndLoc();} + /// Look through nested labels and return the first non-label statement; e.g. + /// if this is 'a:' in 'a: b: c: for(;;)', this returns the for loop/ + const Stmt *getInnermostLabeledStmt() const; + Stmt* getInnermostLabeledStmt() { + return const_cast( + const_cast(this)->getInnermostLabeledStmt()); + } + child_range children() { return child_range(&SubStmt, &SubStmt + 1); } const_child_range children() const { @@ -3048,10 +3056,14 @@ class IndirectGotoStmt : public Stmt { /// Base class for BreakStmt and ContinueStmt. class LoopControlStmt : public Stmt { /// If this is a labeled break/continue, the label whose statement we're - /// targeting. + /// targeting, as well as the source location of the label after the + /// keyword; for example: + /// + /// a: // <-- TargetLabel + /// for (;;) + /// break a; // <-- Label + /// LabelDecl *TargetLabel = nullptr; - - /// Location of the label, if any. SourceLocation Label; protected: @@ -3074,10 +3086,10 @@ class LoopControlStmt : public Stmt { SourceLocation getBeginLoc() const { return getKwLoc(); } SourceLocation getEndLoc() const { - return isLabeled() ? getLabelLoc() : getKwLoc(); + return hasLabelTarget() ? getLabelLoc() : getKwLoc(); } - bool isLabeled() const { return TargetLabel != nullptr; } + bool hasLabelTarget() const { return TargetLabel != nullptr; } SourceLocation getLabelLoc() const { return Label; } void setLabelLoc(SourceLocation L) { Label = L; } @@ -3088,7 +3100,7 @@ class LoopControlStmt : public Stmt { /// If this is a labeled break/continue, get the loop or switch statement /// that this targets. - Stmt *getLabelTarget() const; + const Stmt *getNamedLoopOrSwitch() const; // Iterators child_range children() { diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 79583b68b4112..73ccfa18ace9e 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -7412,10 +7412,10 @@ static ExpectedStmt ImportLoopControlStmt(ASTNodeImporter &NodeImporter, ASTImporter &Importer, StmtClass *S) { Error Err = Error::success(); auto ToLoc = NodeImporter.importChecked(Err, S->getKwLoc()); - auto ToLabelLoc = S->isLabeled() + auto ToLabelLoc = S->hasLabelTarget() ? NodeImporter.importChecked(Err, S->getLabelLoc()) : SourceLocation(); - auto ToDecl = S->isLabeled() + auto ToDecl = S->hasLabelTarget() ? NodeImporter.importChecked(Err, S->getLabelDecl()) : nullptr; if (Err) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 264153f7508d7..69dc81e4ae347 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -897,7 +897,7 @@ namespace { /// Stack of loops and 'switch' statements which we're currently /// breaking/continuing; null entries are used to mark unlabeled /// break/continue. - SmallVector BreakContinueStack; + SmallVector BreakContinueStack; /// Set of objects that are currently being constructed. llvm::DenseMap @@ -5979,8 +5979,7 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info, case Stmt::ContinueStmtClass: case Stmt::BreakStmtClass: { auto *B = cast(S); - Info.BreakContinueStack.push_back(B->isLabeled() ? B->getLabelTarget() - : nullptr); + Info.BreakContinueStack.push_back(B->getNamedLoopOrSwitch()); return isa(S) ? ESR_Continue : ESR_Break; } diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp index 43a61849b30f4..2ef1d93e7ca7a 100644 --- a/clang/lib/AST/JSONNodeDumper.cpp +++ b/clang/lib/AST/JSONNodeDumper.cpp @@ -1677,7 +1677,7 @@ void JSONNodeDumper::VisitLabelStmt(const LabelStmt *LS) { } void JSONNodeDumper::VisitLoopControlStmt(const LoopControlStmt *LS) { - if (LS->isLabeled()) + if (LS->hasLabelTarget()) JOS.attribute("targetLabelDeclId", createPointerRepresentation(LS->getLabelDecl())); } diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp index 030da50223f7b..7add5fe766ec8 100644 --- a/clang/lib/AST/Stmt.cpp +++ b/clang/lib/AST/Stmt.cpp @@ -1483,9 +1483,13 @@ bool CapturedStmt::capturesVariable(const VarDecl *Var) const { return false; } -Stmt *LoopControlStmt::getLabelTarget() const { - Stmt *Target = TargetLabel->getStmt(); - while (isa_and_present(Target)) - Target = cast(Target)->getSubStmt(); - return Target; +const Stmt *LabelStmt::getInnermostLabeledStmt() const { + const Stmt *S = getSubStmt(); + while (isa_and_present(S)) + S = cast(S)->getSubStmt(); + return S; +} + +Stmt *LoopControlStmt::getNamedLoopOrSwitch() const { + return getLabelDecl()->getStmt()->getInnermostLabeledStmt(); } diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp index 410a415597ea3..98dceaf2bc875 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -477,7 +477,7 @@ void StmtPrinter::VisitIndirectGotoStmt(IndirectGotoStmt *Node) { void StmtPrinter::VisitContinueStmt(ContinueStmt *Node) { Indent(); - if (Node->isLabeled()) + if (Node->hasLabelTarget()) OS << "continue " << Node->getLabelDecl()->getIdentifier()->getName() << ';'; else @@ -487,7 +487,7 @@ void StmtPrinter::VisitContinueStmt(ContinueStmt *Node) { void StmtPrinter::VisitBreakStmt(BreakStmt *Node) { Indent(); - if (Node->isLabeled()) + if (Node->hasLabelTarget()) OS << "break " << Node->getLabelDecl()->getIdentifier()->getName() << ';'; else OS << "break;"; diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp index c2d51f986ff80..2eb304a69894b 100644 --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -1414,12 +1414,12 @@ static void dumpBasePath(raw_ostream &OS, const CastExpr *Node) { } void TextNodeDumper::VisitLoopControlStmt(const LoopControlStmt *Node) { - if (!Node->isLabeled()) + if (!Node->hasLabelTarget()) return; OS << " '" << Node->getLabelDecl()->getIdentifier()->getName() << "' ("; - auto *Target = Node->getLabelTarget(); + auto *Target = Node->getNamedLoopOrSwitch(); if (!Target) { ColorScope Color(OS, ShowColors, NullColor); OS << "<<>>"; diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index 70cb869b0f540..e2b7a7136b673 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -1734,10 +1734,10 @@ void CodeGenFunction::EmitDeclStmt(const DeclStmt &S) { auto CodeGenFunction::GetDestForLoopControlStmt(const LoopControlStmt &S) -> const BreakContinue * { - if (!S.isLabeled()) + if (!S.hasLabelTarget()) return &BreakContinueStack.back(); - Stmt *LoopOrSwitch = S.getLabelTarget(); + const Stmt *LoopOrSwitch = S.getNamedLoopOrSwitch(); assert(LoopOrSwitch && "break/continue target not set?"); for (const BreakContinue &BC : llvm::reverse(BreakContinueStack)) if (BC.LoopOrSwitch == LoopOrSwitch) diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 1326c652b2828..3a04bbb1ee5b0 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -8553,7 +8553,7 @@ TreeTransform::TransformIndirectGotoStmt(IndirectGotoStmt *S) { template StmtResult TreeTransform::TransformContinueStmt(ContinueStmt *S) { - if (!S->isLabeled()) + if (!S->hasLabelTarget()) return S; Decl *LD = getDerived().TransformDecl(S->getLabelDecl()->getLocation(), @@ -8568,7 +8568,7 @@ TreeTransform::TransformContinueStmt(ContinueStmt *S) { template StmtResult TreeTransform::TransformBreakStmt(BreakStmt *S) { - if (!S->isLabeled()) + if (!S->hasLabelTarget()) return S; Decl *LD = getDerived().TransformDecl(S->getLabelDecl()->getLocation(), diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 9baaa21121ce7..a18e01477d423 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -313,8 +313,8 @@ void ASTStmtWriter::VisitIndirectGotoStmt(IndirectGotoStmt *S) { void ASTStmtWriter::VisitLoopControlStmt(LoopControlStmt *S) { VisitStmt(S); Record.AddSourceLocation(S->getKwLoc()); - Record.push_back(S->isLabeled()); - if (S->isLabeled()) { + Record.push_back(S->hasLabelTarget()); + if (S->hasLabelTarget()) { Record.AddDeclRef(S->getLabelDecl()); Record.AddSourceLocation(S->getLabelLoc()); } From f6d9e5d8be283c0e9e40d5bef8367c481765d8bc Mon Sep 17 00:00:00 2001 From: Sirraide Date: Tue, 2 Sep 2025 16:49:34 +0200 Subject: [PATCH 32/40] clang-format --- clang/include/clang/AST/Stmt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 90d986f677a05..2e5bff7383c09 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -2176,7 +2176,7 @@ class LabelStmt : public ValueStmt { /// Look through nested labels and return the first non-label statement; e.g. /// if this is 'a:' in 'a: b: c: for(;;)', this returns the for loop/ const Stmt *getInnermostLabeledStmt() const; - Stmt* getInnermostLabeledStmt() { + Stmt *getInnermostLabeledStmt() { return const_cast( const_cast(this)->getInnermostLabeledStmt()); } From 1f2dde536f31b9e60ce256b86771644ddaa7589d Mon Sep 17 00:00:00 2001 From: Sirraide Date: Tue, 2 Sep 2025 16:52:43 +0200 Subject: [PATCH 33/40] reverse constructor delegation --- clang/include/clang/AST/Stmt.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 2e5bff7383c09..3c39a000b780b 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -3067,17 +3067,15 @@ class LoopControlStmt : public Stmt { SourceLocation Label; protected: - LoopControlStmt(StmtClass Class, SourceLocation Loc) : Stmt(Class) { - setKwLoc(Loc); - } - LoopControlStmt(StmtClass Class, SourceLocation Loc, SourceLocation LabelLoc, LabelDecl *Target) - : LoopControlStmt(Class, Loc) { - setLabelLoc(LabelLoc); - setLabelDecl(Target); + : Stmt(Class), TargetLabel(Target), Label(LabelLoc) { + setKwLoc(Loc); } + LoopControlStmt(StmtClass Class, SourceLocation Loc) + : LoopControlStmt(Class, Loc, SourceLocation(), nullptr) {} + LoopControlStmt(StmtClass Class, EmptyShell ES) : Stmt(Class, ES) {} public: From c9b8688c6ca1c410fb0cbdfe6ef8c1204a8174be Mon Sep 17 00:00:00 2001 From: Sirraide Date: Tue, 2 Sep 2025 16:54:55 +0200 Subject: [PATCH 34/40] =?UTF-8?q?Update=20the=20compat=20diag=20to=20say?= =?UTF-8?q?=20=E2=80=98named=E2=80=99=20instead=20of=20=E2=80=98labeled?= =?UTF-8?q?=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- clang/include/clang/Basic/DiagnosticParseKinds.td | 2 +- clang/test/Parser/labeled-break-continue.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 3506f24caf2c3..4e14822df052d 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -216,7 +216,7 @@ def warn_c23_compat_case_range : Warning< def ext_c2y_case_range : Extension< "case ranges are a C2y extension">, InGroup; def err_c2y_labeled_break_continue : Error< - "labeled %select{'break'|'continue'}0 is only supported in C2y">; + "named %select{'break'|'continue'}0 is only supported in C2y">; // Generic errors. def err_expected_expression : Error<"expected expression">; diff --git a/clang/test/Parser/labeled-break-continue.c b/clang/test/Parser/labeled-break-continue.c index f02db35a40dde..81935884023ac 100644 --- a/clang/test/Parser/labeled-break-continue.c +++ b/clang/test/Parser/labeled-break-continue.c @@ -8,6 +8,6 @@ // expected-no-diagnostics void f() { - x1: while (1) break x1; // disabled-error {{labeled 'break' is only supported in C2y}} - x2: while (1) continue x2; // disabled-error {{labeled 'continue' is only supported in C2y}} + x1: while (1) break x1; // disabled-error {{named 'break' is only supported in C2y}} + x2: while (1) continue x2; // disabled-error {{named 'continue' is only supported in C2y}} } From bc1f6352b0d5851f117b6d64d1f874913eba9cfd Mon Sep 17 00:00:00 2001 From: Sirraide Date: Tue, 2 Sep 2025 16:56:36 +0200 Subject: [PATCH 35/40] =?UTF-8?q?=E2=80=98labeled=E2=80=99=20->=20?= =?UTF-8?q?=E2=80=98named=E2=80=99=20in=20a=20number=20of=20places?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- clang/include/clang/AST/Stmt.h | 4 ++-- clang/include/clang/Basic/LangOptions.def | 2 +- clang/lib/AST/ExprConstant.cpp | 2 +- clang/lib/Sema/SemaStmt.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 3c39a000b780b..ad666806424a7 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -3055,7 +3055,7 @@ class IndirectGotoStmt : public Stmt { /// Base class for BreakStmt and ContinueStmt. class LoopControlStmt : public Stmt { - /// If this is a labeled break/continue, the label whose statement we're + /// If this is a named break/continue, the label whose statement we're /// targeting, as well as the source location of the label after the /// keyword; for example: /// @@ -3096,7 +3096,7 @@ class LoopControlStmt : public Stmt { const LabelDecl *getLabelDecl() const { return TargetLabel; } void setLabelDecl(LabelDecl *S) { TargetLabel = S; } - /// If this is a labeled break/continue, get the loop or switch statement + /// If this is a named break/continue, get the loop or switch statement /// that this targets. const Stmt *getNamedLoopOrSwitch() const; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 874ee4cc18af1..579aa5d12ab8e 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -191,7 +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") +LANGOPT(NamedLoops , 1, 0, Benign, "Permit named break/continue") ENUM_LANGOPT(ComplexRange, ComplexRangeKind, 3, CX_None, NotCompatible, "Enable use of range reduction for complex arithmetics.") diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 69dc81e4ae347..bd39b2f45b662 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -5390,7 +5390,7 @@ 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 +/// Helper to implement named 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, diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index 4a7e7bd730a99..67208fc85c239 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -3287,7 +3287,7 @@ static Scope *FindLabeledBreakContinueScope(Sema &S, Scope *CurScope, LabelDecl *Target, SourceLocation LabelLoc, bool IsContinue) { - assert(Target && "not a labeled break/continue?"); + assert(Target && "not a named break/continue?"); Scope *Found = nullptr; for (Scope *Scope = CurScope; Scope; Scope = Scope->getParent()) { if (Scope->isFunctionScope()) From 35bc625e449de366b402f0670f08e82de4ef69b6 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Tue, 2 Sep 2025 16:59:07 +0200 Subject: [PATCH 36/40] Fix typo in comment --- clang/include/clang/AST/Stmt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index ad666806424a7..78157d02a8b5b 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -2174,7 +2174,7 @@ class LabelStmt : public ValueStmt { SourceLocation getEndLoc() const LLVM_READONLY { return SubStmt->getEndLoc();} /// Look through nested labels and return the first non-label statement; e.g. - /// if this is 'a:' in 'a: b: c: for(;;)', this returns the for loop/ + /// if this is 'a:' in 'a: b: c: for(;;)', this returns the for loop. const Stmt *getInnermostLabeledStmt() const; Stmt *getInnermostLabeledStmt() { return const_cast( From 797bf22d0ab4796f33e244016bac5f1e9270dd08 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Tue, 2 Sep 2025 17:22:08 +0200 Subject: [PATCH 37/40] add missing const --- clang/lib/AST/ExprConstant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index bd39b2f45b662..eaafca0b37025 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -5410,7 +5410,7 @@ static bool ShouldPropagateBreakContinue(EvalInfo &Info, // Are we breaking out of or continuing this statement? bool CanBreakOrContinue = !IsSwitch || ESR == ESR_Break; - Stmt *StackTop = Info.BreakContinueStack.back(); + const Stmt *StackTop = Info.BreakContinueStack.back(); if (CanBreakOrContinue && (StackTop == nullptr || StackTop == LoopOrSwitch)) { Info.BreakContinueStack.pop_back(); if (ESR == ESR_Break) From ece369b4e2874d7ce85233c1a7f8c202d8eef444 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Tue, 2 Sep 2025 17:24:44 +0200 Subject: [PATCH 38/40] rename Label -> LabelLoc --- clang/include/clang/AST/Stmt.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 78157d02a8b5b..76942f1a84f9a 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -3061,15 +3061,15 @@ class LoopControlStmt : public Stmt { /// /// a: // <-- TargetLabel /// for (;;) - /// break a; // <-- Label + /// break a; // <-- LabelLoc /// LabelDecl *TargetLabel = nullptr; - SourceLocation Label; + SourceLocation LabelLoc; protected: LoopControlStmt(StmtClass Class, SourceLocation Loc, SourceLocation LabelLoc, LabelDecl *Target) - : Stmt(Class), TargetLabel(Target), Label(LabelLoc) { + : Stmt(Class), TargetLabel(Target), LabelLoc(LabelLoc) { setKwLoc(Loc); } @@ -3089,8 +3089,8 @@ class LoopControlStmt : public Stmt { bool hasLabelTarget() const { return TargetLabel != nullptr; } - SourceLocation getLabelLoc() const { return Label; } - void setLabelLoc(SourceLocation L) { Label = L; } + SourceLocation getLabelLoc() const { return LabelLoc; } + void setLabelLoc(SourceLocation L) { LabelLoc = L; } LabelDecl *getLabelDecl() { return TargetLabel; } const LabelDecl *getLabelDecl() const { return TargetLabel; } From c6d433dc2b8253cef083133cb14bb1a89fa425ba Mon Sep 17 00:00:00 2001 From: Sirraide Date: Tue, 2 Sep 2025 17:38:54 +0200 Subject: [PATCH 39/40] fix return type --- clang/lib/AST/Stmt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp index 7add5fe766ec8..99f91ce3e10d4 100644 --- a/clang/lib/AST/Stmt.cpp +++ b/clang/lib/AST/Stmt.cpp @@ -1490,6 +1490,6 @@ const Stmt *LabelStmt::getInnermostLabeledStmt() const { return S; } -Stmt *LoopControlStmt::getNamedLoopOrSwitch() const { +const Stmt *LoopControlStmt::getNamedLoopOrSwitch() const { return getLabelDecl()->getStmt()->getInnermostLabeledStmt(); } From 82f783d2900ffddecf5d69f4fdd62d26d4b92414 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Tue, 2 Sep 2025 17:53:25 +0200 Subject: [PATCH 40/40] getNamedLoopOrSwitch() should return nullptr if there is no label --- clang/lib/AST/Stmt.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp index 99f91ce3e10d4..9ae8aea3ab37a 100644 --- a/clang/lib/AST/Stmt.cpp +++ b/clang/lib/AST/Stmt.cpp @@ -1491,5 +1491,7 @@ const Stmt *LabelStmt::getInnermostLabeledStmt() const { } const Stmt *LoopControlStmt::getNamedLoopOrSwitch() const { + if (!hasLabelTarget()) + return nullptr; return getLabelDecl()->getStmt()->getInnermostLabeledStmt(); }