Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3720,6 +3720,12 @@ def err_expansion_stmt_incomplete : Error<
"cannot expand expression of incomplete type %0">;
def err_expansion_stmt_lambda : Error<
"cannot expand lambda closure type">;
def err_expansion_stmt_case : Error<
"%select{'case'|'default'}0 belongs to 'switch' outside enclosing expansion statement">;
def note_enclosing_switch_statement_here : Note<
"switch statement is here">;
def err_expansion_stmt_label : Error<
"labels are not allowed in expansion statements">;

def err_attribute_patchable_function_entry_invalid_section
: Error<"section argument to 'patchable_function_entry' attribute is not "
Expand Down
6 changes: 5 additions & 1 deletion clang/include/clang/Sema/ScopeInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,11 @@ class FunctionScopeInfo {
public:
/// A SwitchStmt, along with a flag indicating if its list of case statements
/// is incomplete (because we dropped an invalid one while parsing).
using SwitchInfo = llvm::PointerIntPair<SwitchStmt*, 1, bool>;
struct SwitchInfo : llvm::PointerIntPair<SwitchStmt *, 1, bool> {
DeclContext *EnclosingDC;
SwitchInfo(SwitchStmt *Switch, DeclContext *DC)
: PointerIntPair(Switch, false), EnclosingDC(DC) {}
};

/// SwitchStack - This is the current set of active switch statements in the
/// block.
Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -9531,7 +9531,8 @@ class Sema final : public SemaBase {
/// of an __label__ label name, otherwise it is a normal label definition
/// or use.
LabelDecl *LookupOrCreateLabel(IdentifierInfo *II, SourceLocation IdentLoc,
SourceLocation GnuLabelLoc = SourceLocation());
SourceLocation GnuLabelLoc = SourceLocation(),
bool ForLabelStmt = false);

/// Perform a name lookup for a label with the specified name; this does not
/// create a new label if the lookup fails.
Expand Down
11 changes: 9 additions & 2 deletions clang/lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -744,8 +744,9 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
// identifier ':' statement
SourceLocation ColonLoc = ConsumeToken();

LabelDecl *LD = Actions.LookupOrCreateLabel(IdentTok.getIdentifierInfo(),
IdentTok.getLocation());
LabelDecl *LD = Actions.LookupOrCreateLabel(
IdentTok.getIdentifierInfo(), IdentTok.getLocation(), /*GnuLabelLoc=*/{},
/*ForLabelStmt=*/true);

// Read label attributes, if present.
StmtResult SubStmt;
Expand Down Expand Up @@ -789,6 +790,12 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,

DiagnoseLabelFollowedByDecl(*this, SubStmt.get());

// If a label cannot appear here, just return the underlying statement.
if (!LD) {
Attrs.clear();
return SubStmt.get();
}

Actions.ProcessDeclAttributeList(Actions.CurScope, LD, Attrs);
Attrs.clear();

Expand Down
47 changes: 38 additions & 9 deletions clang/lib/Sema/SemaLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4466,7 +4466,8 @@ LabelDecl *Sema::LookupExistingLabel(IdentifierInfo *II, SourceLocation Loc) {
}

LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
SourceLocation GnuLabelLoc) {
SourceLocation GnuLabelLoc,
bool ForLabelStmt) {
if (GnuLabelLoc.isValid()) {
// Local label definitions always shadow existing labels.
auto *Res = LabelDecl::Create(Context, CurContext, Loc, II, GnuLabelLoc);
Expand All @@ -4475,15 +4476,43 @@ LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
return cast<LabelDecl>(Res);
}

// Not a GNU local label.
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);
Scope *S = CurScope->getFnParent();
assert(S && "Not in a function?");
PushOnScopeChains(Res, S, true);
LabelDecl *Existing = LookupExistingLabel(II, Loc);

