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/3] [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 f4a34a37aff52..fe279a88cd942 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 14bea6a1a7948..15791273e71f7 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 0000000000000..aceba1d41474a
--- /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 94551986b0efa..f4252abd59c44 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/3] [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 fe279a88cd942..113012275bbdd 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 15791273e71f7..6bdde6274af35 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 331c1866e36ec..9e252dcee021f 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 aceba1d41474a..e2b73de7965a5 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 0000000000000..6da7306b42e8d
--- /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 f4252abd59c44..94551986b0efa 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/3] [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 6da7306b42e8d..0a04ca97dc881 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();
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to