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/7] [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/7] [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/7] [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();
+}

>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/7] [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 8fdc3cc9ac4fa..34dfb70aa1bba 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/7] [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 113012275bbdd..bcc4af7ec1ef9 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
 

>From 348e3e63f62704f4c315716f484e74c3741ecb00 Mon Sep 17 00:00:00 2001
From: Akash Manna <[email protected]>
Date: Wed, 23 Sep 2026 18:44:59 +0530
Subject: [PATCH 6/7] [Clang] Build the null statement in the parser and split
 the tests

Keep the empty declaration group out of Sema: the parser turns it into a
null statement itself, so ActOnDeclStmt is unchanged. Move the diagnostic
checks to a Sema test and leave the CodeGen tests to FileCheck only.
---
 clang/docs/ReleaseNotes.md         |  5 ++---
 clang/lib/Parse/ParseStmt.cpp      | 15 +++++++--------
 clang/lib/Sema/SemaStmt.cpp        |  5 ++---
 clang/test/CodeGen/GH215454.c      | 27 +++------------------------
 clang/test/CodeGenCXX/GH215454.cpp | 10 +++-------
 clang/test/Sema/GH215454.c         | 22 ++++++++++++++++++++++
 6 files changed, 39 insertions(+), 45 deletions(-)
 create mode 100644 clang/test/Sema/GH215454.c

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index bcc4af7ec1ef9..3d8957755f278 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -537,9 +537,8 @@ 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 a crash and silently dropped code when a declaration that declares 
nothing, like `int;`, is the last
+  statement of a GNU statement expression or the body of an `if`. (#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)
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 6bdde6274af35..5e348988687c8 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -231,12 +231,12 @@ StmtResult 
Parser::ParseStatementOrDeclarationAfterAttributes(
                                    GNUAttrs.Range.getBegin());
       } else if (GNUAttrs.Range.getBegin().isValid())
         DeclStart = GNUAttrs.Range.getBegin();
-      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())
+      // A declaration that declares nothing (`int;`) yields no Decl but still
+      // occupies the statement position; unlike a pragma, ParseStatement() 
must
+      // not skip it.
+      if (!Decl)
         return Actions.ActOnNullStmt(PrevTokLocation);
-      return R;
+      return Actions.ActOnDeclStmt(Decl, DeclStart, DeclEnd);
     }
 
     if (Tok.is(tok::r_brace)) {
@@ -1226,10 +1226,9 @@ StmtResult Parser::ParseCompoundStatementBody(bool 
isStmtExpr) {
         ParsedAttributes DeclSpecAttrs(AttrFactory);
         DeclGroupPtrTy Res = ParseDeclaration(DeclaratorContext::Block, 
DeclEnd,
                                               attrs, DeclSpecAttrs);
-        R = Actions.ActOnDeclStmt(Res, DeclStart, DeclEnd);
         // See ParseStatementOrDeclarationAfterAttributes.
-        if (R.isUnset())
-          R = Actions.ActOnNullStmt(PrevTokLocation);
+        R = Res ? Actions.ActOnDeclStmt(Res, DeclStart, DeclEnd)
+                : Actions.ActOnNullStmt(PrevTokLocation);
       } else {
         // Otherwise this was a unary __extension__ marker.
         ExprResult Res(ParseExpressionWithLeadingExtension(ExtLoc));
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 9e252dcee021f..12da741437409 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -77,10 +77,9 @@ StmtResult Sema::ActOnDeclStmt(DeclGroupPtrTy dg, 
SourceLocation StartLoc,
                                SourceLocation EndLoc) {
   DeclGroupRef DG = dg.get();
 
-  // No declarations, so no statement. This is not an error: `int;` only warns,
-  // and a group emptied by an error was already diagnosed.
+  // If we have an invalid decl, just return an error.
   if (DG.isNull())
-    return StmtEmpty();
+    return StmtError();
 
   return new (Context) DeclStmt(DG, StartLoc, EndLoc);
 }
diff --git a/clang/test/CodeGen/GH215454.c b/clang/test/CodeGen/GH215454.c
index e2b73de7965a5..255636555c215 100644
--- a/clang/test/CodeGen/GH215454.c
+++ b/clang/test/CodeGen/GH215454.c
@@ -1,33 +1,12 @@
-// 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
+// RUN: %clang_cc1 -std=gnu99 -triple x86_64-unknown-linux-gnu -emit-llvm -o - 
%s | FileCheck %s
 
 // 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.
+// invalid without an error, and the call in it was dropped.
 
 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}}