// C++26 [stmt.label]p4 An identifier label shall not be enclosed by an
// expansion-statement.
//
// As an extension, we allow GNU local labels since they are logically
// scoped to the containing block, which prevents us from ending up with
// multiple copies of the same label in a function after instantiation.
//
// While allowing this is slightly more complicated, it also has the nice
// side-effect of avoiding otherwise rather horrible diagnostics you'd get
// when trying to use '__label__' if we didn't support this.
if (ForLabelStmt && CurContext->isExpansionStmt()) {
if (Existing && Existing->isGnuLocal())
return Existing;

// Drop the label from the AST as creating it anyway would cause us to
// either issue various unhelpful diagnostics (if we were to declare
// it in the function decl context) or shadow a valid label with the
// same name outside the expansion statement.
Diag(Loc, diag::err_expansion_stmt_label);
return nullptr;
}

if (Existing)
return Existing;

// Declare non-local labels outside any expansion statements; this is required
// to support jumping out of an expansion statement.
ContextRAII Ctx{*this, CurContext->getEnclosingNonExpansionStatementContext(),
/*NewThisContext=*/false};

// Not a GNU local label. Create the backing decl.
auto *Res = LabelDecl::Create(Context, CurContext, Loc, II);
Scope *S = CurScope->getFnParent();
assert(S && "Not in a function?");
PushOnScopeChains(Res, S, true);
return Res;
}

Expand Down
30 changes: 28 additions & 2 deletions clang/lib/Sema/SemaStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,25 @@ Sema::ActOnCaseExpr(SourceLocation CaseLoc, ExprResult Val) {
return CheckAndFinish(Val.get());
}

static bool DiagnoseSwitchCaseInExpansionStmt(Sema &S, SourceLocation KwLoc,
bool IsDefault) {
// C++26 [stmt.expand] The compound-statement of an expansion-statement is a
// control-flow-limited statement.
//
// We diagnose this here rather than in JumpDiagnostics because those run
// after the expansion statement is instantiated, at which point we will have
// have already complained about duplicate case labels, which is not exactly
// great QOI.
if (S.CurContext->isExpansionStmt() &&
S.getCurFunction()->SwitchStack.back().EnclosingDC != S.CurContext) {
S.Diag(KwLoc, diag::err_expansion_stmt_case) << IsDefault;
S.Diag(S.getCurFunction()->SwitchStack.back().getPointer()->getSwitchLoc(),
diag::note_enclosing_switch_statement_here);
return true;
}
return false;
}

