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/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 0e9fcaa5fac6a..831b2a47bca4e 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -103,6 +103,7 @@ C Language Changes C2y Feature Support ^^^^^^^^^^^^^^^^^^^ +- Clang now supports `N3355 `_ Named Loops. 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..a86d5548b39f1 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -277,24 +277,14 @@ class alignas(void *) Stmt { SourceLocation GotoLoc; }; - class ContinueStmtBitfields { - friend class ContinueStmt; + class LoopControlStmtBitfields { + friend class LoopControlStmt; LLVM_PREFERRED_TYPE(StmtBitfields) unsigned : NumStmtBits; - /// The location of the "continue". - SourceLocation ContinueLoc; - }; - - class BreakStmtBitfields { - friend class BreakStmt; - - LLVM_PREFERRED_TYPE(StmtBitfields) - unsigned : NumStmtBits; - - /// The location of the "break". - SourceLocation BreakLoc; + /// The location of the "continue"/"break". + SourceLocation KwLoc; }; class ReturnStmtBitfields { @@ -1325,8 +1315,7 @@ class alignas(void *) Stmt { DoStmtBitfields DoStmtBits; ForStmtBitfields ForStmtBits; GotoStmtBitfields GotoStmtBits; - ContinueStmtBitfields ContinueStmtBits; - BreakStmtBitfields BreakStmtBits; + LoopControlStmtBitfields LoopControlStmtBits; ReturnStmtBitfields ReturnStmtBits; SwitchCaseBitfields SwitchCaseBits; @@ -3056,26 +3045,51 @@ class IndirectGotoStmt : public Stmt { } }; -/// ContinueStmt - This represents a continue. -class ContinueStmt : public Stmt { -public: - ContinueStmt(SourceLocation CL) : Stmt(ContinueStmtClass) { - setContinueLoc(CL); +/// Base class for BreakStmt and ContinueStmt. +class LoopControlStmt : public Stmt { + /// If this is a labeled break/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); } - /// Build an empty continue statement. - explicit ContinueStmt(EmptyShell Empty) : Stmt(ContinueStmtClass, Empty) {} + LoopControlStmt(StmtClass Class, SourceLocation Loc, SourceLocation LabelLoc, + LabelDecl *Target) + : LoopControlStmt(Class, Loc) { + setLabelLoc(LabelLoc); + setLabelDecl(Target); + } - SourceLocation getContinueLoc() const { return ContinueStmtBits.ContinueLoc; } - void setContinueLoc(SourceLocation L) { ContinueStmtBits.ContinueLoc = L; } + LoopControlStmt(StmtClass Class, EmptyShell ES) : Stmt(Class, ES) {} - SourceLocation getBeginLoc() const { return getContinueLoc(); } - SourceLocation getEndLoc() const { return getContinueLoc(); } +public: + SourceLocation getKwLoc() const { return LoopControlStmtBits.KwLoc; } + void setKwLoc(SourceLocation L) { LoopControlStmtBits.KwLoc = L; } - static bool classof(const Stmt *T) { - return T->getStmtClass() == ContinueStmtClass; + SourceLocation getBeginLoc() const { return getKwLoc(); } + SourceLocation getEndLoc() const { + return isLabeled() ? getLabelLoc() : getKwLoc(); } + bool isLabeled() const { return TargetLabel != nullptr; } + + SourceLocation getLabelLoc() const { return Label; } + void setLabelLoc(SourceLocation L) { Label = L; } + + LabelDecl *getLabelDecl() { return TargetLabel; } + const LabelDecl *getLabelDecl() const { return TargetLabel; } + void setLabelDecl(LabelDecl *S) { TargetLabel = S; } + + /// If this is a labeled break/continue, get the loop or switch statement + /// that this targets. + Stmt *getLabelTarget() const; + // Iterators child_range children() { return child_range(child_iterator(), child_iterator()); @@ -3084,35 +3098,42 @@ class ContinueStmt : public Stmt { const_child_range children() const { return const_child_range(const_child_iterator(), const_child_iterator()); } -}; -/// BreakStmt - This represents a break. -class BreakStmt : public Stmt { -public: - BreakStmt(SourceLocation BL) : Stmt(BreakStmtClass) { - setBreakLoc(BL); + static bool classof(const Stmt *T) { + StmtClass Class = T->getStmtClass(); + return Class == ContinueStmtClass || Class == BreakStmtClass; } +}; - /// 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; } +/// ContinueStmt - This represents a continue. +class ContinueStmt : public LoopControlStmt { +public: + ContinueStmt(SourceLocation CL) : LoopControlStmt(ContinueStmtClass, CL) {} + ContinueStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target) + : LoopControlStmt(ContinueStmtClass, CL, LabelLoc, Target) {} - 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, LabelLoc, 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..3506f24caf2c3 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -215,6 +215,8 @@ def warn_c23_compat_case_range : Warning< DefaultIgnore, InGroup; def ext_c2y_case_range : Extension< "case ranges are a C2y extension">, InGroup; +def err_c2y_labeled_break_continue : Error< + "labeled %select{'break'|'continue'}0 is only supported in C2y">; // 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..46e20ad2ece3a 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{break|continue}0' label does not name an enclosing " + "%select{loop or 'switch'|loop}0">; +def err_continue_switch : Error< + "label of 'continue' refers to a switch statement">; def warn_loop_ctrl_binds_to_inner : Warning< "'%0' is bound to current loop, GCC binds it to the enclosing loop">, InGroup; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 08d98a77e0252..874ee4cc18af1 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -191,6 +191,7 @@ LANGOPT(NoHonorInfs , 1, 0, Benign, "Permit Floating Point optimization wi LANGOPT(NoSignedZero , 1, 0, Benign, "Permit Floating Point optimization without regard to signed zeros") LANGOPT(AllowRecip , 1, 0, Benign, "Permit Floating Point reciprocal") LANGOPT(ApproxFunc , 1, 0, Benign, "Permit Floating Point approximation") +LANGOPT(NamedLoops , 1, 0, Benign, "Permit labeled break/continue") ENUM_LANGOPT(ComplexRange, ComplexRangeKind, 3, CX_None, NotCompatible, "Enable use of range reduction for complex arithmetics.") diff --git a/clang/include/clang/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/Driver/Options.td b/clang/include/clang/Driver/Options.td index 6aab43c9ed57f..2867dca6f7ddb 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1642,6 +1642,17 @@ defm auto_import : BoolFOption<"auto-import", def offload_EQ : CommaJoined<["--"], "offload=">, Flags<[NoXarchOption]>, Alias, HelpText<"Specify comma-separated list of offloading target triples (CUDA and HIP only)">; +// This flag is only here so we can test the named loops implementation +// in C++ mode and C language modes before C2y to make sure it actually +// works; it should be removed once the syntax of the feature is stable +// enough to backport it to earlier language modes (and to C++ if it ever +// gets standardised as a C++ feature). +defm named_loops + : BoolFOption< + "named-loops", LangOpts<"NamedLoops">, DefaultFalse, + PosFlag, + NegFlag>; + // C++ Coroutines defm coroutines : BoolFOption<"coroutines", LangOpts<"Coroutines">, Default, diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index e9437e6d46366..a9a87fb586fc2 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -7213,7 +7213,8 @@ class Parser : public CodeCompletionHandler { /// 'while', or 'for'). StmtResult ParseStatement(SourceLocation *TrailingElseLoc = nullptr, - ParsedStmtContext StmtCtx = ParsedStmtContext::SubStmt); + ParsedStmtContext StmtCtx = ParsedStmtContext::SubStmt, + LabelDecl *PrecedingLabel = nullptr); /// ParseStatementOrDeclaration - Read 'statement' or 'declaration'. /// \verbatim @@ -7268,12 +7269,13 @@ class Parser : public CodeCompletionHandler { /// StmtResult ParseStatementOrDeclaration(StmtVector &Stmts, ParsedStmtContext StmtCtx, - SourceLocation *TrailingElseLoc = nullptr); + SourceLocation *TrailingElseLoc = nullptr, + LabelDecl *PrecedingLabel = nullptr); StmtResult ParseStatementOrDeclarationAfterAttributes( StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, ParsedAttributes &DeclAttrs, - ParsedAttributes &DeclSpecAttrs); + ParsedAttributes &DeclSpecAttrs, LabelDecl *PrecedingLabel); /// Parse an expression statement. StmtResult ParseExprStatement(ParsedStmtContext StmtCtx); @@ -7398,7 +7400,8 @@ class Parser : public CodeCompletionHandler { /// 'switch' '(' expression ')' statement /// [C++] 'switch' '(' condition ')' statement /// \endverbatim - StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc); + StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel); /// ParseWhileStatement /// \verbatim @@ -7406,7 +7409,8 @@ class Parser : public CodeCompletionHandler { /// 'while' '(' expression ')' statement /// [C++] 'while' '(' condition ')' statement /// \endverbatim - StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc); + StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel); /// ParseDoStatement /// \verbatim @@ -7414,7 +7418,7 @@ class Parser : public CodeCompletionHandler { /// 'do' statement 'while' '(' expression ')' ';' /// \endverbatim /// Note: this lets the caller parse the end ';'. - StmtResult ParseDoStatement(); + StmtResult ParseDoStatement(LabelDecl *PrecedingLabel); /// ParseForStatement /// \verbatim @@ -7441,7 +7445,8 @@ class Parser : public CodeCompletionHandler { /// [C++0x] expression /// [C++0x] braced-init-list [TODO] /// \endverbatim - StmtResult ParseForStatement(SourceLocation *TrailingElseLoc); + StmtResult ParseForStatement(SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel); /// ParseGotoStatement /// \verbatim @@ -7458,6 +7463,7 @@ class Parser : public CodeCompletionHandler { /// \verbatim /// jump-statement: /// 'continue' ';' + /// [C2y] 'continue' identifier ';' /// \endverbatim /// /// Note: this lets the caller parse the end ';'. @@ -7468,6 +7474,7 @@ class Parser : public CodeCompletionHandler { /// \verbatim /// jump-statement: /// 'break' ';' + /// [C2y] 'break' identifier ';' /// \endverbatim /// /// Note: this lets the caller parse the end ';'. @@ -7484,9 +7491,12 @@ class Parser : public CodeCompletionHandler { /// \endverbatim StmtResult ParseReturnStatement(); + StmtResult ParseBreakOrContinueStatement(bool IsContinue); + StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, - ParsedAttributes &Attrs); + ParsedAttributes &Attrs, + LabelDecl *PrecedingLabel); void ParseMicrosoftIfExistsStatement(StmtVector &Stmts); diff --git a/clang/include/clang/Sema/Scope.h b/clang/include/clang/Sema/Scope.h index 757f3dcc3fe8d..0d1c0ff6a1e91 100644 --- a/clang/include/clang/Sema/Scope.h +++ b/clang/include/clang/Sema/Scope.h @@ -255,6 +255,10 @@ class Scope { /// available for this variable in the current scope. llvm::SmallPtrSet ReturnSlots; + /// If this scope belongs to a loop or switch statement, the label that + /// directly precedes it, if any. + LabelDecl *PrecedingLabel; + void setFlags(Scope *Parent, unsigned F); public: @@ -268,6 +272,14 @@ class Scope { void setFlags(unsigned F) { setFlags(getParent(), F); } + /// Get the label that precedes this scope. + LabelDecl *getPrecedingLabel() const { return PrecedingLabel; } + void setPrecedingLabel(LabelDecl *LD) { + assert((Flags & BreakScope || Flags & ContinueScope) && + "not a loop or switch"); + PrecedingLabel = LD; + } + /// isBlockScope - Return true if this scope correspond to a closure. bool isBlockScope() const { return Flags & BlockScope; } @@ -583,6 +595,12 @@ class Scope { return getFlags() & ScopeFlags::ContinueScope; } + /// Determine whether this is a scope which can have 'break' or 'continue' + /// statements embedded into it. + bool isBreakOrContinueScope() const { + return getFlags() & (ContinueScope | BreakScope); + } + /// Determine whether this scope is a C++ 'try' block. bool isTryScope() const { return getFlags() & Scope::TryScope; } diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 5211373367677..29d698b2fb748 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -9479,6 +9479,10 @@ class Sema final : public SemaBase { LabelDecl *LookupOrCreateLabel(IdentifierInfo *II, SourceLocation IdentLoc, SourceLocation GnuLabelLoc = SourceLocation()); + /// Perform a name lookup for a label with the specified name; this does not + /// create a new label if the lookup fails. + LabelDecl *LookupExistingLabel(IdentifierInfo *II, SourceLocation IdentLoc); + /// Look up the constructors for the given class. DeclContextLookupResult LookupConstructors(CXXRecordDecl *Class); @@ -11033,8 +11037,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/Basic/LangOptions.cpp b/clang/lib/Basic/LangOptions.cpp index 9c14a25699f89..f034514466d3f 100644 --- a/clang/lib/Basic/LangOptions.cpp +++ b/clang/lib/Basic/LangOptions.cpp @@ -128,6 +128,7 @@ void LangOptions::setLangDefaults(LangOptions &Opts, Language Lang, Opts.WChar = Std.isCPlusPlus(); Opts.Digraphs = Std.hasDigraphs(); Opts.RawStringLiterals = Std.hasRawStringLiterals(); + Opts.NamedLoops = Std.isC2y(); Opts.HLSL = Lang == Language::HLSL; if (Opts.HLSL && Opts.IncludeDefaultHeader) diff --git a/clang/lib/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/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 9f77e621a5e68..04583af6a4b0a 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -623,6 +623,9 @@ static bool FixupInvocation(CompilerInvocation &Invocation, LangOpts.RawStringLiterals = true; } + LangOpts.NamedLoops = + Args.hasFlag(OPT_fnamed_loops, OPT_fno_named_loops, LangOpts.C2y); + // Prevent the user from specifying both -fsycl-is-device and -fsycl-is-host. if (LangOpts.SYCLIsDevice && LangOpts.SYCLIsHost) Diags.Report(diag::err_drv_argument_not_allowed_with) << "-fsycl-is-device" diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index bf1978c22ee9f..62361c066a3f3 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -37,23 +37,25 @@ using namespace clang; //===----------------------------------------------------------------------===// StmtResult Parser::ParseStatement(SourceLocation *TrailingElseLoc, - ParsedStmtContext StmtCtx) { + ParsedStmtContext StmtCtx, + LabelDecl *PrecedingLabel) { StmtResult Res; // We may get back a null statement if we found a #pragma. Keep going until // we get an actual statement. StmtVector Stmts; do { - Res = ParseStatementOrDeclaration(Stmts, StmtCtx, TrailingElseLoc); + Res = ParseStatementOrDeclaration(Stmts, StmtCtx, TrailingElseLoc, + PrecedingLabel); } while (!Res.isInvalid() && !Res.get()); return Res; } -StmtResult -Parser::ParseStatementOrDeclaration(StmtVector &Stmts, - ParsedStmtContext StmtCtx, - SourceLocation *TrailingElseLoc) { +StmtResult Parser::ParseStatementOrDeclaration(StmtVector &Stmts, + ParsedStmtContext StmtCtx, + SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel) { ParenBraceBracketBalancer BalancerRAIIObj(*this); @@ -73,7 +75,8 @@ Parser::ParseStatementOrDeclaration(StmtVector &Stmts, MaybeParseMicrosoftAttributes(GNUOrMSAttrs); StmtResult Res = ParseStatementOrDeclarationAfterAttributes( - Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUOrMSAttrs); + Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, GNUOrMSAttrs, + PrecedingLabel); MaybeDestroyTemplateIds(); takeAndConcatenateAttrs(CXX11Attrs, std::move(GNUOrMSAttrs)); @@ -130,7 +133,7 @@ class StatementFilterCCC final : public CorrectionCandidateCallback { StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, ParsedAttributes &CXX11Attrs, - ParsedAttributes &GNUAttrs) { + ParsedAttributes &GNUAttrs, LabelDecl *PrecedingLabel) { const char *SemiError = nullptr; StmtResult Res; SourceLocation GNUAttributeLoc; @@ -278,16 +281,16 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( case tok::kw_if: // C99 6.8.4.1: if-statement return ParseIfStatement(TrailingElseLoc); case tok::kw_switch: // C99 6.8.4.2: switch-statement - return ParseSwitchStatement(TrailingElseLoc); + return ParseSwitchStatement(TrailingElseLoc, PrecedingLabel); case tok::kw_while: // C99 6.8.5.1: while-statement - return ParseWhileStatement(TrailingElseLoc); + return ParseWhileStatement(TrailingElseLoc, PrecedingLabel); case tok::kw_do: // C99 6.8.5.2: do-statement - Res = ParseDoStatement(); + Res = ParseDoStatement(PrecedingLabel); SemiError = "do/while"; break; case tok::kw_for: // C99 6.8.5.3: for-statement - return ParseForStatement(TrailingElseLoc); + return ParseForStatement(TrailingElseLoc, PrecedingLabel); case tok::kw_goto: // C99 6.8.6.1: goto-statement Res = ParseGotoStatement(); @@ -483,7 +486,8 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( case tok::annot_pragma_loop_hint: ProhibitAttributes(CXX11Attrs); ProhibitAttributes(GNUAttrs); - return ParsePragmaLoopHint(Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs); + return ParsePragmaLoopHint(Stmts, StmtCtx, TrailingElseLoc, CXX11Attrs, + PrecedingLabel); case tok::annot_pragma_dump: ProhibitAttributes(CXX11Attrs); @@ -697,6 +701,9 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs, // identifier ':' statement SourceLocation ColonLoc = ConsumeToken(); + LabelDecl *LD = Actions.LookupOrCreateLabel(IdentTok.getIdentifierInfo(), + IdentTok.getLocation()); + // Read label attributes, if present. StmtResult SubStmt; if (Tok.is(tok::kw___attribute)) { @@ -716,7 +723,8 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs, StmtVector Stmts; ParsedAttributes EmptyCXX11Attrs(AttrFactory); SubStmt = ParseStatementOrDeclarationAfterAttributes( - Stmts, StmtCtx, nullptr, EmptyCXX11Attrs, TempAttrs); + Stmts, StmtCtx, /*TrailingElseLoc=*/nullptr, EmptyCXX11Attrs, + TempAttrs, LD); if (!TempAttrs.empty() && !SubStmt.isInvalid()) SubStmt = Actions.ActOnAttributedStmt(TempAttrs, SubStmt.get()); } @@ -730,7 +738,7 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs, // If we've not parsed a statement yet, parse one now. if (SubStmt.isUnset()) - SubStmt = ParseStatement(nullptr, StmtCtx); + SubStmt = ParseStatement(nullptr, StmtCtx, LD); // Broken substmt shouldn't prevent the label from being added to the AST. if (SubStmt.isInvalid()) @@ -738,8 +746,6 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs, DiagnoseLabelFollowedByDecl(*this, SubStmt.get()); - LabelDecl *LD = Actions.LookupOrCreateLabel(IdentTok.getIdentifierInfo(), - IdentTok.getLocation()); Actions.ProcessDeclAttributeList(Actions.CurScope, LD, Attrs); Attrs.clear(); @@ -1620,7 +1626,8 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) { ThenStmt.get(), ElseLoc, ElseStmt.get()); } -StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc) { +StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel) { assert(Tok.is(tok::kw_switch) && "Not a switch stmt!"); SourceLocation SwitchLoc = ConsumeToken(); // eat the 'switch'. @@ -1686,6 +1693,7 @@ StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc) { // condition and a new scope for substatement in C++. // getCurScope()->AddFlags(Scope::BreakScope); + getCurScope()->setPrecedingLabel(PrecedingLabel); ParseScope InnerScope(this, Scope::DeclScope, C99orCXX, Tok.is(tok::l_brace)); // We have incremented the mangling number for the SwitchScope and the @@ -1703,7 +1711,8 @@ StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc) { return Actions.ActOnFinishSwitchStmt(SwitchLoc, Switch.get(), Body.get()); } -StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc) { +StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel) { assert(Tok.is(tok::kw_while) && "Not a while stmt!"); SourceLocation WhileLoc = Tok.getLocation(); ConsumeToken(); // eat the 'while'. @@ -1748,6 +1757,7 @@ StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc) { // combinations, so diagnose that here in OpenACC mode. SemaOpenACC::LoopInConstructRAII LCR{getActions().OpenACC()}; getActions().OpenACC().ActOnWhileStmt(WhileLoc); + getCurScope()->setPrecedingLabel(PrecedingLabel); // C99 6.8.5p5 - In C99, the body of the while statement is a scope, even if // there is no compound stmt. C90 does not have this clause. We only do this @@ -1779,7 +1789,7 @@ StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc) { return Actions.ActOnWhileStmt(WhileLoc, LParen, Cond, RParen, Body.get()); } -StmtResult Parser::ParseDoStatement() { +StmtResult Parser::ParseDoStatement(LabelDecl *PrecedingLabel) { assert(Tok.is(tok::kw_do) && "Not a do stmt!"); SourceLocation DoLoc = ConsumeToken(); // eat the 'do'. @@ -1797,6 +1807,7 @@ StmtResult Parser::ParseDoStatement() { // combinations, so diagnose that here in OpenACC mode. SemaOpenACC::LoopInConstructRAII LCR{getActions().OpenACC()}; getActions().OpenACC().ActOnDoStmt(DoLoc); + getCurScope()->setPrecedingLabel(PrecedingLabel); // C99 6.8.5p5 - In C99, the body of the do statement is a scope, even if // there is no compound stmt. C90 does not have this clause. We only do this @@ -1815,6 +1826,9 @@ StmtResult Parser::ParseDoStatement() { // Pop the body scope if needed. InnerScope.Exit(); + // Reset this to disallow break/continue out of the condition. + getCurScope()->setPrecedingLabel(nullptr); + if (Tok.isNot(tok::kw_while)) { if (!Body.isInvalid()) { Diag(Tok, diag::err_expected_while); @@ -1876,7 +1890,8 @@ bool Parser::isForRangeIdentifier() { return false; } -StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) { +StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc, + LabelDecl *PrecedingLabel) { assert(Tok.is(tok::kw_for) && "Not a for stmt!"); SourceLocation ForLoc = ConsumeToken(); // eat the 'for'. @@ -2208,6 +2223,10 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) { getActions().OpenACC().ActOnForStmtBegin( ForLoc, FirstPart.get(), SecondPart.get().second, ThirdPart.get()); + // Set this only right before parsing the body to disallow break/continue in + // the other parts. + getCurScope()->setPrecedingLabel(PrecedingLabel); + // C99 6.8.5p5 - In C99, the body of the for statement is a scope, even if // there is no compound stmt. C90 does not have this clause. We only do this // if the body isn't a compound statement to avoid push/pop in common cases. @@ -2288,14 +2307,35 @@ 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.LookupExistingLabel(Tok.getIdentifierInfo(), Tok.getLocation()); + LabelLoc = ConsumeToken(); + if (!getLangOpts().NamedLoops) + // TODO: Make this a compatibility/extension warning instead once the + // syntax of this feature is finalised. + Diag(LabelLoc, diag::err_c2y_labeled_break_continue) << IsContinue; + if (!Target) { + Diag(LabelLoc, diag::err_break_continue_label_not_found) << IsContinue; + return StmtError(); + } + } + + 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() { @@ -2339,7 +2379,8 @@ StmtResult Parser::ParseReturnStatement() { StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx, SourceLocation *TrailingElseLoc, - ParsedAttributes &Attrs) { + ParsedAttributes &Attrs, + LabelDecl *PrecedingLabel) { // Create temporary attribute list. ParsedAttributes TempAttrs(AttrFactory); @@ -2363,7 +2404,8 @@ StmtResult Parser::ParsePragmaLoopHint(StmtVector &Stmts, ParsedAttributes EmptyDeclSpecAttrs(AttrFactory); StmtResult S = ParseStatementOrDeclarationAfterAttributes( - Stmts, StmtCtx, TrailingElseLoc, Attrs, EmptyDeclSpecAttrs); + Stmts, StmtCtx, TrailingElseLoc, Attrs, EmptyDeclSpecAttrs, + PrecedingLabel); Attrs.takeAllFrom(TempAttrs); diff --git a/clang/lib/Sema/Scope.cpp b/clang/lib/Sema/Scope.cpp index ab04fe554be82..e66cce255230b 100644 --- a/clang/lib/Sema/Scope.cpp +++ b/clang/lib/Sema/Scope.cpp @@ -99,6 +99,7 @@ void Scope::Init(Scope *parent, unsigned flags) { UsingDirectives.clear(); Entity = nullptr; ErrorTrap.reset(); + PrecedingLabel = nullptr; NRVO = std::nullopt; } diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp index dc73dedfb5598..d5dea3512c6d7 100644 --- a/clang/lib/Sema/SemaLookup.cpp +++ b/clang/lib/Sema/SemaLookup.cpp @@ -4455,26 +4455,28 @@ void Sema::LookupVisibleDecls(DeclContext *Ctx, LookupNameKind Kind, H.lookupVisibleDecls(*this, Ctx, Kind, IncludeGlobalScope); } +LabelDecl *Sema::LookupExistingLabel(IdentifierInfo *II, SourceLocation Loc) { + NamedDecl *Res = LookupSingleName(CurScope, II, Loc, LookupLabel, + RedeclarationKind::NotForRedeclaration); + // If we found a label, check to see if it is in the same context as us. + // When in a Block, we don't want to reuse a label in an enclosing function. + if (!Res || Res->getDeclContext() != CurContext) + return nullptr; + return cast(Res); +} + LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc, SourceLocation GnuLabelLoc) { - // Do a lookup to see if we have a label with this name already. - NamedDecl *Res = nullptr; - if (GnuLabelLoc.isValid()) { // Local label definitions always shadow existing labels. - Res = LabelDecl::Create(Context, CurContext, Loc, II, GnuLabelLoc); + auto *Res = LabelDecl::Create(Context, CurContext, Loc, II, GnuLabelLoc); Scope *S = CurScope; PushOnScopeChains(Res, S, true); return cast(Res); } // Not a GNU local label. - Res = LookupSingleName(CurScope, II, Loc, LookupLabel, - RedeclarationKind::NotForRedeclaration); - // If we found a label, check to see if it is in the same context as us. - // When in a Block, we don't want to reuse a label in an enclosing function. - if (Res && Res->getDeclContext() != CurContext) - Res = nullptr; + LabelDecl *Res = LookupExistingLabel(II, Loc); if (!Res) { // If not forward referenced or defined already, create the backing decl. Res = LabelDecl::Create(Context, CurContext, Loc, II); @@ -4482,7 +4484,7 @@ LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc, assert(S && "Not in a function?"); PushOnScopeChains(Res, S, true); } - return cast(Res); + return Res; } //===----------------------------------------------------------------------===// diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index a5f92020f49f8..4a7e7bd730a99 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,9 +3282,55 @@ static void CheckJumpOutOfSEHFinally(Sema &S, SourceLocation Loc, } } -StmtResult -Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope) { - Scope *S = CurScope->getContinueParent(); +static Scope *FindLabeledBreakContinueScope(Sema &S, Scope *CurScope, + SourceLocation KWLoc, + LabelDecl *Target, + SourceLocation LabelLoc, + bool IsContinue) { + assert(Target && "not a labeled break/continue?"); + Scope *Found = nullptr; + for (Scope *Scope = CurScope; Scope; Scope = Scope->getParent()) { + if (Scope->isFunctionScope()) + break; + + if (Scope->isOpenACCComputeConstructScope()) { + S.Diag(KWLoc, diag::err_acc_branch_in_out_compute_construct) + << /*branch*/ 0 << /*out of*/ 0; + return nullptr; + } + + if (Scope->isBreakOrContinueScope() && + Scope->getPrecedingLabel() == Target) { + Found = Scope; + break; + } + } + + if (Found) { + if (IsContinue && !Found->isContinueScope()) { + S.Diag(LabelLoc, diag::err_continue_switch); + return nullptr; + } + return Found; + } + + S.Diag(LabelLoc, diag::err_break_continue_label_not_found) << IsContinue; + return nullptr; +} + +StmtResult Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope, + LabelDecl *Target, SourceLocation LabelLoc) { + Scope *S; + if (Target) { + S = FindLabeledBreakContinueScope(*this, CurScope, ContinueLoc, Target, + LabelLoc, + /*IsContinue=*/true); + if (!S) + return StmtError(); + } else { + S = CurScope->getContinueParent(); + } + if (!S) { // C99 6.8.6.2p1: A break shall appear only in or as a loop body. return StmtError(Diag(ContinueLoc, diag::err_continue_not_in_loop)); @@ -3306,16 +3352,27 @@ Sema::ActOnContinueStmt(SourceLocation ContinueLoc, Scope *CurScope) { CheckJumpOutOfSEHFinally(*this, ContinueLoc, *S); - return new (Context) ContinueStmt(ContinueLoc); + return new (Context) ContinueStmt(ContinueLoc, LabelLoc, Target); } -StmtResult -Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope) { - Scope *S = CurScope->getBreakParent(); +StmtResult Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope, + LabelDecl *Target, SourceLocation LabelLoc) { + Scope *S; + if (Target) { + S = FindLabeledBreakContinueScope(*this, CurScope, BreakLoc, Target, + LabelLoc, + /*IsContinue=*/false); + if (!S) + return StmtError(); + } else { + 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)); } + if (S->isOpenMPLoopScope()) return StmtError(Diag(BreakLoc, diag::err_omp_loop_cannot_use_stmt) << "break"); @@ -3336,7 +3393,7 @@ Sema::ActOnBreakStmt(SourceLocation BreakLoc, Scope *CurScope) { CheckJumpOutOfSEHFinally(*this, BreakLoc, *S); - return new (Context) BreakStmt(BreakLoc); + return new (Context) BreakStmt(BreakLoc, LabelLoc, Target); } Sema::NamedReturnInfo Sema::getNamedReturnInfo(Expr *&E, diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 0030946301a93..1326c652b2828 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 new (SemaRef.Context) + ContinueStmt(S->getKwLoc(), S->getLabelLoc(), cast(LD)); } 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 new (SemaRef.Context) + BreakStmt(S->getKwLoc(), S->getLabelLoc(), cast(LD)); } 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..19f8ff300a187 --- /dev/null +++ b/clang/test/AST/ast-dump-labeled-break-continue-json.c @@ -0,0 +1,306 @@ +// RUN: %clang_cc1 -std=c2y -ast-dump=json -ast-dump-filter Test %s | FileCheck %s + +void TestLabeledBreakContinue() { + a: while (true) { + break a; + continue a; + c: for (;;) { + break a; + continue a; + break c; + } + } +} + +// NOTE: CHECK lines have been autogenerated by gen_ast_dump_json_test.py + + +// CHECK-NOT: {{^}}Dumping +// CHECK: "kind": "FunctionDecl", +// CHECK-NEXT: "loc": { +// 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": 243, +// 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": 243, +// 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": 241, +// 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": "WhileStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 123, +// CHECK-NEXT: "line": 4, +// CHECK-NEXT: "col": 6, +// CHECK-NEXT: "tokLen": 5 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 241, +// 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": 130, +// CHECK-NEXT: "line": 4, +// CHECK-NEXT: "col": 13, +// CHECK-NEXT: "tokLen": 4 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 130, +// CHECK-NEXT: "col": 13, +// CHECK-NEXT: "tokLen": 4 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "type": { +// CHECK-NEXT: "qualType": "bool" +// CHECK-NEXT: }, +// CHECK-NEXT: "valueCategory": "prvalue", +// CHECK-NEXT: "value": true +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "CompoundStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 136, +// CHECK-NEXT: "col": 19, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 241, +// CHECK-NEXT: "line": 12, +// CHECK-NEXT: "col": 3, +// CHECK-NEXT: "tokLen": 1 +// 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": 142, +// CHECK-NEXT: "line": 5, +// CHECK-NEXT: "col": 5, +// CHECK-NEXT: "tokLen": 5 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 148, +// 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": 155, +// CHECK-NEXT: "line": 6, +// CHECK-NEXT: "col": 5, +// CHECK-NEXT: "tokLen": 8 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 164, +// CHECK-NEXT: "col": 14, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "targetLabelDeclId": "0x{{.*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "LabelStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 171, +// CHECK-NEXT: "line": 7, +// CHECK-NEXT: "col": 5, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 237, +// CHECK-NEXT: "line": 11, +// CHECK-NEXT: "col": 5, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "name": "c", +// CHECK-NEXT: "declId": "0x{{.*}}", +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "ForStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 174, +// CHECK-NEXT: "line": 7, +// CHECK-NEXT: "col": 8, +// CHECK-NEXT: "tokLen": 3 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 237, +// CHECK-NEXT: "line": 11, +// CHECK-NEXT: "col": 5, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: {}, +// CHECK-NEXT: {}, +// CHECK-NEXT: {}, +// CHECK-NEXT: {}, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "CompoundStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 183, +// CHECK-NEXT: "line": 7, +// CHECK-NEXT: "col": 17, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 237, +// CHECK-NEXT: "line": 11, +// CHECK-NEXT: "col": 5, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "inner": [ +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "BreakStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 191, +// CHECK-NEXT: "line": 8, +// CHECK-NEXT: "col": 7, +// CHECK-NEXT: "tokLen": 5 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 197, +// 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": 206, +// CHECK-NEXT: "line": 9, +// CHECK-NEXT: "col": 7, +// CHECK-NEXT: "tokLen": 8 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 215, +// CHECK-NEXT: "col": 16, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: "targetLabelDeclId": "0x{{.*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "id": "0x{{.*}}", +// CHECK-NEXT: "kind": "BreakStmt", +// CHECK-NEXT: "range": { +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 224, +// CHECK-NEXT: "line": 10, +// CHECK-NEXT: "col": 7, +// CHECK-NEXT: "tokLen": 5 +// CHECK-NEXT: }, +// CHECK-NEXT: "end": { +// CHECK-NEXT: "offset": 230, +// 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: } 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..a1ec812017557 --- /dev/null +++ b/clang/test/AST/ast-dump-labeled-break-continue.c @@ -0,0 +1,40 @@ +// 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: while (true) { + break a; + continue a; + c: for (;;) { + break a; + continue a; + break c; + } + } +} + +// CHECK-LABEL: `-FunctionDecl {{.*}} TestLabeledBreakContinue +// CHECK-NEXT: `-CompoundStmt {{.*}} +// CHECK-NEXT: `-LabelStmt {{.*}} 'a' +// CHECK-NEXT: `-WhileStmt {{.*}} +// CHECK-NEXT: |-CXXBoolLiteralExpr {{.*}} 'bool' true +// CHECK-NEXT: `-CompoundStmt {{.*}} +// CHECK-NEXT: |-BreakStmt {{.*}} 'a' (WhileStmt {{.*}}) +// CHECK-NEXT: |-ContinueStmt {{.*}} 'a' (WhileStmt {{.*}}) +// CHECK-NEXT: `-LabelStmt {{.*}} 'c' +// CHECK-NEXT: `-ForStmt {{.*}} +// CHECK-NEXT: |-<<>> +// CHECK-NEXT: |-<<>> +// CHECK-NEXT: |-<<>> +// CHECK-NEXT: |-<<>> +// CHECK-NEXT: `-CompoundStmt {{.*}} +// CHECK-NEXT: |-BreakStmt {{.*}} 'a' (WhileStmt {{.*}}) +// CHECK-NEXT: |-ContinueStmt {{.*}} 'a' (WhileStmt {{.*}}) +// CHECK-NEXT: `-BreakStmt {{.*}} 'c' (ForStmt {{.*}}) diff --git a/clang/test/AST/ast-print-labeled-break-continue.c b/clang/test/AST/ast-print-labeled-break-continue.c new file mode 100644 index 0000000000000..163bb759aa59e --- /dev/null +++ b/clang/test/AST/ast-print-labeled-break-continue.c @@ -0,0 +1,28 @@ +// RUN: %clang_cc1 -std=c2y -ast-print %s | FileCheck %s + +void TestLabeledBreakContinue() { + a: while (true) { + break a; + continue a; + c: for (;;) { + break a; + continue a; + break c; + } + } +} + +// CHECK-LABEL: void TestLabeledBreakContinue(void) { +// CHECK-NEXT: a: +// CHECK-NEXT: while (true) +// CHECK-NEXT: { +// CHECK-NEXT: break a; +// CHECK-NEXT: continue a; +// CHECK-NEXT: c: +// CHECK-NEXT: for (;;) { +// CHECK-NEXT: break a; +// CHECK-NEXT: continue a; +// CHECK-NEXT: break c; +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: } diff --git a/clang/test/Analysis/cfg.c b/clang/test/Analysis/cfg.c index e21f6109dbd59..0db82ef2f3d70 100644 --- a/clang/test/Analysis/cfg.c +++ b/clang/test/Analysis/cfg.c @@ -1,6 +1,9 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -Wno-error=invalid-gnu-asm-cast %s > %t 2>&1 // RUN: FileCheck --input-file=%t --check-prefix=CHECK %s +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -triple x86_64-apple-darwin12 -std=c2y -Wno-error=invalid-gnu-asm-cast %s > %t 2>&1 +// RUN: FileCheck --input-file=%t --check-prefixes=CHECK,SINCE-C26 %s + // This file is the C version of cfg.cpp. // Tests that are C-specific should go into this file. @@ -118,3 +121,144 @@ void vla_type_indirect(int x) { // Do not evaluate x void (*fp_vla)(int[x]); } + +#if __STDC_VERSION__ >= 202400L // If C26 or above +// SINCE-C26: int labeled_break_and_continue(int x) +// SINCE-C26-NEXT: [B17 (ENTRY)] +// SINCE-C26-NEXT: Succs (1): B2 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B1] +// SINCE-C26-NEXT: 1: 0 +// SINCE-C26-NEXT: 2: return [B1.1]; +// SINCE-C26-NEXT: Preds (1): B9 +// SINCE-C26-NEXT: Succs (1): B0 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B2] +// SINCE-C26-NEXT: a: +// SINCE-C26-NEXT: 1: x +// SINCE-C26-NEXT: 2: [B2.1] (ImplicitCastExpr, LValueToRValue, int) +// SINCE-C26-NEXT: T: switch [B2.2] +// SINCE-C26-NEXT: Preds (1): B17 +// SINCE-C26-NEXT: Succs (3): B9 B16 B8 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B3] +// SINCE-C26-NEXT: 1: x +// SINCE-C26-NEXT: 2: [B3.1] (ImplicitCastExpr, LValueToRValue, int) +// SINCE-C26-NEXT: 3: 2 +// SINCE-C26-NEXT: 4: [B3.2] + [B3.3] +// SINCE-C26-NEXT: 5: return [B3.4]; +// SINCE-C26-NEXT: Preds (3): B6 B7 B4 +// SINCE-C26-NEXT: Succs (1): B0 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B4] +// SINCE-C26-NEXT: c: +// SINCE-C26-NEXT: 1: x +// SINCE-C26-NEXT: 2: [B4.1] (ImplicitCastExpr, LValueToRValue, int) +// SINCE-C26-NEXT: T: switch [B4.2] +// SINCE-C26-NEXT: Preds (1): B8 +// SINCE-C26-NEXT: Succs (3): B6 B7 B3 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B5] +// SINCE-C26-NEXT: 1: x +// SINCE-C26-NEXT: 2: [B5.1] (ImplicitCastExpr, LValueToRValue, int) +// SINCE-C26-NEXT: 3: 3 +// SINCE-C26-NEXT: 4: [B5.2] + [B5.3] +// SINCE-C26-NEXT: 5: return [B5.4]; +// SINCE-C26-NEXT: Succs (1): B0 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B6] +// SINCE-C26-NEXT: case 30: +// SINCE-C26-NEXT: T: break c; +// SINCE-C26-NEXT: Preds (1): B4 +// SINCE-C26-NEXT: Succs (1): B3 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B7] +// SINCE-C26-NEXT: case 10: +// SINCE-C26-NEXT: T: break a; +// SINCE-C26-NEXT: Preds (1): B4 +// SINCE-C26-NEXT: Succs (1): B3 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B8] +// SINCE-C26-NEXT: default: +// SINCE-C26-NEXT: Preds (1): B2 +// SINCE-C26-NEXT: Succs (1): B4 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B9] +// SINCE-C26-NEXT: case 2: +// SINCE-C26-NEXT: T: break a; +// SINCE-C26-NEXT: Preds (2): B2 B11 +// SINCE-C26-NEXT: Succs (1): B1 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B10] +// SINCE-C26-NEXT: 1: 1 +// SINCE-C26-NEXT: T: do ... while [B10.1] +// SINCE-C26-NEXT: Preds (1): B12 +// SINCE-C26-NEXT: Succs (2): B14 NULL +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B11] +// SINCE-C26-NEXT: T: break b; +// SINCE-C26-NEXT: Preds (1): B13 +// SINCE-C26-NEXT: Succs (1): B9 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B12] +// SINCE-C26-NEXT: 1: x +// SINCE-C26-NEXT: 2: ++[B12.1] +// SINCE-C26-NEXT: T: continue b; +// SINCE-C26-NEXT: Preds (1): B13 +// SINCE-C26-NEXT: Succs (1): B10 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B13] +// SINCE-C26-NEXT: 1: x +// SINCE-C26-NEXT: 2: [B13.1] (ImplicitCastExpr, LValueToRValue, int) +// SINCE-C26-NEXT: 3: x +// SINCE-C26-NEXT: 4: [B13.3] (ImplicitCastExpr, LValueToRValue, int) +// SINCE-C26-NEXT: 5: [B13.2] * [B13.4] +// SINCE-C26-NEXT: 6: 100 +// SINCE-C26-NEXT: 7: [B13.5] > [B13.6] +// SINCE-C26-NEXT: T: if [B13.7] +// SINCE-C26-NEXT: Preds (2): B14 B15 +// SINCE-C26-NEXT: Succs (2): B12 B11 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B14] +// SINCE-C26-NEXT: Preds (1): B10 +// SINCE-C26-NEXT: Succs (1): B13 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B15] +// SINCE-C26-NEXT: b: +// SINCE-C26-NEXT: Preds (1): B16 +// SINCE-C26-NEXT: Succs (1): B13 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B16] +// SINCE-C26-NEXT: case 1: +// SINCE-C26-NEXT: Preds (1): B2 +// SINCE-C26-NEXT: Succs (1): B15 +// SINCE-C26-EMPTY: +// SINCE-C26-NEXT: [B0 (EXIT)] +// SINCE-C26-NEXT: Preds (3): B1 B3 B5 +int labeled_break_and_continue(int x) { + a: switch (x) { + case 1: + b: do { + if (x * x > 100) { + ++x; + continue b; + } + break b; + } while (1); + case 2: + break a; + default: + c: switch (x) { + case 10: + break a; + case 30: + break c; + return x + 3; // dead code + } + return x + 2; + } + + return 0; +} + +#endif // __STDC_VERSION__ >= 202400L // If C26 or above 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..4bdb5369aa8f1 --- /dev/null +++ b/clang/test/CodeGenCXX/labeled-break-continue.cpp @@ -0,0 +1,221 @@ +// RUN: %clang_cc1 -fnamed-loops -triple x86_64-unknown-linux -std=c++20 -emit-llvm -o - %s | FileCheck %s + +static int a[10]{}; +struct NonTrivialDestructor { + ~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..e9979fe437b61 --- /dev/null +++ b/clang/test/CodeGenObjC/labeled-break-continue.m @@ -0,0 +1,174 @@ +// RUN: %clang_cc1 -std=c2y -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..5f6f9c9a3fbc9 100644 --- a/clang/test/OpenMP/for_loop_messages.cpp +++ b/clang/test/OpenMP/for_loop_messages.cpp @@ -1,8 +1,8 @@ -// RUN: %clang_cc1 -fsyntax-only -fopenmp -fopenmp-version=45 -x c++ -std=c++11 -fexceptions -fcxx-exceptions -verify=expected,omp4 %s -Wuninitialized -// RUN: %clang_cc1 -fsyntax-only -fopenmp -x c++ -std=c++11 -fexceptions -fcxx-exceptions -verify=expected,omp5 %s -Wuninitialized +// RUN: %clang_cc1 -fsyntax-only -fopenmp -fopenmp-version=45 -x c++ -std=c++11 -fexceptions -fcxx-exceptions -fnamed-loops -verify=expected,omp4 %s -Wuninitialized +// RUN: %clang_cc1 -fsyntax-only -fopenmp -x c++ -std=c++11 -fexceptions -fcxx-exceptions -fnamed-loops -verify=expected,omp5 %s -Wuninitialized -// RUN: %clang_cc1 -fsyntax-only -fopenmp-simd -fopenmp-version=45 -x c++ -std=c++11 -fexceptions -fcxx-exceptions -verify=expected,omp4 %s -Wuninitialized -// RUN: %clang_cc1 -fsyntax-only -fopenmp-simd -x c++ -std=c++11 -fexceptions -fcxx-exceptions -verify=expected,omp5 %s -Wuninitialized +// RUN: %clang_cc1 -fsyntax-only -fopenmp-simd -fopenmp-version=45 -x c++ -std=c++11 -fexceptions -fcxx-exceptions -fnamed-loops -verify=expected,omp4 %s -Wuninitialized +// RUN: %clang_cc1 -fsyntax-only -fopenmp-simd -x c++ -std=c++11 -fexceptions -fcxx-exceptions -fnamed-loops -verify=expected,omp5 %s -Wuninitialized class S { int a; @@ -842,3 +842,22 @@ void test_static_data_member() { }; } } + +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; // expected-error {{'break' statement cannot be used in OpenMP for loop}} + continue a; + } + + b: while (1) { +#pragma omp parallel +#pragma omp for + for (int i = 0; i < 16; ++i) { + break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + continue b; // expected-error {{'continue' label does not name an enclosing loop}} + } + } +} diff --git a/clang/test/Parser/labeled-break-continue.c b/clang/test/Parser/labeled-break-continue.c new file mode 100644 index 0000000000000..f02db35a40dde --- /dev/null +++ b/clang/test/Parser/labeled-break-continue.c @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c2y %s +// RUN: %clang_cc1 -fsyntax-only -verify -fnamed-loops -std=c23 %s +// RUN: %clang_cc1 -fsyntax-only -verify -fnamed-loops -x c++ %s +// RUN: %clang_cc1 -fsyntax-only -verify=disabled -std=c23 %s +// RUN: %clang_cc1 -fsyntax-only -verify=disabled -x c++ %s +// RUN: %clang_cc1 -fsyntax-only -verify=disabled -std=c23 -pedantic %s +// RUN: %clang_cc1 -fsyntax-only -verify=disabled -x c++ -pedantic %s +// expected-no-diagnostics + +void f() { + x1: while (1) break x1; // disabled-error {{labeled 'break' is only supported in C2y}} + x2: while (1) continue x2; // disabled-error {{labeled 'continue' is only supported in C2y}} +} diff --git a/clang/test/Sema/__try.c b/clang/test/Sema/__try.c index 9bfd914c013c1..06360cb0a5dcf 100644 --- a/clang/test/Sema/__try.c +++ b/clang/test/Sema/__try.c @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -triple x86_64-windows -fborland-extensions -DBORLAND -fsyntax-only -verify -fblocks %s -// RUN: %clang_cc1 -triple x86_64-windows -fms-extensions -fsyntax-only -verify -fblocks %s +// RUN: %clang_cc1 -triple x86_64-windows -fborland-extensions -DBORLAND -fsyntax-only -verify -fblocks -fnamed-loops %s +// RUN: %clang_cc1 -triple x86_64-windows -fms-extensions -fsyntax-only -verify -fblocks -fnamed-loops %s #define JOIN2(x,y) x ## y #define JOIN(x,y) JOIN2(x,y) @@ -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..78f81c484c3d5 --- /dev/null +++ b/clang/test/Sema/labeled-break-continue.c @@ -0,0 +1,161 @@ +// RUN: %clang_cc1 -std=c2y -verify -fsyntax-only -fblocks %s +// RUN: %clang_cc1 -std=c23 -verify -fsyntax-only -fblocks -fnamed-loops %s +// RUN: %clang_cc1 -x c++ -verify -fsyntax-only -fblocks -fnamed-loops %s + +void f1() { + l1: while (true) { + 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; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + break c; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + break d; + + continue a; // expected-error {{'continue' label does not name an enclosing loop}} + continue b; // expected-error {{'continue' label does not name an enclosing loop}} + continue c; // expected-error {{'continue' label does not name an enclosing loop}} + continue d; + + e: while (true) { + break a; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + break b; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + break c; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + break d; + break e; + + continue a; // expected-error {{'continue' label does not name an enclosing loop}} + continue b; // expected-error {{'continue' label does not name an enclosing loop}} + continue c; // expected-error {{'continue' label does not name an enclosing loop}} + continue d; + continue e; + } + + 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; + })) { + ({ break a; }); + ({ continue a; }); + } + + 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; + }) + ) { + ({ break b; }); + ({ continue b; }); + } + + c: do { + ({ break c; }); + ({ continue c; }); + } while (({ + break c; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + continue c; // expected-error {{'continue' label does not name an enclosing loop}} + 1; + })); + + d: switch (({ + break d; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + continue d; // expected-error {{'continue' label does not name an enclosing loop}} + 1; + })) { + case 1: { + ({ break d; }); + ({ continue d; }); // expected-error {{label of 'continue' refers to a switch statement}} + } + } +} + +void f7() { + a: while (true) { + (void) ^{ + 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}} + }; + } + + while (true) { + break c; // expected-error {{'break' label does not name an enclosing loop or 'switch'}} + continue d; // expected-error {{'continue' label does not name an enclosing loop}} + } +} diff --git a/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp new file mode 100644 index 0000000000000..bec6c582a1f0d --- /dev/null +++ b/clang/test/SemaCXX/labeled-break-continue-constexpr.cpp @@ -0,0 +1,169 @@ +// RUN: %clang_cc1 -fnamed-loops -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..3d34211ed745a --- /dev/null +++ b/clang/test/SemaCXX/labeled-break-continue.cpp @@ -0,0 +1,51 @@ +// RUN: %clang_cc1 -std=c++20 -verify -fsyntax-only -fnamed-loops %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: while (true) { + (void) []{ + 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}} + }; + } +} diff --git a/clang/test/SemaObjC/labeled-break-continue.m b/clang/test/SemaObjC/labeled-break-continue.m new file mode 100644 index 0000000000000..2791474a579b7 --- /dev/null +++ b/clang/test/SemaObjC/labeled-break-continue.m @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -std=c2y -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 {{'break' label does not name an enclosing loop or 'switch'}} + continue b; // expected-error {{'continue' label does not name an enclosing loop}} + }; + } +} diff --git a/clang/test/SemaOpenACC/no-branch-in-out.c b/clang/test/SemaOpenACC/no-branch-in-out.c index 37126d8f2200e..370722b52ab19 100644 --- a/clang/test/SemaOpenACC/no-branch-in-out.c +++ b/clang/test/SemaOpenACC/no-branch-in-out.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 %s -verify -fopenacc +// RUN: %clang_cc1 %s -verify -fopenacc -fnamed-loops void BreakContinue() { @@ -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