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) { 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/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 ^^^^^^^^^^^^^^^^^^^ 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/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index a5b0d5053003f..45930eef4f91c 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,25 +3045,42 @@ class IndirectGotoStmt : public Stmt { } }; -/// ContinueStmt - This represents a continue. -class ContinueStmt : 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. + LabelDecl *TargetLabel = nullptr; + + /// Location of the label, if any. + SourceLocation Label; + +protected: + LoopControlStmt(StmtClass Class, SourceLocation Loc) : Stmt(Class) { + setKwLoc(Loc); + } + + LoopControlStmt(StmtClass Class, EmptyShell ES) : Stmt(Class, ES) {} + public: - ContinueStmt(SourceLocation CL) : Stmt(ContinueStmtClass) { - setContinueLoc(CL); + 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(); } - /// Build an empty continue statement. - explicit ContinueStmt(EmptyShell Empty) : Stmt(ContinueStmtClass, Empty) {} + bool isLabeled() const { return TargetLabel; } - SourceLocation getContinueLoc() const { return ContinueStmtBits.ContinueLoc; } - void setContinueLoc(SourceLocation L) { ContinueStmtBits.ContinueLoc = L; } + SourceLocation getLabelLoc() const { return Label; } + void setLabelLoc(SourceLocation L) { Label = L; } - SourceLocation getBeginLoc() const { return getContinueLoc(); } - SourceLocation getEndLoc() const { return getContinueLoc(); } + LabelDecl *getLabelDecl() const { return TargetLabel; } + void setLabelDecl(LabelDecl *S) { TargetLabel = S; } - static bool classof(const Stmt *T) { - return T->getStmtClass() == ContinueStmtClass; - } + /// If this is a labeled break/continue, get the loop or switch statement + /// that this targets. + Stmt *getLabelTarget() const; // Iterators child_range children() { @@ -3084,35 +3090,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, LabelDecl *Target) + : LoopControlStmt(ContinueStmtClass, CL) { + setLabelLoc(LabelLoc); + setLabelDecl(Target); } - /// 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, LabelDecl *Target) + : LoopControlStmt(BreakStmtClass, CL) { + setLabelLoc(LabelLoc); + setLabelDecl(Target); } - 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/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/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 0042afccba2c8..6f2498d3bc7c3 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -215,6 +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; // 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 cf23594201143..94647a033d497 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10796,6 +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 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/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index e9437e6d46366..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 ';'. @@ -7484,6 +7486,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/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h index 94b247a689c2d..2a46edc478591 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,8 @@ class FunctionScopeInfo { HasBranchIntoScope = true; } + void setHasLabeledBreakOrContinue() { HasLabeledBreakOrContinue = true; } + void setHasBranchProtectedScope() { HasBranchProtectedScope = true; } @@ -485,8 +491,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 8e2927bdc8d6f..79583b68b4112 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 ToDecl = S->isLabeled() + ? NodeImporter.importChecked(Err, S->getLabelDecl()) + : nullptr; + if (Err) + return std::move(Err); + return new (Importer.getToContext()) StmtClass(ToLoc, ToLabelLoc, ToDecl); +} + 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/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 3679327da7b0c..264153f7508d7 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,44 @@ 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 +5438,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 +5504,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 +5607,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 +5630,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 +5794,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 +5813,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 +5857,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 +5950,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 +5977,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/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/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/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/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/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..70cb869b0f540 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,20 @@ 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 +1756,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 +1769,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 +2398,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..86206b6042172 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/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index bf1978c22ee9f..45b92b03ff3e1 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -2288,14 +2288,30 @@ StmtResult Parser::ParseGotoStatement() { return Res; } +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; + } + + if (IsContinue) + return Actions.ActOnContinueStmt(KwLoc, getCurScope(), Target, LabelLoc); + return Actions.ActOnBreakStmt(KwLoc, getCurScope(), Target, LabelLoc); +} + StmtResult Parser::ParseContinueStatement() { - SourceLocation ContinueLoc = ConsumeToken(); // eat the 'continue'. - return Actions.ActOnContinueStmt(ContinueLoc, getCurScope()); + return ParseBreakOrContinueStatement(/*IsContinue=*/true); } StmtResult Parser::ParseBreakStatement() { - SourceLocation BreakLoc = ConsumeToken(); // eat the 'break'. - return Actions.ActOnBreakStmt(BreakLoc, getCurScope()); + return ParseBreakOrContinueStatement(/*IsContinue=*/false); } StmtResult Parser::ParseReturnStatement() { diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp index 36704c3826dfd..4fcdc4c084fb4 100644 --- a/clang/lib/Sema/JumpDiagnostics.cpp +++ b/clang/lib/Sema/JumpDiagnostics.cpp @@ -84,6 +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, + unsigned InDiag = 0); void VerifyJumps(); void VerifyIndirectJumps(); @@ -296,6 +299,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, 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 @@ -309,8 +334,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: @@ -319,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; } @@ -339,18 +361,12 @@ 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,36 @@ 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() || @@ -639,11 +685,7 @@ 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 @@ -721,6 +763,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()) { @@ -961,6 +1031,28 @@ 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))) @@ -969,14 +1061,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) || 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) { + 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; @@ -987,11 +1083,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); @@ -999,6 +1106,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 a5f92020f49f8..253da543b0689 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) { @@ -3282,8 +3282,14 @@ 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) { + if (Target) { + getCurFunction()->setHasLabeledBreakOrContinue(); + 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. @@ -3309,13 +3315,29 @@ 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) { + if (Target) { + getCurFunction()->setHasLabeledBreakOrContinue(); + 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. 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/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/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 3f37dfbc3dea9..76fdd4024b0b7 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -320,16 +320,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->setLabelDecl(readDeclAs()); + S->setLabelLoc(readSourceLocation()); + } } -void ASTStmtReader::VisitBreakStmt(BreakStmt *S) { - VisitStmt(S); - S->setBreakLoc(readSourceLocation()); +void ASTStmtReader::VisitContinueStmt(ContinueStmt *S) { + VisitLoopControlStmt(S); } +void ASTStmtReader::VisitBreakStmt(BreakStmt *S) { VisitLoopControlStmt(S); } + void ASTStmtReader::VisitReturnStmt(ReturnStmt *S) { VisitStmt(S); diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index be9bad9e96cc1..9baaa21121ce7 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -310,15 +310,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.AddDeclRef(S->getLabelDecl()); + 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..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->getContinueLoc(), - 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->getBreakLoc(), - syntax::NodeRole::IntroducerKeyword); + Builder.markChildToken(S->getKwLoc(), syntax::NodeRole::IntroducerKeyword); Builder.foldNode(Builder.getStmtRange(S), new (allocator()) syntax::BreakStatement, S); return true; 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: } 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/CodeGenCXX/labeled-break-continue.cpp b/clang/test/CodeGenCXX/labeled-break-continue.cpp new file mode 100644 index 0000000000000..bf1b6d520efc4 --- /dev/null +++ b/clang/test/CodeGenCXX/labeled-break-continue.cpp @@ -0,0 +1,221 @@ +// 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; + } +} + +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/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/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/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/__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/Sema/labeled-break-continue.c b/clang/test/Sema/labeled-break-continue.c new file mode 100644 index 0000000000000..16993af261f51 --- /dev/null +++ b/clang/test/Sema/labeled-break-continue.c @@ -0,0 +1,146 @@ +// 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) { + 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}} + } + + 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() { + 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'}} + } +} 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..fe58004ca6ff0 --- /dev/null +++ b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp @@ -0,0 +1,169 @@ +// 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()); + +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); 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'}} + }; + } +} 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'}} + }; + } +} 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; + } +} 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