https://github.com/akash-manna-sky updated https://github.com/llvm/llvm-project/pull/224682
>From f3a27d8cb00ca5e83a5eeb971c619a7af059919a Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Fri, 18 Sep 2026 21:59:16 +0530 Subject: [PATCH 1/5] [Clang] Don't make a statement expression invalid when its last statement is dropped without an error A declaration that declares nothing, such as `__typeof__(e);`, is only a warning, but it produces no Decl and so ActOnDeclStmt returns StmtError(). Since cad09404cc80 the parser turned any invalid last statement of a `({ ... })` into StmtError() for the whole body, which became an ExprError with no diagnostic. Depending on where the statement expression was used, it was either silently dropped from the AST (deleting side effects like `({ foo(); __typeof__(e); })`) or replaced by a RecoveryExpr in an `if` condition that then reached CodeGen and asserted in EvaluateAsInt. Instead of failing, append a null statement in place of the dropped one. The statement expression is built with type void, which is also what GCC does, and the previous statement can no longer be mistaken for the value, which is what cad09404cc80 was guarding against. Fixes #215454 --- clang/docs/ReleaseNotes.md | 2 ++ clang/lib/Parse/ParseStmt.cpp | 20 +++++++++++--------- clang/test/CodeGen/GH215454.c | 32 ++++++++++++++++++++++++++++++++ clang/test/SemaCXX/gh113468.cpp | 2 +- 4 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 clang/test/CodeGen/GH215454.c diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index f4a34a37aff52e..fe279a88cd9425 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -547,6 +547,8 @@ features cannot lower the translation-unit ABI level; - Fixed a crash when an `asm` label names the register for a global variable of incomplete type. (#GH219746) - Fixed an ICE hat occurred when using `__imag int/float` as lvalue in assignment. (#GH119498) - Fixed an assertion failure in `-Wsign-compare` when a negated or complemented vector of unsigned integers was compared against a signed constant. (#GH203575) +- Fixed a crash in code generation and silently dropped side effects when the last statement of a GNU statement + expression is a declaration that declares nothing, such as `({ f(); __typeof__(x); })`. (#GH215454) #### Bug Fixes to Compiler Builtins diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 14bea6a1a79487..15791273e71f73 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -1185,7 +1185,7 @@ StmtResult Parser::ParseCompoundStatementBody(bool isStmtExpr) { ParsedStmtContext::Compound | (isStmtExpr ? ParsedStmtContext::InStmtExpr : ParsedStmtContext()); - bool LastIsError = false; + bool LastIsInvalid = false; while (!tryParseMisplacedModuleImport() && Tok.isNot(tok::r_brace) && Tok.isNot(tok::eof)) { if (Tok.is(tok::annot_pragma_unused)) { @@ -1242,14 +1242,16 @@ StmtResult Parser::ParseCompoundStatementBody(bool isStmtExpr) { if (R.isUsable()) Stmts.push_back(R.get()); - LastIsError = R.isInvalid(); - } - // StmtExpr needs to do copy initialization for last statement. - // If last statement is invalid, the last statement in `Stmts` will be - // incorrect. Then the whole compound statement should also be marked as - // invalid to prevent subsequent errors. - if (isStmtExpr && LastIsError && !Stmts.empty()) - return StmtError(); + LastIsInvalid = R.isInvalid(); + } + // The last statement of a statement expression is its value and was already + // copy-initialized when parsed. If it was dropped, the statement now at the + // end must not become the value, so replace the dropped one with a null + // statement. Don't return StmtError here: an invalid statement does not + // imply an error was diagnosed (e.g. `__typeof__(x);` only warns), and an + // undiagnosed ExprError silently drops the statement expression. + if (isStmtExpr && LastIsInvalid) + Stmts.push_back(Actions.ActOnNullStmt(PrevTokLocation).get()); // Warn the user that using option `-ffp-eval-method=source` on a // 32-bit target and feature `sse` disabled, or using diff --git a/clang/test/CodeGen/GH215454.c b/clang/test/CodeGen/GH215454.c new file mode 100644 index 00000000000000..aceba1d41474a4 --- /dev/null +++ b/clang/test/CodeGen/GH215454.c @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -std=gnu99 -verify -emit-llvm-only %s +// RUN: %clang_cc1 -std=gnu99 -DCODEGEN -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s + +// A declaration that declares nothing as the last statement of a statement +// expression made the whole statement expression invalid without an error, +// which dropped the call below or crashed CodeGen on a RecoveryExpr. + +void foo(void); + +// CHECK-LABEL: define{{.*}} void @keeps_side_effects( +// CHECK: call void @foo() +void keeps_side_effects(int e) { + ({ foo(); __typeof__(e); }); // expected-warning {{declaration does not declare anything}} +} + +#ifndef CODEGEN +void d2(int e) { + if (({ ; __typeof__(e); })) {} // expected-warning {{declaration does not declare anything}} \ + // expected-error {{statement requires expression of scalar type ('void' invalid)}} +} + +// Reproducer from the issue; the unclosed '({' makes recovery run to EOF. +#define c(a, b) \ + {;__typeof__(b);} +void d(int e) {if((c(, e);); // expected-warning {{'(' and '{' tokens introducing statement expression appear in different macro expansion contexts}} \ + // expected-note {{'{' token is here}} \ + // expected-warning {{declaration does not declare anything}} \ + // expected-error {{unexpected ';' before ')'}} \ + // expected-note {{to match this '{'}} +} // expected-error {{expected expression}} \ + // expected-error@+2 {{expected '}'}} +#endif diff --git a/clang/test/SemaCXX/gh113468.cpp b/clang/test/SemaCXX/gh113468.cpp index 94551986b0efaa..f4252abd59c443 100644 --- a/clang/test/SemaCXX/gh113468.cpp +++ b/clang/test/SemaCXX/gh113468.cpp @@ -1,7 +1,7 @@ // RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s constexpr int expr() { - if (({ + if (({ // expected-error {{value of type 'void' is not contextually convertible to 'bool'}} int f; f = 0; if (f) >From f2a1f064609a6acf142fa5e541fbd8013e94d859 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Tue, 22 Sep 2026 00:01:40 +0530 Subject: [PATCH 2/5] [Clang] Don't treat a declaration that declares nothing as an invalid statement A declaration with no declarator, such as `int;` or `__typeof__(e);`, is accepted with a warning, but it produces no Decl and ActOnDeclStmt turned the empty group into StmtError() without any diagnostic. Since cad09404cc80 a statement expression whose last statement is invalid is itself invalid, so `({ foo(); __typeof__(e); })` became an undiagnosed ExprError: it was dropped from the AST along with the call, or, as an `if` condition, replaced by a RecoveryExpr that reached CodeGen and asserted in EvaluateAsInt. In C++ the same thing dropped an entire `if (foo(), e) int;`. ActOnDeclStmt now returns StmtEmpty() for an empty group. The two callers that parse a declaration in statement position turn that into a null statement, because ParseStatement() skips unset results (that is how a pragma in statement position works) and would otherwise take the following statement as the substatement. All other callers already treat a null result as "no declaration". Fixes #215454 --- clang/docs/ReleaseNotes.md | 5 +++-- clang/lib/Parse/ParseStmt.cpp | 30 ++++++++++++++++++------------ clang/lib/Sema/SemaStmt.cpp | 6 ++++-- clang/test/CodeGen/GH215454.c | 7 ++++--- clang/test/CodeGenCXX/GH215454.cpp | 13 +++++++++++++ clang/test/SemaCXX/gh113468.cpp | 2 +- 6 files changed, 43 insertions(+), 20 deletions(-) create mode 100644 clang/test/CodeGenCXX/GH215454.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index fe279a88cd9425..113012275bbdd9 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -547,8 +547,9 @@ features cannot lower the translation-unit ABI level; - Fixed a crash when an `asm` label names the register for a global variable of incomplete type. (#GH219746) - Fixed an ICE hat occurred when using `__imag int/float` as lvalue in assignment. (#GH119498) - Fixed an assertion failure in `-Wsign-compare` when a negated or complemented vector of unsigned integers was compared against a signed constant. (#GH203575) -- Fixed a crash in code generation and silently dropped side effects when the last statement of a GNU statement - expression is a declaration that declares nothing, such as `({ f(); __typeof__(x); })`. (#GH215454) +- A declaration that declares nothing, such as `int;` or `__typeof__(x);`, used as a statement was treated as an + error without any diagnostic. This silently dropped the enclosing statement expression or `if` statement, and + could crash code generation when such a statement expression was used as an `if` condition. (#GH215454) #### Bug Fixes to Compiler Builtins diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 15791273e71f73..6bdde6274af351 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -231,7 +231,12 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( GNUAttrs.Range.getBegin()); } else if (GNUAttrs.Range.getBegin().isValid()) DeclStart = GNUAttrs.Range.getBegin(); - return Actions.ActOnDeclStmt(Decl, DeclStart, DeclEnd); + StmtResult R = Actions.ActOnDeclStmt(Decl, DeclStart, DeclEnd); + // A declaration that declares nothing (`int;`) still occupies the + // statement position; unlike a pragma, ParseStatement() must not skip it. + if (R.isUnset()) + return Actions.ActOnNullStmt(PrevTokLocation); + return R; } if (Tok.is(tok::r_brace)) { @@ -1185,7 +1190,7 @@ StmtResult Parser::ParseCompoundStatementBody(bool isStmtExpr) { ParsedStmtContext::Compound | (isStmtExpr ? ParsedStmtContext::InStmtExpr : ParsedStmtContext()); - bool LastIsInvalid = false; + bool LastIsError = false; while (!tryParseMisplacedModuleImport() && Tok.isNot(tok::r_brace) && Tok.isNot(tok::eof)) { if (Tok.is(tok::annot_pragma_unused)) { @@ -1222,6 +1227,9 @@ StmtResult Parser::ParseCompoundStatementBody(bool isStmtExpr) { DeclGroupPtrTy Res = ParseDeclaration(DeclaratorContext::Block, DeclEnd, attrs, DeclSpecAttrs); R = Actions.ActOnDeclStmt(Res, DeclStart, DeclEnd); + // See ParseStatementOrDeclarationAfterAttributes. + if (R.isUnset()) + R = Actions.ActOnNullStmt(PrevTokLocation); } else { // Otherwise this was a unary __extension__ marker. ExprResult Res(ParseExpressionWithLeadingExtension(ExtLoc)); @@ -1242,16 +1250,14 @@ StmtResult Parser::ParseCompoundStatementBody(bool isStmtExpr) { if (R.isUsable()) Stmts.push_back(R.get()); - LastIsInvalid = R.isInvalid(); - } - // The last statement of a statement expression is its value and was already - // copy-initialized when parsed. If it was dropped, the statement now at the - // end must not become the value, so replace the dropped one with a null - // statement. Don't return StmtError here: an invalid statement does not - // imply an error was diagnosed (e.g. `__typeof__(x);` only warns), and an - // undiagnosed ExprError silently drops the statement expression. - if (isStmtExpr && LastIsInvalid) - Stmts.push_back(Actions.ActOnNullStmt(PrevTokLocation).get()); + LastIsError = R.isInvalid(); + } + // StmtExpr needs to do copy initialization for last statement. + // If last statement is invalid, the last statement in `Stmts` will be + // incorrect. Then the whole compound statement should also be marked as + // invalid to prevent subsequent errors. + if (isStmtExpr && LastIsError && !Stmts.empty()) + return StmtError(); // Warn the user that using option `-ffp-eval-method=source` on a // 32-bit target and feature `sse` disabled, or using diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index 331c1866e36ec4..9e252dcee021f2 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -77,8 +77,10 @@ StmtResult Sema::ActOnDeclStmt(DeclGroupPtrTy dg, SourceLocation StartLoc, SourceLocation EndLoc) { DeclGroupRef DG = dg.get(); - // If we have an invalid decl, just return an error. - if (DG.isNull()) return StmtError(); + // No declarations, so no statement. This is not an error: `int;` only warns, + // and a group emptied by an error was already diagnosed. + if (DG.isNull()) + return StmtEmpty(); return new (Context) DeclStmt(DG, StartLoc, EndLoc); } diff --git a/clang/test/CodeGen/GH215454.c b/clang/test/CodeGen/GH215454.c index aceba1d41474a4..e2b73de7965a57 100644 --- a/clang/test/CodeGen/GH215454.c +++ b/clang/test/CodeGen/GH215454.c @@ -1,9 +1,10 @@ // RUN: %clang_cc1 -std=gnu99 -verify -emit-llvm-only %s +// RUN: %clang_cc1 -std=gnu99 -verify -emit-llvm-only -fno-recovery-ast %s // RUN: %clang_cc1 -std=gnu99 -DCODEGEN -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s -// A declaration that declares nothing as the last statement of a statement -// expression made the whole statement expression invalid without an error, -// which dropped the call below or crashed CodeGen on a RecoveryExpr. +// A declaration that declares nothing made the enclosing statement expression +// invalid without an error: the call below was dropped, and as an 'if' +// condition CodeGen crashed on a RecoveryExpr. void foo(void); diff --git a/clang/test/CodeGenCXX/GH215454.cpp b/clang/test/CodeGenCXX/GH215454.cpp new file mode 100644 index 00000000000000..6da7306b42e8da --- /dev/null +++ b/clang/test/CodeGenCXX/GH215454.cpp @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -verify -emit-llvm -o - %s | FileCheck %s + +void foo(); + +// A declaration that declares nothing as the substatement dropped the whole +// 'if', including the call in its condition. +// CHECK-LABEL: define{{.*}} void @_Z1di( +// CHECK: call void @_Z3foov() +void d(int e) { + if (foo(), e) int; // expected-warning {{declaration does not declare anything}} \ + // expected-warning {{if statement has empty body}} \ + // expected-note {{put the semicolon on a separate line to silence this warning}} +} diff --git a/clang/test/SemaCXX/gh113468.cpp b/clang/test/SemaCXX/gh113468.cpp index f4252abd59c443..94551986b0efaa 100644 --- a/clang/test/SemaCXX/gh113468.cpp +++ b/clang/test/SemaCXX/gh113468.cpp @@ -1,7 +1,7 @@ // RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s constexpr int expr() { - if (({ // expected-error {{value of type 'void' is not contextually convertible to 'bool'}} + if (({ int f; f = 0; if (f) >From fed0a22dbf5ad1a6adb485bad02c99a4812c63a7 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Tue, 22 Sep 2026 00:27:17 +0530 Subject: [PATCH 3/5] [Clang] Test that a declaration declaring nothing stays the if body Pin that `if (e) int; bar();` keeps `bar()` outside the if: the empty declaration becomes a null statement, not something ParseStatement() skips over. --- clang/test/CodeGenCXX/GH215454.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/clang/test/CodeGenCXX/GH215454.cpp b/clang/test/CodeGenCXX/GH215454.cpp index 6da7306b42e8da..0a04ca97dc8812 100644 --- a/clang/test/CodeGenCXX/GH215454.cpp +++ b/clang/test/CodeGenCXX/GH215454.cpp @@ -1,6 +1,7 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -verify -emit-llvm -o - %s | FileCheck %s void foo(); +void bar(); // A declaration that declares nothing as the substatement dropped the whole // 'if', including the call in its condition. @@ -11,3 +12,16 @@ void d(int e) { // expected-warning {{if statement has empty body}} \ // expected-note {{put the semicolon on a separate line to silence this warning}} } + +// The empty declaration is the body; the next statement must not become it. +// CHECK-LABEL: define{{.*}} void @_Z1fi( +// CHECK: if.then: +// CHECK-NEXT: br label %if.end +// CHECK: if.end: +// CHECK-NEXT: call void @_Z3barv() +void f(int e) { + if (e) int; // expected-warning {{declaration does not declare anything}} \ + // expected-warning {{if statement has empty body}} \ + // expected-note {{put the semicolon on a separate line to silence this warning}} + bar(); +} >From 731f3382d86e076611ed784d05558610bf2c2c3e Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Tue, 22 Sep 2026 22:14:50 +0530 Subject: [PATCH 4/5] [Clang] Update OpenACC test for empty declarations becoming null statements `using j;` is an error and yields no declaration, which is now a null statement rather than an invalid one, so 'acc loop' correctly reports that it was not applied to a 'for' loop. --- clang/test/ParserOpenACC/directive-scope-setup.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang/test/ParserOpenACC/directive-scope-setup.cpp b/clang/test/ParserOpenACC/directive-scope-setup.cpp index 8fdc3cc9ac4fad..34dfb70aa1bba8 100644 --- a/clang/test/ParserOpenACC/directive-scope-setup.cpp +++ b/clang/test/ParserOpenACC/directive-scope-setup.cpp @@ -3,10 +3,12 @@ void func() { #pragma acc parallel using i; // expected-error{{using declaration requires a qualified name}} -#pragma acc loop - using j; // expected-error{{using declaration requires a qualified name}} -#pragma acc parallel loop - using k; // expected-error{{using declaration requires a qualified name}} +#pragma acc loop // expected-note{{'loop' construct is here}} + using j; // expected-error{{using declaration requires a qualified name}} \ + expected-error{{OpenACC 'loop' construct can only be applied to a 'for' loop}} +#pragma acc parallel loop // expected-note{{'parallel loop' construct is here}} + using k; // expected-error{{using declaration requires a qualified name}} \ + expected-error{{OpenACC 'parallel loop' construct can only be applied to a 'for' loop}} #pragma acc data default(none) using l; // expected-error{{using declaration requires a qualified name}} } >From 14ab779e3821e84a4888ed102939cde09732cf5b Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Tue, 22 Sep 2026 22:19:02 +0530 Subject: [PATCH 5/5] [Clang] Move GH215454 release note away from the list tail to avoid conflict --- clang/docs/ReleaseNotes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 113012275bbdd9..bcc4af7ec1ef90 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -537,6 +537,9 @@ features cannot lower the translation-unit ABI level; - Fixed a crash when checking scalar type with excess braces. (#GH69213), (#GH137845), (#GH198767), (#GH207566), (#GH106180) - Fixed an assertion crash when instantiating a nested requirement with an invalid constraint. (#GH213575) - Clang now defines the GCC-compatible predefined macro `__SIG_ATOMIC_TYPE__`. (#GH213895) +- A declaration that declares nothing, such as `int;` or `__typeof__(x);`, used as a statement was treated as an + error without any diagnostic. This silently dropped the enclosing statement expression or `if` statement, and + could crash code generation when such a statement expression was used as an `if` condition. (#GH215454) - Fixed IEEE f128 complex mul/div using the IBM f128 libcalls on powerpc. (#GH216820) - Fixed an ICE that occurred when a structured binding pack is expanded outside the lambda where it was declared. (#GH214160) - Fixed a bug where a stray closing curley brace in an OpenMP/OpenACC pragma could cause pragma parsing issues when inside of a member function. (#GH214195) @@ -547,9 +550,6 @@ features cannot lower the translation-unit ABI level; - Fixed a crash when an `asm` label names the register for a global variable of incomplete type. (#GH219746) - Fixed an ICE hat occurred when using `__imag int/float` as lvalue in assignment. (#GH119498) - Fixed an assertion failure in `-Wsign-compare` when a negated or complemented vector of unsigned integers was compared against a signed constant. (#GH203575) -- A declaration that declares nothing, such as `int;` or `__typeof__(x);`, used as a statement was treated as an - error without any diagnostic. This silently dropped the enclosing statement expression or `if` statement, and - could crash code generation when such a statement expression was used as an `if` condition. (#GH215454) #### Bug Fixes to Compiler Builtins _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
