[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