StmtResult
Sema::ActOnCaseStmt(SourceLocation CaseLoc, ExprResult LHSVal,
SourceLocation DotDotDotLoc, ExprResult RHSVal,
Expand All @@ -547,6 +566,9 @@ Sema::ActOnCaseStmt(SourceLocation CaseLoc, ExprResult LHSVal,
return StmtError();
}

if (DiagnoseSwitchCaseInExpansionStmt(*this, CaseLoc, false))
return StmtError();

if (LangOpts.OpenACC &&
getCurScope()->isInOpenACCComputeConstructScope(Scope::SwitchScope)) {
Diag(CaseLoc, diag::err_acc_branch_in_out_compute_construct)
Expand All @@ -572,6 +594,9 @@ Sema::ActOnDefaultStmt(SourceLocation DefaultLoc, SourceLocation ColonLoc,
return SubStmt;
}

if (DiagnoseSwitchCaseInExpansionStmt(*this, DefaultLoc, true))
return StmtError();

if (LangOpts.OpenACC &&
getCurScope()->isInOpenACCComputeConstructScope(Scope::SwitchScope)) {
Diag(DefaultLoc, diag::err_acc_branch_in_out_compute_construct)
Expand Down Expand Up @@ -1196,8 +1221,9 @@ StmtResult Sema::ActOnStartOfSwitchStmt(SourceLocation SwitchLoc,

auto *SS = SwitchStmt::Create(Context, InitStmt, Cond.get().first, CondExpr,
LParenLoc, RParenLoc);
SS->setSwitchLoc(SwitchLoc);
getCurFunction()->SwitchStack.push_back(
FunctionScopeInfo::SwitchInfo(SS, false));
FunctionScopeInfo::SwitchInfo(SS, CurContext));
return SS;
}

Expand Down Expand Up @@ -1313,7 +1339,7 @@ Sema::ActOnFinishSwitchStmt(SourceLocation SwitchLoc, Stmt *Switch,
BodyStmt = new (Context) NullStmt(BodyStmt->getBeginLoc());
}

SS->setBody(BodyStmt, SwitchLoc);
SS->setBody(BodyStmt);

Expr *CondExpr = SS->getCond();
if (!CondExpr) return StmtError();
Expand Down
117 changes: 117 additions & 0 deletions clang/test/SemaCXX/cxx2c-expansion-stmts-control-flow.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fblocks -verify

void g(int);

void label() {
template for (auto x : {1, 2}) {
invalid1:; // expected-error {{labels are not allowed in expansion statements}}
invalid2:; // expected-error {{labels are not allowed in expansion statements}}
goto invalid1; // expected-error {{use of undeclared label 'invalid1'}}
}

template for (auto x : {1, 2}) {
(void) [] {
template for (auto x : {1, 2}) {
invalid3:; // expected-error {{labels are not allowed in expansion statements}}
}
ok:;
};

(void) ^{
template for (auto x : {1, 2}) {
invalid4:; // expected-error {{labels are not allowed in expansion statements}}
}
ok:;
};

struct X {
void f() {
ok:;
}
};
}

// GNU local labels are allowed.
template for (auto x : {1, 2}) {
__label__ a;
if (x == 1) goto a;
a:;
if (x == 1) goto a;
}

// Likewise, jumping *out* of an expansion statement is fine.
template for (auto x : {1, 2}) {
if (x == 1) goto lbl;
g(x);
}
lbl:;
template for (auto x : {1, 2}) {
if (x == 1) goto lbl;
g(x);
}

// Jumping into one is not possible, as local labels aren't visible
// outside the block that declares them, and non-local labels are invalid.
goto exp1; // expected-error {{use of undeclared label 'exp1'}}
goto exp3; // expected-error {{use of undeclared label 'exp3'}}
template for (auto x : {1, 2}) {
__label__ exp1, exp2;
exp1:;
exp2:;
exp3:; // expected-error {{labels are not allowed in expansion statements}}
}
goto exp2; // expected-error {{use of undeclared label 'exp2'}}

// Allow jumping from inside an expansion statement to a local label in
// one of its parents.
out1:;
template for (auto x : {1, 2}) {
__label__ x, y;
x:
goto out1;
goto out2;
template for (auto x : {3, 4}) {
goto x;
goto y;
goto out1;
goto out2;
}
y:
}
out2:;
}


void case_default(int i) {
switch (i) { // expected-note 3 {{switch statement is here}}
template for (auto x : {1, 2}) {
case 1:; // expected-error {{'case' belongs to 'switch' outside enclosing expansion statement}}
template for (auto x : {1, 2}) {
case 2:; // expected-error {{'case' belongs to 'switch' outside enclosing expansion statement}}
}
default: // expected-error {{'default' belongs to 'switch' outside enclosing expansion statement}}
switch (i) { // expected-note {{switch statement is here}}
case 3:;
default:
template for (auto x : {1, 2}) {
case 4:; // expected-error {{'case' belongs to 'switch' outside enclosing expansion statement}}
}
}
}
}

template for (auto x : {1, 2}) {
switch (i) {
case 1:;
default:
}
}

// Ensure that we diagnose this even if the statements would be discarded.
switch (i) { // expected-note 2 {{switch statement is here}}
template for (auto x : {}) {
case 1:; // expected-error {{'case' belongs to 'switch' outside enclosing expansion statement}}
default:; // expected-error {{'default' belongs to 'switch' outside enclosing expansion statement}}
}
}
}
Loading