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

Reply via email to