[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@gmail.com>
This commit is contained in:
Sirraide 2025-09-02 18:37:19 +02:00 committed by GitHub
parent 83f390859e
commit e4a1b5f36e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 2217 additions and 182 deletions

View File

@ -1106,11 +1106,11 @@ public:
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) {

View File

@ -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
^^^^^^^^^^^^^^^^^^^

View File

@ -333,6 +333,7 @@ public:
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);

View File

@ -277,24 +277,14 @@ protected:
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 @@ protected:
DoStmtBitfields DoStmtBits;
ForStmtBitfields ForStmtBits;
GotoStmtBitfields GotoStmtBits;
ContinueStmtBitfields ContinueStmtBits;
BreakStmtBitfields BreakStmtBits;
LoopControlStmtBitfields LoopControlStmtBits;
ReturnStmtBitfields ReturnStmtBits;
SwitchCaseBitfields SwitchCaseBits;
@ -2184,6 +2173,14 @@ public:
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,64 +3053,98 @@ public:
}
};
/// 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);
}
LoopControlStmt(StmtClass Class, SourceLocation Loc)
: LoopControlStmt(Class, Loc, SourceLocation(), nullptr) {}
LoopControlStmt(StmtClass Class, EmptyShell ES) : Stmt(Class, ES) {}
public:
SourceLocation getKwLoc() const { return LoopControlStmtBits.KwLoc; }
void setKwLoc(SourceLocation L) { LoopControlStmtBits.KwLoc = L; }
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());
}
const_child_range children() const {
return const_child_range(const_child_iterator(), const_child_iterator());
}
static bool classof(const Stmt *T) {
StmtClass Class = T->getStmtClass();
return Class == ContinueStmtClass || Class == BreakStmtClass;
}
};
/// 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) {}
/// Build an empty continue statement.
explicit ContinueStmt(EmptyShell Empty) : Stmt(ContinueStmtClass, Empty) {}
SourceLocation getContinueLoc() const { return ContinueStmtBits.ContinueLoc; }
void setContinueLoc(SourceLocation L) { ContinueStmtBits.ContinueLoc = L; }
SourceLocation getBeginLoc() const { return getContinueLoc(); }
SourceLocation getEndLoc() const { return getContinueLoc(); }
explicit ContinueStmt(EmptyShell Empty)
: LoopControlStmt(ContinueStmtClass, Empty) {}
static bool classof(const Stmt *T) {
return T->getStmtClass() == ContinueStmtClass;
}
// Iterators
child_range children() {
return child_range(child_iterator(), child_iterator());
}
const_child_range children() const {
return const_child_range(const_child_iterator(), const_child_iterator());
}
};
/// BreakStmt - This represents a break.
class BreakStmt : public Stmt {
class BreakStmt : public LoopControlStmt {
public:
BreakStmt(SourceLocation BL) : Stmt(BreakStmtClass) {
setBreakLoc(BL);
}
BreakStmt(SourceLocation BL) : LoopControlStmt(BreakStmtClass, BL) {}
BreakStmt(SourceLocation CL, SourceLocation LabelLoc, LabelDecl *Target)
: LoopControlStmt(BreakStmtClass, CL, LabelLoc, Target) {}
/// Build an empty break statement.
explicit BreakStmt(EmptyShell Empty) : Stmt(BreakStmtClass, Empty) {}
SourceLocation getBreakLoc() const { return BreakStmtBits.BreakLoc; }
void setBreakLoc(SourceLocation L) { BreakStmtBits.BreakLoc = L; }
SourceLocation getBeginLoc() const { return getBreakLoc(); }
SourceLocation getEndLoc() const { return getBreakLoc(); }
explicit BreakStmt(EmptyShell Empty)
: LoopControlStmt(BreakStmtClass, Empty) {}
static bool classof(const Stmt *T) {
return T->getStmtClass() == BreakStmtClass;
}
// Iterators
child_range children() {
return child_range(child_iterator(), child_iterator());
}
const_child_range children() const {
return const_child_range(const_child_iterator(), const_child_iterator());
}
};
/// ReturnStmt - This represents a return, optionally of an expression:

View File

@ -255,6 +255,7 @@ public:
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);

View File

@ -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">;

View File

@ -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>;

View File

@ -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.")

View File

@ -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>;

View File

@ -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>,

View File

@ -7213,7 +7213,8 @@ public:
/// '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 @@ public:
///
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 @@ public:
/// 'switch' '(' expression ')' statement
/// [C++] 'switch' '(' condition ')' statement
/// \endverbatim
StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc);
StmtResult ParseSwitchStatement(SourceLocation *TrailingElseLoc,
LabelDecl *PrecedingLabel);
/// ParseWhileStatement
/// \verbatim
@ -7406,7 +7409,8 @@ public:
/// 'while' '(' expression ')' statement
/// [C++] 'while' '(' condition ')' statement
/// \endverbatim
StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc);
StmtResult ParseWhileStatement(SourceLocation *TrailingElseLoc,
LabelDecl *PrecedingLabel);
/// ParseDoStatement
/// \verbatim
@ -7414,7 +7418,7 @@ public:
/// '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 @@ public:
/// [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 @@ public:
/// \verbatim
/// jump-statement:
/// 'continue' ';'
/// [C2y] 'continue' identifier ';'
/// \endverbatim
///
/// Note: this lets the caller parse the end ';'.
@ -7468,6 +7474,7 @@ public:
/// \verbatim
/// jump-statement:
/// 'break' ';'
/// [C2y] 'break' identifier ';'
/// \endverbatim
///
/// Note: this lets the caller parse the end ';'.
@ -7484,9 +7491,12 @@ public:
/// \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);

View File

@ -255,6 +255,10 @@ private:
/// 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 @@ public:
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 @@ public:
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; }

View File

@ -9488,6 +9488,10 @@ public:
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 @@ public:
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;

View File

@ -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) {

View File

@ -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);

View File

@ -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()));

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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";

View File

@ -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)

View File

@ -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());

View File

@ -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());

View File

@ -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(

View File

@ -1552,9 +1552,11 @@ private:
// 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 @@ public:
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);

View File

@ -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"

View File

@ -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 @@ private:
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 @@ Retry:
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 @@ Retry:
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);

View File

@ -99,6 +99,7 @@ void Scope::Init(Scope *parent, unsigned flags) {
UsingDirectives.clear();
Entity = nullptr;
ErrorTrap.reset();
PrecedingLabel = nullptr;
NRVO = std::nullopt;
}

View File

@ -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;
}
//===----------------------------------------------------------------------===//

View File

@ -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,

View File

@ -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>

View File

@ -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);

View File

@ -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;
}

View File

@ -1482,16 +1482,14 @@ public:
}
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;

View File

@ -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: }

View File

@ -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 {{.*}})

View File

@ -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: }

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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>();

View File

@ -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;
}
}
}

View File

@ -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}}
}
}
}

View File

@ -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}}
}

View File

@ -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;
}
}
}
}

View File

@ -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}}
}
}

View File

@ -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);

View File

@ -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}}
};
}
}

View File

@ -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}}
};
}
}

View File

@ -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;
}
}

View File

@ -252,7 +252,7 @@ conformance.</p>
<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>