[clang] e4a1b5f - [Clang] [C2y] Implement N3355 ‘Named Loops’ (#152870)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 2 09:37:24 PDT 2025
Author: Sirraide
Date: 2025-09-02T16:37:19Z
New Revision: e4a1b5f36e71c8c382bdd531867c5f6eb3f7deac
URL: https://github.com/llvm/llvm-project/commit/e4a1b5f36e71c8c382bdd531867c5f6eb3f7deac
DIFF: https://github.com/llvm/llvm-project/commit/e4a1b5f36e71c8c382bdd531867c5f6eb3f7deac.diff
LOG: [Clang] [C2y] Implement N3355 ‘Named Loops’ (#152870)
This implements support for [named
loops](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm) for
C2y.
When parsing a `LabelStmt`, we create the `LabeDecl` early before we parse
the substatement; this label is then passed down to `ParseWhileStatement()`
and friends, which then store it in the loop’s (or switch statement’s) `Scope`;
when we encounter a `break/continue` statement, we perform a lookup for
the label (and error if it doesn’t exist), and then walk the scope stack and
check if there is a scope whose preceding label is the target label, which
identifies the jump target.
The feature is only supported in C2y mode, though a cc1-only option
exists for testing (`-fnamed-loops`), which is mostly intended to try
and make sure that we don’t have to refactor this entire implementation
when/if we start supporting it in C++.
---------
Co-authored-by: Balazs Benics <benicsbalazs at gmail.com>
Added:
clang/test/AST/ast-dump-labeled-break-continue-json.c
clang/test/AST/ast-dump-labeled-break-continue.c
clang/test/AST/ast-print-labeled-break-continue.c
clang/test/CodeGen/labeled-break-continue.c
clang/test/CodeGenCXX/labeled-break-continue.cpp
clang/test/CodeGenObjC/labeled-break-continue.m
clang/test/Parser/labeled-break-continue.c
clang/test/Sema/labeled-break-continue.c
clang/test/SemaCXX/labeled-break-continue-constexpr.cpp
clang/test/SemaCXX/labeled-break-continue.cpp
clang/test/SemaObjC/labeled-break-continue.m
Modified:
clang-tools-extra/clangd/XRefs.cpp
clang/docs/ReleaseNotes.rst
clang/include/clang/AST/JSONNodeDumper.h
clang/include/clang/AST/Stmt.h
clang/include/clang/AST/TextNodeDumper.h
clang/include/clang/Basic/DiagnosticParseKinds.td
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/include/clang/Basic/LangOptions.def
clang/include/clang/Basic/StmtNodes.td
clang/include/clang/Driver/Options.td
clang/include/clang/Parse/Parser.h
clang/include/clang/Sema/Scope.h
clang/include/clang/Sema/Sema.h
clang/lib/AST/ASTImporter.cpp
clang/lib/AST/ExprConstant.cpp
clang/lib/AST/JSONNodeDumper.cpp
clang/lib/AST/Stmt.cpp
clang/lib/AST/StmtPrinter.cpp
clang/lib/AST/TextNodeDumper.cpp
clang/lib/Basic/LangOptions.cpp
clang/lib/CodeGen/CGObjC.cpp
clang/lib/CodeGen/CGStmt.cpp
clang/lib/CodeGen/CGStmtOpenMP.cpp
clang/lib/CodeGen/CodeGenFunction.h
clang/lib/Frontend/CompilerInvocation.cpp
clang/lib/Parse/ParseStmt.cpp
clang/lib/Sema/Scope.cpp
clang/lib/Sema/SemaLookup.cpp
clang/lib/Sema/SemaStmt.cpp
clang/lib/Sema/TreeTransform.h
clang/lib/Serialization/ASTReaderStmt.cpp
clang/lib/Serialization/ASTWriterStmt.cpp
clang/lib/Tooling/Syntax/BuildTree.cpp
clang/test/Analysis/cfg.c
clang/test/OpenMP/for_loop_messages.cpp
clang/test/Sema/__try.c
clang/test/SemaOpenACC/no-branch-in-out.c
clang/www/c_status.html
Removed:
################################################################################
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index a253a630a48cc..e1c50f906de08 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1106,11 +1106,11 @@ class FindControlFlow : public RecursiveASTVisitor<FindControlFlow> {
return true;
}
bool VisitBreakStmt(BreakStmt *B) {
- found(Break, B->getBreakLoc());
+ found(Break, B->getKwLoc());
return true;
}
bool VisitContinueStmt(ContinueStmt *C) {
- found(Continue, C->getContinueLoc());
+ found(Continue, C->getKwLoc());
return true;
}
bool VisitSwitchCase(SwitchCase *C) {
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index c0c2766f76b4b..dd53b4d46f3cc 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -134,6 +134,7 @@ C Language Changes
C2y Feature Support
^^^^^^^^^^^^^^^^^^^
+- Clang now supports `N3355 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm>`_ Named Loops.
C23 Feature Support
^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/AST/JSONNodeDumper.h b/clang/include/clang/AST/JSONNodeDumper.h
index 8640780206dba..427a9c51ece1b 100644
--- a/clang/include/clang/AST/JSONNodeDumper.h
+++ b/clang/include/clang/AST/JSONNodeDumper.h
@@ -333,6 +333,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..76942f1a84f9a 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;
@@ -2184,6 +2173,14 @@ class LabelStmt : public ValueStmt {
SourceLocation getBeginLoc() const { return getIdentLoc(); }
SourceLocation getEndLoc() const LLVM_READONLY { return SubStmt->getEndLoc();}
+ /// Look through nested labels and return the first non-label statement; e.g.
+ /// if this is 'a:' in 'a: b: c: for(;;)', this returns the for loop.
+ const Stmt *getInnermostLabeledStmt() const;
+ Stmt *getInnermostLabeledStmt() {
+ return const_cast<Stmt *>(
+ const_cast<const LabelStmt *>(this)->getInnermostLabeledStmt());
+ }
+
child_range children() { return child_range(&SubStmt, &SubStmt + 1); }
const_child_range children() const {
@@ -3056,26 +3053,53 @@ 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 named break/continue, the label whose statement we're
+ /// targeting, as well as the source location of the label after the
+ /// keyword; for example:
+ ///
+ /// a: // <-- TargetLabel
+ /// for (;;)
+ /// break a; // <-- LabelLoc
+ ///
+ LabelDecl *TargetLabel = nullptr;
+ SourceLocation LabelLoc;
+
+protected:
+ LoopControlStmt(StmtClass Class, SourceLocation Loc, SourceLocation LabelLoc,
+ LabelDecl *Target)
+ : Stmt(Class), TargetLabel(Target), LabelLoc(LabelLoc) {
+ setKwLoc(Loc);
}
- /// Build an empty continue statement.
- explicit ContinueStmt(EmptyShell Empty) : Stmt(ContinueStmtClass, Empty) {}
+ LoopControlStmt(StmtClass Class, SourceLocation Loc)
+ : LoopControlStmt(Class, Loc, SourceLocation(), nullptr) {}
- 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 hasLabelTarget() ? getLabelLoc() : getKwLoc();
}
+ bool hasLabelTarget() const { return TargetLabel != nullptr; }
+
+ SourceLocation getLabelLoc() const { return LabelLoc; }
+ void setLabelLoc(SourceLocation L) { LabelLoc = L; }
+
+ LabelDecl *getLabelDecl() { return TargetLabel; }
+ const LabelDecl *getLabelDecl() const { return TargetLabel; }
+ void setLabelDecl(LabelDecl *S) { TargetLabel = S; }
+
+ /// If this is a named break/continue, get the loop or switch statement
+ /// that this targets.
+ const Stmt *getNamedLoopOrSwitch() const;
+
// Iterators
child_range children() {
return child_range(child_iterator(), child_iterator());
@@ -3084,35 +3108,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 6d2795111685a..88ecd526e3d7e 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 ff506fb258b64..bc7a6e231d93c 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<CPre2yCompat>;
def ext_c2y_case_range : Extension<
"case ranges are a C2y extension">, InGroup<C2y>;
+def err_c2y_labeled_break_continue : Error<
+ "named %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 c934fed2c7462..0f3aa9aea215f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10824,6 +10824,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<GccCompat>;
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index e0a5351143dfd..84f5ab3443a59 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -193,6 +193,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 named 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<Stmt>;
def ForStmt : StmtNode<Stmt>;
def GotoStmt : StmtNode<Stmt>;
def IndirectGotoStmt : StmtNode<Stmt>;
-def ContinueStmt : StmtNode<Stmt>;
-def BreakStmt : StmtNode<Stmt>;
def ReturnStmt : StmtNode<Stmt>;
def DeclStmt : StmtNode<Stmt>;
def SwitchCase : StmtNode<Stmt, 1>;
@@ -26,6 +24,11 @@ def DefaultStmt : StmtNode<SwitchCase>;
def CapturedStmt : StmtNode<Stmt>;
def SYCLKernelCallStmt : StmtNode<Stmt>;
+// Break/continue.
+def LoopControlStmt : StmtNode<Stmt, 1>;
+def ContinueStmt : StmtNode<LoopControlStmt>;
+def BreakStmt : StmtNode<LoopControlStmt>;
+
// Statements that might produce a value (for example, as the last non-null
// statement in a GNU statement-expression).
def ValueStmt : StmtNode<Stmt, 1>;
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index f507968d30670..902a28d60b349 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -1642,6 +1642,17 @@ defm auto_import : BoolFOption<"auto-import",
def offload_EQ : CommaJoined<["--"], "offload=">, Flags<[NoXarchOption]>, Alias<offload_targets_EQ>,
HelpText<"Specify comma-separated list of offloading target triples (CUDA and HIP only)">;
+// This flag is only here so we can test the named loops implementation
+// in C++ mode and C language modes before C2y to make sure it actually
+// works; it should be removed once the syntax of the feature is stable
+// enough to backport it to earlier language modes (and to C++ if it ever
+// gets standardised as a C++ feature).
+defm named_loops
+ : BoolFOption<
+ "named-loops", LangOpts<"NamedLoops">, DefaultFalse,
+ PosFlag<SetTrue, [], [CC1Option], "Enable support for named loops">,
+ NegFlag<SetFalse>>;
+
// C++ Coroutines
defm coroutines : BoolFOption<"coroutines",
LangOpts<"Coroutines">, Default<cpp20.KeyPath>,
diff --git a/clang/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<VarDecl *, 8> 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 c3fb57774c8dc..aa035a1555950 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -9488,6 +9488,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);
@@ -11042,8 +11046,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 6299efaf6bbfc..0899e86c2e25d 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -7337,18 +7337,28 @@ ExpectedStmt ASTNodeImporter::VisitIndirectGotoStmt(IndirectGotoStmt *S) {
ToGotoLoc, ToStarLoc, ToTarget);
}
+template <typename StmtClass>
+static ExpectedStmt ImportLoopControlStmt(ASTNodeImporter &NodeImporter,
+ ASTImporter &Importer, StmtClass *S) {
+ Error Err = Error::success();
+ auto ToLoc = NodeImporter.importChecked(Err, S->getKwLoc());
+ auto ToLabelLoc = S->hasLabelTarget()
+ ? NodeImporter.importChecked(Err, S->getLabelLoc())
+ : SourceLocation();
+ auto ToDecl = S->hasLabelTarget()
+ ? 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 b4f1e76187e25..798e19f38f093 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -885,6 +885,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<const Stmt *> BreakContinueStack;
+
/// Set of objects that are currently being constructed.
llvm::DenseMap<ObjectUnderConstruction, ConstructionPhase>
ObjectsUnderConstruction;
@@ -5377,6 +5382,44 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
const Stmt *S,
const SwitchCase *SC = nullptr);
+/// Helper to implement named break/continue. Returns 'true' if the evaluation
+/// result should be propagated up. Otherwise, it sets the evaluation result
+/// to either Continue to continue the current loop, or Succeeded to break it.
+static bool ShouldPropagateBreakContinue(EvalInfo &Info,
+ const Stmt *LoopOrSwitch,
+ ArrayRef<BlockScopeRAII *> Scopes,
+ EvalStmtResult &ESR) {
+ bool IsSwitch = isa<SwitchStmt>(LoopOrSwitch);
+
+ // For loops, map Succeeded to Continue so we don't have to check for both.
+ if (!IsSwitch && ESR == ESR_Succeeded) {
+ ESR = ESR_Continue;
+ return false;
+ }
+
+ if (ESR != ESR_Break && ESR != ESR_Continue)
+ return false;
+
+ // Are we breaking out of or continuing this statement?
+ bool CanBreakOrContinue = !IsSwitch || ESR == ESR_Break;
+ const 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,
@@ -5387,18 +5430,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.
@@ -5464,10 +5496,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:
@@ -5565,6 +5599,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
case Stmt::WhileStmtClass: {
EvalStmtResult ESR =
EvaluateLoopBody(Result, Info, cast<WhileStmt>(S)->getBody(), Case);
+ if (ShouldPropagateBreakContinue(Info, S, /*Scopes=*/{}, ESR))
+ return ESR;
if (ESR != ESR_Continue)
return ESR;
break;
@@ -5586,6 +5622,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()) {
@@ -5748,6 +5786,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;
@@ -5764,6 +5805,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;
@@ -5806,6 +5849,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;
@@ -5897,6 +5942,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;
@@ -5922,10 +5969,11 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
return EvaluateSwitch(Result, Info, cast<SwitchStmt>(S));
case Stmt::ContinueStmtClass:
- return ESR_Continue;
-
- case Stmt::BreakStmtClass:
- return ESR_Break;
+ case Stmt::BreakStmtClass: {
+ auto *B = cast<LoopControlStmt>(S);
+ Info.BreakContinueStack.push_back(B->getNamedLoopOrSwitch());
+ return isa<ContinueStmt>(S) ? ESR_Continue : ESR_Break;
+ }
case Stmt::LabelStmtClass:
return EvaluateStmt(Result, Info, cast<LabelStmt>(S)->getSubStmt(), Case);
diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp
index ca8e2af284c2b..2f4aebd0845dd 100644
--- a/clang/lib/AST/JSONNodeDumper.cpp
+++ b/clang/lib/AST/JSONNodeDumper.cpp
@@ -1671,6 +1671,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->hasLabelTarget())
+ 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..9ae8aea3ab37a 100644
--- a/clang/lib/AST/Stmt.cpp
+++ b/clang/lib/AST/Stmt.cpp
@@ -1482,3 +1482,16 @@ bool CapturedStmt::capturesVariable(const VarDecl *Var) const {
return false;
}
+
+const Stmt *LabelStmt::getInnermostLabeledStmt() const {
+ const Stmt *S = getSubStmt();
+ while (isa_and_present<LabelStmt>(S))
+ S = cast<LabelStmt>(S)->getSubStmt();
+ return S;
+}
+
+const Stmt *LoopControlStmt::getNamedLoopOrSwitch() const {
+ if (!hasLabelTarget())
+ return nullptr;
+ return getLabelDecl()->getStmt()->getInnermostLabeledStmt();
+}
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index afccba8778fd2..0030300521128 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -473,12 +473,21 @@ void StmtPrinter::VisitIndirectGotoStmt(IndirectGotoStmt *Node) {
}
void StmtPrinter::VisitContinueStmt(ContinueStmt *Node) {
- Indent() << "continue;";
+ Indent();
+ if (Node->hasLabelTarget())
+ OS << "continue " << Node->getLabelDecl()->getIdentifier()->getName()
+ << ';';
+ else
+ OS << "continue;";
if (Policy.IncludeNewlines) OS << NL;
}
void StmtPrinter::VisitBreakStmt(BreakStmt *Node) {
- Indent() << "break;";
+ Indent();
+ if (Node->hasLabelTarget())
+ 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 9dca5cf088a85..8c59dbd345439 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -1412,6 +1412,26 @@ static void dumpBasePath(raw_ostream &OS, const CastExpr *Node) {
OS << ')';
}
+void TextNodeDumper::VisitLoopControlStmt(const LoopControlStmt *Node) {
+ if (!Node->hasLabelTarget())
+ return;
+
+ OS << " '" << Node->getLabelDecl()->getIdentifier()->getName() << "' (";
+
+ auto *Target = Node->getNamedLoopOrSwitch();
+ if (!Target) {
+ ColorScope Color(OS, ShowColors, NullColor);
+ OS << "<<<NULL>>>";
+ } else {
+ {
+ ColorScope Color(OS, ShowColors, StmtColor);
+ OS << Target->getStmtClassName();
+ }
+ dumpPointer(Target);
+ }
+ OS << ")";
+}
+
void TextNodeDumper::VisitIfStmt(const IfStmt *Node) {
if (Node->hasInitStorage())
OS << " has_init";
diff --git a/clang/lib/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 b01d5471a9836..10aad2e26938d 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 031ef73214e76..aeff73d525c10 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.hasLabelTarget())
+ return &BreakContinueStack.back();
+
+ const Stmt *LoopOrSwitch = S.getNamedLoopOrSwitch();
+ 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 1074ee5d34fde..2708fc0470f5b 100644
--- a/clang/lib/CodeGen/CGStmtOpenMP.cpp
+++ b/clang/lib/CodeGen/CGStmtOpenMP.cpp
@@ -1981,7 +1981,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;
@@ -2210,7 +2210,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);
@@ -3055,7 +3055,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 c02ac18ec0198..123cb4f51f828 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1552,9 +1552,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;
};
@@ -3606,6 +3608,8 @@ class CodeGenFunction : public CodeGenTypeCache {
void EmitCaseStmtRange(const CaseStmt &S, ArrayRef<const Attr *> Attrs);
void EmitAsmStmt(const AsmStmt &S);
+ const BreakContinue *GetDestForLoopControlStmt(const LoopControlStmt &S);
+
void EmitObjCForCollectionStmt(const ObjCForCollectionStmt &S);
void EmitObjCAtTryStmt(const ObjCAtTryStmt &S);
void EmitObjCAtThrowStmt(const ObjCAtThrowStmt &S);
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 29f9cf3a7f0e3..aadda694a0854 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 86ffae9363beb..54918c560b655 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -4453,26 +4453,28 @@ void Sema::LookupVisibleDecls(DeclContext *Ctx, LookupNameKind Kind,
H.lookupVisibleDecls(*this, Ctx, Kind, IncludeGlobalScope);
}
+LabelDecl *Sema::LookupExistingLabel(IdentifierInfo *II, SourceLocation Loc) {
+ NamedDecl *Res = LookupSingleName(CurScope, II, Loc, LookupLabel,
+ RedeclarationKind::NotForRedeclaration);
+ // If we found a label, check to see if it is in the same context as us.
+ // When in a Block, we don't want to reuse a label in an enclosing function.
+ if (!Res || Res->getDeclContext() != CurContext)
+ return nullptr;
+ return cast<LabelDecl>(Res);
+}
+
LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
SourceLocation GnuLabelLoc) {
- // Do a lookup to see if we have a label with this name already.
- NamedDecl *Res = nullptr;
-
if (GnuLabelLoc.isValid()) {
// Local label definitions always shadow existing labels.
- Res = LabelDecl::Create(Context, CurContext, Loc, II, GnuLabelLoc);
+ auto *Res = LabelDecl::Create(Context, CurContext, Loc, II, GnuLabelLoc);
Scope *S = CurScope;
PushOnScopeChains(Res, S, true);
return cast<LabelDecl>(Res);
}
// Not a GNU local label.
- Res = LookupSingleName(CurScope, II, Loc, LookupLabel,
- RedeclarationKind::NotForRedeclaration);
- // If we found a label, check to see if it is in the same context as us.
- // When in a Block, we don't want to reuse a label in an enclosing function.
- if (Res && Res->getDeclContext() != CurContext)
- Res = nullptr;
+ LabelDecl *Res = LookupExistingLabel(II, Loc);
if (!Res) {
// If not forward referenced or defined already, create the backing decl.
Res = LabelDecl::Create(Context, CurContext, Loc, II);
@@ -4480,7 +4482,7 @@ LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
assert(S && "Not in a function?");
PushOnScopeChains(Res, S, true);
}
- return cast<LabelDecl>(Res);
+ return Res;
}
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 5625fb359807a..ae0bb616beb82 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -2122,12 +2122,12 @@ namespace {
typedef ConstEvaluatedExprVisitor<BreakContinueFinder> Inherited;
void VisitContinueStmt(const ContinueStmt* E) {
- ContinueLoc = E->getContinueLoc();
+ ContinueLoc = E->getKwLoc();
}
void VisitBreakStmt(const BreakStmt* E) {
if (!InSwitch)
- BreakLoc = E->getBreakLoc();
+ BreakLoc = E->getKwLoc();
}
void VisitSwitchStmt(const SwitchStmt* S) {
@@ -3275,9 +3275,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 named 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));
@@ -3299,16 +3345,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");
@@ -3329,7 +3386,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 aa1bb3232d6fa..fbadb8d6881a5 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -8548,13 +8548,31 @@ TreeTransform<Derived>::TransformIndirectGotoStmt(IndirectGotoStmt *S) {
template<typename Derived>
StmtResult
TreeTransform<Derived>::TransformContinueStmt(ContinueStmt *S) {
- return S;
+ if (!S->hasLabelTarget())
+ 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<LabelDecl>(LD));
}
template<typename Derived>
StmtResult
TreeTransform<Derived>::TransformBreakStmt(BreakStmt *S) {
- return S;
+ if (!S->hasLabelTarget())
+ 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<LabelDecl>(LD));
}
template<typename Derived>
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<LabelDecl>());
+ 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 301ed9b23c206..e36d83fe4559b 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->hasLabelTarget());
+ if (S->hasLabelTarget()) {
+ 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 546161cee33f4..b75f8ff6defee 100644
--- a/clang/lib/Tooling/Syntax/BuildTree.cpp
+++ b/clang/lib/Tooling/Syntax/BuildTree.cpp
@@ -1482,16 +1482,14 @@ class BuildTreeVisitor : public RecursiveASTVisitor<BuildTreeVisitor> {
}
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/ <undeserialized declarations>//" -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 {{.*}} <col:33, line:21:1>
+// CHECK-NEXT: `-LabelStmt {{.*}} <line:12:3, line:20:3> 'a'
+// CHECK-NEXT: `-WhileStmt {{.*}} <line:12:6, line:20:3>
+// CHECK-NEXT: |-CXXBoolLiteralExpr {{.*}} <line:12:13> 'bool' true
+// CHECK-NEXT: `-CompoundStmt {{.*}} <col:19, line:20:3>
+// CHECK-NEXT: |-BreakStmt {{.*}} <line:13:5, col:11> 'a' (WhileStmt {{.*}})
+// CHECK-NEXT: |-ContinueStmt {{.*}} <line:14:5, col:14> 'a' (WhileStmt {{.*}})
+// CHECK-NEXT: `-LabelStmt {{.*}} <line:15:5, line:19:5> 'c'
+// CHECK-NEXT: `-ForStmt {{.*}} <line:15:8, line:19:5>
+// CHECK-NEXT: |-<<<NULL>>>
+// CHECK-NEXT: |-<<<NULL>>>
+// CHECK-NEXT: |-<<<NULL>>>
+// CHECK-NEXT: |-<<<NULL>>>
+// CHECK-NEXT: `-CompoundStmt {{.*}} <line:15:17, line:19:5>
+// CHECK-NEXT: |-BreakStmt {{.*}} <line:16:7, col:13> 'a' (WhileStmt {{.*}})
+// CHECK-NEXT: |-ContinueStmt {{.*}} <line:17:7, col:16> 'a' (WhileStmt {{.*}})
+// CHECK-NEXT: `-BreakStmt {{.*}} <line:18:7, col:13> '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 <bool Continue>
+void f3() {
+ l1: while (g(1)) {
+ for (;g(2);) {
+ if constexpr (Continue) continue l1;
+ else break l1;
+ }
+ }
+}
+
+// CHECK-LABEL: define {{.*}} void @_Z2f3ILb1EEvv()
+// CHECK: entry:
+// CHECK: br label %l1
+// CHECK: l1:
+// CHECK: br label %while.cond
+// CHECK: while.cond:
+// CHECK: %call = call {{.*}} i1 @_Z1gi(i32 {{.*}} 1)
+// CHECK: br i1 %call, label %while.body, label %while.end
+// CHECK: while.body:
+// CHECK: br label %for.cond
+// CHECK: for.cond:
+// CHECK: %call1 = call {{.*}} i1 @_Z1gi(i32 {{.*}} 2)
+// CHECK: br i1 %call1, label %for.body, label %for.end
+// CHECK: for.body:
+// CHECK: br label %while.cond
+// CHECK: for.end:
+// CHECK: br label %while.cond
+// CHECK: while.end:
+// CHECK: ret void
+template void f3<true>();
+
+// CHECK-LABEL: define {{.*}} void @_Z2f3ILb0EEvv()
+// CHECK: entry:
+// CHECK: br label %l1
+// CHECK: l1:
+// CHECK: br label %while.cond
+// CHECK: while.cond:
+// CHECK: %call = call {{.*}} i1 @_Z1gi(i32 {{.*}} 1)
+// CHECK: br i1 %call, label %while.body, label %while.end
+// CHECK: while.body:
+// CHECK: br label %for.cond
+// CHECK: for.cond:
+// CHECK: %call1 = call {{.*}} i1 @_Z1gi(i32 {{.*}} 2)
+// CHECK: br i1 %call1, label %for.body, label %for.end
+// CHECK: for.body:
+// CHECK: br label %while.end
+// CHECK: for.end:
+// CHECK: br label %while.cond
+// CHECK: while.end:
+// CHECK: ret void
+template void f3<false>();
diff --git a/clang/test/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..81935884023ac
--- /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 {{named 'break' is only supported in C2y}}
+ x2: while (1) continue x2; // disabled-error {{named '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 <typename T>
+constexpr T f12() {
+ T x{};
+ a: for (T i = 0; i < 10; i++) {
+ b: for (T j = 0; j < 10; j++) {
+ x += j;
+ if (i == 2 && j == 2) break a;
+ }
+ }
+ return x;
+}
+static_assert(f12<int>() == 93);
+static_assert(f12<unsigned>() == 93u);
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 5b31f97e7a2e5..f65c34cc64022 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -252,7 +252,7 @@ <h2 id="c2y">C2y implementation status</h2>
<tr>
<td>Named loops, v3</td>
<td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3355.htm">N3355</a></td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 22</td>
</tr>
<!-- Graz Feb 2025 Papers -->
<tr>
More information about the cfe-commits
mailing list