Skip to content

[C23][Parser] Diagnostic for attribute declaration where statement is required #146224

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,10 @@ Bug Fixes in This Version
- Fixed an infinite recursion when checking constexpr destructors. (#GH141789)
- Fixed a crash when a malformed using declaration appears in a ``constexpr`` function. (#GH144264)
- Fixed a bug when use unicode character name in macro concatenation. (#GH145240)
- In C23, something like [[/*possible attributes*/]]; is an attribute declaration, not a statement. So it is not
allowed by the syntax in places where a statement is required, specifically as the secondary block of a
selection or iteration statement. This differs from C++, since C++ allows declaration statements.
Clang now warning this patterns. (#GH141659)

Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,10 @@ def err_expected_while : Error<"expected 'while' in do/while loop">;

def err_expected_semi_after_stmt : Error<"expected ';' after %0 statement">;
def err_expected_semi_after_expr : Error<"expected ';' after expression">;
def warn_expected_stmt_before_semi_in_secondary_block : Warning<
"expected a statement before ';' but got an attribute declaration, "
"it is not allowed by the syntax in places where a statement is required">,
InGroup<CXXCompat>;
def err_extraneous_token_before_semi : Error<"extraneous '%0' before ';'">;

def err_expected_semi_after_method_proto : Error<
Expand Down
5 changes: 4 additions & 1 deletion clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -7168,13 +7168,16 @@ class Parser : public CodeCompletionHandler {
AllowStandaloneOpenMPDirectives = 0x2,
/// This context is at the top level of a GNU statement expression.
InStmtExpr = 0x4,
/// This context is the C99 secondary-block in selection or iteration
/// statement.
SecondaryBlockInC = 0x8,

/// The context of a regular substatement.
SubStmt = 0,
/// The context of a compound-statement.
Compound = AllowDeclarationsInC | AllowStandaloneOpenMPDirectives,

LLVM_MARK_AS_BITMASK_ENUM(InStmtExpr)
LLVM_MARK_AS_BITMASK_ENUM(SecondaryBlockInC)
};

/// Act on an expression statement that might be the last statement in a
Expand Down
40 changes: 30 additions & 10 deletions clang/lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ Parser::ParseStatementOrDeclaration(StmtVector &Stmts,
// at the start of the statement. Thus, we're not using MaybeParseAttributes
// here because we don't want to allow arbitrary orderings.
ParsedAttributes CXX11Attrs(AttrFactory);
MaybeParseCXX11Attributes(CXX11Attrs, /*MightBeObjCMessageSend*/ true);
bool HasStdAttr =
MaybeParseCXX11Attributes(CXX11Attrs, /*MightBeObjCMessageSend*/ true);
ParsedAttributes GNUOrMSAttrs(AttrFactory);
if (getLangOpts().OpenCL)
MaybeParseGNUAttributes(GNUOrMSAttrs);
Expand All @@ -80,6 +81,12 @@ Parser::ParseStatementOrDeclaration(StmtVector &Stmts,
assert((CXX11Attrs.empty() || Res.isInvalid() || Res.isUsable()) &&
"attributes on empty statement");

if (HasStdAttr && getLangOpts().C23 &&
(StmtCtx & ParsedStmtContext::SecondaryBlockInC) != ParsedStmtContext{} &&
isa_and_present<NullStmt>(Res.get()))
Diag(Res.get()->getBeginLoc(),
diag::warn_expected_stmt_before_semi_in_secondary_block);

if (CXX11Attrs.empty() || Res.isInvalid())
return Res;

Expand Down Expand Up @@ -1491,6 +1498,10 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) {

SourceLocation InnerStatementTrailingElseLoc;
StmtResult ThenStmt;
ParsedStmtContext StmtCtx = getLangOpts().C99
? ParsedStmtContext::SecondaryBlockInC
: ParsedStmtContext::SubStmt;

{
bool ShouldEnter = ConstexprCondition && !*ConstexprCondition;
Sema::ExpressionEvaluationContext Context =
Expand All @@ -1503,7 +1514,7 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) {
EnterExpressionEvaluationContext PotentiallyDiscarded(
Actions, Context, nullptr,
Sema::ExpressionEvaluationContextRecord::EK_Other, ShouldEnter);
ThenStmt = ParseStatement(&InnerStatementTrailingElseLoc);
ThenStmt = ParseStatement(&InnerStatementTrailingElseLoc, StmtCtx);
}

if (Tok.isNot(tok::kw_else))
Expand Down Expand Up @@ -1548,7 +1559,7 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) {
EnterExpressionEvaluationContext PotentiallyDiscarded(
Actions, Context, nullptr,
Sema::ExpressionEvaluationContextRecord::EK_Other, ShouldEnter);
ElseStmt = ParseStatement();
ElseStmt = ParseStatement(/*TrailingElseLoc=*/nullptr, StmtCtx);

if (ElseStmt.isUsable())
MIChecker.Check();
Expand Down Expand Up @@ -1684,8 +1695,11 @@ StmtResult Parser::ParseSwitchStatement(SourceLocation *TrailingElseLoc) {
if (C99orCXX)
getCurScope()->decrementMSManglingNumber();

ParsedStmtContext StmtCtx = getLangOpts().C99
? ParsedStmtContext::SecondaryBlockInC
: ParsedStmtContext::SubStmt;
// Read the body statement.
StmtResult Body(ParseStatement(TrailingElseLoc));
StmtResult Body(ParseStatement(TrailingElseLoc, StmtCtx));

// Pop the scopes.
InnerScope.Exit();
Expand Down Expand Up @@ -1754,9 +1768,11 @@ StmtResult Parser::ParseWhileStatement(SourceLocation *TrailingElseLoc) {
ParseScope InnerScope(this, Scope::DeclScope, C99orCXX, Tok.is(tok::l_brace));

MisleadingIndentationChecker MIChecker(*this, MSK_while, WhileLoc);

ParsedStmtContext StmtCtx = getLangOpts().C99
? ParsedStmtContext::SecondaryBlockInC
: ParsedStmtContext::SubStmt;
// Read the body statement.
StmtResult Body(ParseStatement(TrailingElseLoc));
StmtResult Body(ParseStatement(TrailingElseLoc, StmtCtx));

if (Body.isUsable())
MIChecker.Check();
Expand Down Expand Up @@ -1799,9 +1815,11 @@ StmtResult Parser::ParseDoStatement() {
//
bool C99orCXX = getLangOpts().C99 || getLangOpts().CPlusPlus;
ParseScope InnerScope(this, Scope::DeclScope, C99orCXX, Tok.is(tok::l_brace));

ParsedStmtContext StmtCtx = getLangOpts().C99
? ParsedStmtContext::SecondaryBlockInC
: ParsedStmtContext::SubStmt;
// Read the body statement.
StmtResult Body(ParseStatement());
StmtResult Body(ParseStatement(/*TrailingElseLoc=*/nullptr, StmtCtx));

// Pop the body scope if needed.
InnerScope.Exit();
Expand Down Expand Up @@ -2221,9 +2239,11 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) {
getCurScope()->decrementMSManglingNumber();

MisleadingIndentationChecker MIChecker(*this, MSK_for, ForLoc);

ParsedStmtContext StmtCtx = getLangOpts().C99
? ParsedStmtContext::SecondaryBlockInC
: ParsedStmtContext::SubStmt;
// Read the body statement.
StmtResult Body(ParseStatement(TrailingElseLoc));
StmtResult Body(ParseStatement(TrailingElseLoc, StmtCtx));

if (Body.isUsable())
MIChecker.Check();
Expand Down
30 changes: 30 additions & 0 deletions clang/test/Parser/statements.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s -Wno-unreachable-code
// RUN: %clang_cc1 -std=c23 -fsyntax-only -verify %s -Wno-unreachable-code

void test1(void) {
{ ; { ;;}} ;;
Expand Down Expand Up @@ -77,3 +78,32 @@ int test9(void) {

return 4, // expected-error {{expected ';' after return statement}}
}

#if __STDC_VERSION__ >= 202311L
void attr_decl_in_selection_statement(int n) {
if (1)
[[]]; // expected-warning {{expected a statement before ';' but got an attribute declaration, it is not allowed by the syntax in places where a statement is required}}

if (1) {

} else
[[]]; // expected-warning {{expected a statement before ';' but got an attribute declaration, it is not allowed by the syntax in places where a statement is required}}


switch (n)
[[]]; // expected-warning {{expected a statement before ';' but got an attribute declaration, it is not allowed by the syntax in places where a statement is required}}
}

void attr_decl_in_iteration_statement(int n) {
int i;
for (i = 0; i < n; ++i)
[[]]; // expected-warning {{expected a statement before ';' but got an attribute declaration, it is not allowed by the syntax in places where a statement is required}}

while (i > 0)
[[]]; // expected-warning {{expected a statement before ';' but got an attribute declaration, it is not allowed by the syntax in places where a statement is required}}

do
[[]]; // expected-warning {{expected a statement before ';' but got an attribute declaration, it is not allowed by the syntax in places where a statement is required}}
while (i > 0);
}
#endif // __STDC_VERSION__ >= 202311L
17 changes: 11 additions & 6 deletions clang/test/Sema/c2x-fallthrough.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_cc1 -fsyntax-only -std=c2x -verify %s
// RUN: %clang_cc1 -fsyntax-only -std=c23 -verify %s

// This is the latest version of fallthrough that we support.
_Static_assert(__has_c_attribute(fallthrough) == 201910L);
Expand All @@ -16,17 +16,22 @@ void f(int n) {
}
case 2:
for (int n = 0; n != 10; ++n)
[[fallthrough]]; // expected-error {{does not directly precede switch label}}
[[fallthrough]]; // expected-error {{does not directly precede switch label}} \
// expected-warning {{expected a statement before ';' but got an attribute declaration, it is not allowed by the syntax in places where a statement is required}}
case 3:
while (1)
[[fallthrough]]; // expected-error {{does not directly precede switch label}}
[[fallthrough]]; // expected-error {{does not directly precede switch label}} \
// expected-warning {{expected a statement before ';' but got an attribute declaration, it is not allowed by the syntax in places where a statement is required}}
case 4:
while (0)
[[fallthrough]]; // expected-error {{does not directly precede switch label}}
[[fallthrough]]; // expected-error {{does not directly precede switch label}} \
// expected-warning {{expected a statement before ';' but got an attribute declaration, it is not allowed by the syntax in places where a statement is required}}
case 5:
do [[fallthrough]]; while (1); // expected-error {{does not directly precede switch label}}
do [[fallthrough]]; while (1); // expected-error {{does not directly precede switch label}} \
// expected-warning {{expected a statement before ';' but got an attribute declaration, it is not allowed by the syntax in places where a statement is required}}
case 6:
do [[fallthrough]]; while (0); // expected-error {{does not directly precede switch label}}
do [[fallthrough]]; while (0); // expected-error {{does not directly precede switch label}} \
// expected-warning {{expected a statement before ';' but got an attribute declaration, it is not allowed by the syntax in places where a statement is required}}
case 7:
switch (n) {
case 0:
Expand Down
Loading