[clang] [Clang][Parse] Fix crash on stmt-expr ending with 'extern void;' (PR #181579)

via cfe-commits cfe-commits at lists.llvm.org
Sun Feb 15 14:05:11 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Giovanni B. (Z3rox-dev)

<details>
<summary>Changes</summary>

Fix a crash in CodeGen (`Unexpected placeholder builtin type!` in `CodeGenTypes::ConvertType`) when a GNU statement expression ends with `extern void;` preceded by other statements.

## Root Cause

`extern void;` produces a null `DeclGroupPtrTy`, causing `ActOnDeclStmt` to return `StmtError()`. In `ParseCompoundStatementBody`, this unconditionally set `LastIsError = true`. For multi-statement statement expressions, the check:

```cpp
if (isStmtExpr && LastIsError && !Stmts.empty())
  return StmtError();
```

caused the entire compound statement to fail. This cascaded up to `ParsePostfixExpressionSuffix` where `ExpressionListIsInvalid` created a `RecoveryExpr` with `<dependent type>` **without emitting a proper error diagnostic**. Since no error was recorded by Sema, CodeGen proceeded and hit `llvm_unreachable` when encountering the dependent type.

## Fix

Refine `LastIsError` in `ParseCompoundStatementBody` to only be set when `R.isInvalid()` **and** a new error diagnostic was actually emitted during parsing of that statement. `extern void;` only emits a warning (not an error), so it no longer triggers the cascade. The proper type-checking error (`passing 'void' to parameter of incompatible type 'int'`) is now emitted as expected.

## Reproducer

```c
extern int bar(int);
int foo(int x) {
  return 1 + bar(({
    switch (x) { default: break; }
    extern void;
  }));
}
```

Before: crash with `UNREACHABLE executed at CodeGenTypes.cpp: Unexpected placeholder builtin type!`
After: clean error `passing 'void' to parameter of incompatible type 'int'`

## Testing

- All `check-clang-sema` (1255), `check-clang-parser` (403), `check-clang-semacxx` (1378), `check-clang-codegen` (2937) tests pass with zero failures.
- Added regression test `clang/test/CodeGen/stmt-expr-void-cast.c`.


---
Full diff: https://github.com/llvm/llvm-project/pull/181579.diff


2 Files Affected:

- (modified) clang/lib/Parse/ParseStmt.cpp (+10-1) 
- (added) clang/test/CodeGen/stmt-expr-void-cast.c (+33) 


``````````diff
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 1a45ed66950be..8c43e49e8f4e4 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -1160,6 +1160,8 @@ StmtResult Parser::ParseCompoundStatementBody(bool isStmtExpr) {
   bool LastIsError = false;
   while (!tryParseMisplacedModuleImport() && Tok.isNot(tok::r_brace) &&
          Tok.isNot(tok::eof)) {
+    unsigned PrevErrors = Actions.getDiagnostics().getNumErrors();
+
     if (Tok.is(tok::annot_pragma_unused)) {
       HandlePragmaUnused();
       continue;
@@ -1214,7 +1216,14 @@ StmtResult Parser::ParseCompoundStatementBody(bool isStmtExpr) {
 
     if (R.isUsable())
       Stmts.push_back(R.get());
-    LastIsError = R.isInvalid();
+    // Only treat the last statement as an error if an actual error diagnostic
+    // was emitted during parsing. Constructs like "extern void;" may produce
+    // an invalid StmtResult (via null DeclGroup) with only a warning, which
+    // should not cause the entire compound statement to fail in a stmt-expr.
+    // See https://github.com/llvm/llvm-project/issues/173921
+    LastIsError =
+        R.isInvalid() &&
+        Actions.getDiagnostics().getNumErrors() > PrevErrors;
   }
   // StmtExpr needs to do copy initialization for last statement.
   // If last statement is invalid, the last statement in `Stmts` will be
diff --git a/clang/test/CodeGen/stmt-expr-void-cast.c b/clang/test/CodeGen/stmt-expr-void-cast.c
new file mode 100644
index 0000000000000..48891aa89b4a8
--- /dev/null
+++ b/clang/test/CodeGen/stmt-expr-void-cast.c
@@ -0,0 +1,33 @@
+// RUN: %clang_cc1 -emit-llvm -verify %s -o /dev/null
+
+// Regression test for https://github.com/llvm/llvm-project/issues/173921
+// Ensure that "extern void;" inside a multi-statement GNU statement expression
+// does not cause a crash in CodeGen due to a leaked RecoveryExpr with
+// dependent type.
+
+extern int bar(int); // expected-note 3{{passing argument to parameter here}}
+
+int test_switch(int x) {
+  return 1 + bar(({     // expected-error {{passing 'void' to parameter of incompatible type 'int'}}
+           int y;
+           switch (x) {
+           default:
+             y = 7;
+             break;
+           }
+           extern void; // expected-warning {{declaration does not declare anything}}
+         }));
+}
+
+int test_single_stmt(int x) {
+  return bar(({          // expected-error {{passing 'void' to parameter of incompatible type 'int'}}
+           extern void;  // expected-warning {{declaration does not declare anything}}
+         }));
+}
+
+int test_if(int x) {
+  return bar(({          // expected-error {{passing 'void' to parameter of incompatible type 'int'}}
+           if (x) {}
+           extern void;  // expected-warning {{declaration does not declare anything}}
+         }));
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/181579


More information about the cfe-commits mailing list