[C] Disallow declarations where a statement is required (#92908)

This fixes a regression introduced in
8bd06d5b65845e5e01dd899a2deb773580460b89 where Clang began to accept a
declaration where a statement is required. e.g.,
```
if (1)
  int x; // Previously accepted, now properly rejected
```

Fixes #92775
This commit is contained in:
Aaron Ballman 2024-05-28 14:55:18 -04:00 committed by GitHub
parent d33864d5d8
commit 5901d4005f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 63 additions and 7 deletions

View File

@ -632,6 +632,9 @@ Bug Fixes in This Version
- ``__is_array`` and ``__is_bounded_array`` no longer return ``true`` for
zero-sized arrays. Fixes (#GH54705).
- Correctly reject declarations where a statement is required in C.
Fixes #GH92775
Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -467,15 +467,18 @@ class Parser : public CodeCompletionHandler {
/// Flags describing a context in which we're parsing a statement.
enum class ParsedStmtContext {
/// This context permits declarations in language modes where declarations
/// are not statements.
AllowDeclarationsInC = 0x1,
/// This context permits standalone OpenMP directives.
AllowStandaloneOpenMPDirectives = 0x1,
AllowStandaloneOpenMPDirectives = 0x2,
/// This context is at the top level of a GNU statement expression.
InStmtExpr = 0x2,
InStmtExpr = 0x4,
/// The context of a regular substatement.
SubStmt = 0,
/// The context of a compound-statement.
Compound = AllowStandaloneOpenMPDirectives,
Compound = AllowDeclarationsInC | AllowStandaloneOpenMPDirectives,
LLVM_MARK_AS_BITMASK_ENUM(InStmtExpr)
};

View File

@ -239,7 +239,15 @@ Retry:
auto IsStmtAttr = [](ParsedAttr &Attr) { return Attr.isStmtAttr(); };
bool AllAttrsAreStmtAttrs = llvm::all_of(CXX11Attrs, IsStmtAttr) &&
llvm::all_of(GNUAttrs, IsStmtAttr);
if (((GNUAttributeLoc.isValid() && !(HaveAttrs && AllAttrsAreStmtAttrs)) ||
// In C, the grammar production for statement (C23 6.8.1p1) does not allow
// for declarations, which is different from C++ (C++23 [stmt.pre]p1). So
// in C++, we always allow a declaration, but in C we need to check whether
// we're in a statement context that allows declarations. e.g., in C, the
// following is invalid: if (1) int x;
if ((getLangOpts().CPlusPlus || getLangOpts().MicrosoftExt ||
(StmtCtx & ParsedStmtContext::AllowDeclarationsInC) !=
ParsedStmtContext()) &&
((GNUAttributeLoc.isValid() && !(HaveAttrs && AllAttrsAreStmtAttrs)) ||
isDeclarationStatement())) {
SourceLocation DeclStart = Tok.getLocation(), DeclEnd;
DeclGroupPtrTy Decl;

View File

@ -18,8 +18,9 @@
enum {a, b};
void different(void) {
if (sizeof(enum {b, a}) != sizeof(int))
if (sizeof(enum {b, a}) != sizeof(int)) {
_Static_assert(a == 1, "");
}
/* In C89, the 'b' found here would have been from the enum declaration in
* the controlling expression of the selection statement, not from the global
* declaration. In C99 and later, that enumeration is scoped to the 'if'

39
clang/test/Parser/decls.c Normal file
View File

@ -0,0 +1,39 @@
// RUN: %clang_cc1 %s -fsyntax-only -verify -pedantic
// Test that we can parse declarations at global scope.
int v;
void func(void) {
// Test that we can parse declarations within a compound statement.
int a;
{
int b;
}
int z = ({ // expected-warning {{use of GNU statement expression extension}}
// Test that we can parse declarations within a GNU statement expression.
int w = 12;
w;
});
// Test that we diagnose declarations where a statement is required.
// See GH92775.
if (1)
int x; // expected-error {{expected expression}}
for (;;)
int c; // expected-error {{expected expression}}
label:
int y; // expected-warning {{label followed by a declaration is a C23 extension}}
// Test that lookup works as expected.
(void)a;
(void)v;
(void)z;
(void)b; // expected-error {{use of undeclared identifier 'b'}}
(void)w; // expected-error {{use of undeclared identifier 'w'}}
(void)x; // expected-error {{use of undeclared identifier 'x'}}
(void)c; // expected-error {{use of undeclared identifier 'c'}}
(void)y;
}

View File

@ -33,9 +33,11 @@ int foo3;
void func() {
// FIXME: Should we disallow this on declarations, or consider this to be on
// the initialization?
// the initialization? This is currently rejected in C because
// Parser::ParseOpenACCDirectiveStmt() calls ParseStatement() and passes the
// statement context as "SubStmt" which does not allow for a declaration in C.
#pragma acc parallel
int foo;
int foo; // expected-error {{expected expression}}
#pragma acc parallel
{