+  ({ foo(); __typeof__(e); });
 }
-
-#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/CodeGenCXX/GH215454.cpp 
b/clang/test/CodeGenCXX/GH215454.cpp
index 0a04ca97dc881..b5d87de8f166e 100644
--- a/clang/test/CodeGenCXX/GH215454.cpp
+++ b/clang/test/CodeGenCXX/GH215454.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -verify -emit-llvm -o - %s 
| FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | 
FileCheck %s
 
 void foo();
 void bar();
@@ -8,9 +8,7 @@ void bar();
 // 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}}
+  if (foo(), e) int;
 }
 
 // The empty declaration is the body; the next statement must not become it.
@@ -20,8 +18,6 @@ void d(int e) {
 // 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}}
+  if (e) int;
   bar();
 }
diff --git a/clang/test/Sema/GH215454.c b/clang/test/Sema/GH215454.c
new file mode 100644
index 0000000000000..5cf64b1590e1e
--- /dev/null
+++ b/clang/test/Sema/GH215454.c
@@ -0,0 +1,22 @@
+// RUN: %clang_cc1 -std=gnu99 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=gnu99 -fsyntax-only -verify -fno-recovery-ast %s
+
+// A declaration that declares nothing made the enclosing statement expression
+// invalid without an error, so the void condition below was never diagnosed
+// and CodeGen crashed on it.
+
+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@+1 {{expected '}'}}

>From 839a773a8e1a12950ff8151954068e8e7b6ab56f Mon Sep 17 00:00:00 2001
From: Akash Manna <[email protected]>
Date: Thu, 24 Sep 2026 20:16:02 +0530
Subject: [PATCH 7/7] [Clang] Add tests for a declaration declaring nothing as
 an if body

Pin that `if (1) int;` is diagnosed exactly as before in C ("expected
expression") and C++ ("declaration does not declare anything"), and back
out a formatting-only change in SemaStmt.cpp.
---
 clang/lib/Sema/SemaStmt.cpp     |  3 +--
 clang/test/Sema/GH215454.c      |  6 ++++++
 clang/test/SemaCXX/GH215454.cpp | 17 +++++++++++++++++
 3 files changed, 24 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/SemaCXX/GH215454.cpp

diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 12da741437409..331c1866e36ec 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -78,8 +78,7 @@ StmtResult Sema::ActOnDeclStmt(DeclGroupPtrTy dg, 
SourceLocation StartLoc,
   DeclGroupRef DG = dg.get();
 
   // If we have an invalid decl, just return an error.
-  if (DG.isNull())
-    return StmtError();
+  if (DG.isNull()) return StmtError();
 
   return new (Context) DeclStmt(DG, StartLoc, EndLoc);
 }
diff --git a/clang/test/Sema/GH215454.c b/clang/test/Sema/GH215454.c
index 5cf64b1590e1e..e163c7b63d5e8 100644
--- a/clang/test/Sema/GH215454.c
+++ b/clang/test/Sema/GH215454.c
@@ -10,6 +10,12 @@ void d2(int e) {
                                  // expected-error {{statement requires 
expression of scalar type ('void' invalid)}}
 }
 
+// In C a declaration is not a statement, so this never reaches the fixed code.
+void d3(void) {
+  if (1)
+    int; // expected-error {{expected expression}}
+}
+
 // Reproducer from the issue; the unclosed '({' makes recovery run to EOF.
 #define c(a, b)                                                                
\
   {;__typeof__(b);}
diff --git a/clang/test/SemaCXX/GH215454.cpp b/clang/test/SemaCXX/GH215454.cpp
new file mode 100644
index 0000000000000..8550bb49e63f7
--- /dev/null
+++ b/clang/test/SemaCXX/GH215454.cpp
@@ -0,0 +1,17 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+// A declaration that declares nothing is still diagnosed as before; the
+// enclosing 'if' is now kept with a null statement as its body instead of
+// being dropped.
+void foo();
+void f() {
+  if (1)
+    int; // expected-warning {{declaration does not declare anything}}
+
+  if (foo(), 1)
+    int; // expected-warning {{declaration does not declare anything}}
+
+  if (1) 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}}
+}

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

Reply via email to