[clang] [ObjC][SEH] Fix clang crash when using finally statements (PR #176779)
Hugo Melder via cfe-commits
cfe-commits at lists.llvm.org
Mon Jan 19 09:19:41 PST 2026
https://github.com/hmelder created https://github.com/llvm/llvm-project/pull/176779
When targeting a platform that does not have funclet-based EH, we push the finally cleanup (normal edge) and catchall (unwind edge) onto the EHStack _before_ pushing all catch handlers. The try statement is then emitted and catch handlers popped from EHStack. Last, the finally cleanup is popped from EHStack.
For funclet-based EH, we outline and push the finally funclet (of type `NormalAndEHCleanup`) onto the EHStack _after_ pushing the catch handlers and never pop it. This results in a crash during codegen when we try to emit the catch handlers. Not popping the finally cleanup from the EHStack results in incorrect calls to cleanup handlers in nested try/catch/finally statements.
I fixed the two issues by:
1. Pushing the finally cleanup first, and
2. Popping it at the end of `CGObjCRuntime::EmitTryCatchStmt`.
>From d4a718f4ad86349925b64788c0b3eb387f5d375e Mon Sep 17 00:00:00 2001
From: hmelder <service at hugomelder.com>
Date: Mon, 19 Jan 2026 13:57:33 +0000
Subject: [PATCH 1/2] [ObjC][SEH] Push cleanup before catch handlers
The cleanup funclet for an `@finally` statement needs to be pushed onto
EHStack before the catch handlers are pushed.
---
clang/lib/CodeGen/CGObjCRuntime.cpp | 51 +++++++++++++++--------------
1 file changed, 27 insertions(+), 24 deletions(-)
diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp
index 3d47dc9560c65..923118d859d54 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -155,6 +155,26 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
FinallyInfo.enter(CGF, Finally->getFinallyBody(),
beginCatchFn, endCatchFn, exceptionRethrowFn);
+ if (useFunclets)
+ if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) {
+ CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true);
+ if (!CGF.CurSEHParent)
+ CGF.CurSEHParent = cast<NamedDecl>(CGF.CurFuncDecl);
+ // Outline the finally block.
+ const Stmt *FinallyBlock = Finally->getFinallyBody();
+ HelperCGF.startOutlinedSEHHelper(CGF, /*isFilter*/ false, FinallyBlock);
+
+ // Emit the original filter expression, convert to i32, and return.
+ HelperCGF.EmitStmt(FinallyBlock);
+
+ HelperCGF.FinishFunction(FinallyBlock->getEndLoc());
+
+ llvm::Function *FinallyFunc = HelperCGF.CurFn;
+
+ // Push a cleanup for __finally blocks.
+ CGF.pushSEHCleanup(NormalAndEHCleanup, FinallyFunc);
+ }
+
SmallVector<CatchHandler, 8> Handlers;
@@ -187,28 +207,6 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
Catch->setHandler(I, { Handlers[I].TypeInfo, Handlers[I].Flags }, Handlers[I].Block);
}
- if (useFunclets)
- if (const ObjCAtFinallyStmt *Finally = S.getFinallyStmt()) {
- CodeGenFunction HelperCGF(CGM, /*suppressNewContext=*/true);
- if (!CGF.CurSEHParent)
- CGF.CurSEHParent = cast<NamedDecl>(CGF.CurFuncDecl);
- // Outline the finally block.
- const Stmt *FinallyBlock = Finally->getFinallyBody();
- HelperCGF.startOutlinedSEHHelper(CGF, /*isFilter*/false, FinallyBlock);
-
- // Emit the original filter expression, convert to i32, and return.
- HelperCGF.EmitStmt(FinallyBlock);
-
- HelperCGF.FinishFunction(FinallyBlock->getEndLoc());
-
- llvm::Function *FinallyFunc = HelperCGF.CurFn;
-
-
- // Push a cleanup for __finally blocks.
- CGF.pushSEHCleanup(NormalAndEHCleanup, FinallyFunc);
- }
-
-
// Emit the try body.
CGF.EmitStmt(S.getTryBody());
@@ -276,8 +274,13 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
CGF.Builder.restoreIP(SavedIP);
// Pop out of the finally.
- if (!useFunclets && S.getFinallyStmt())
- FinallyInfo.exit(CGF);
+ if (S.getFinallyStmt()) {
+ if (useFunclets) {
+ CGF.PopCleanupBlock();
+ } else {
+ FinallyInfo.exit(CGF);
+ }
+ }
if (Cont.isValid())
CGF.EmitBlock(Cont.getBlock());
>From 8fdc8dbba69fa3d7599f968f2851cdf1e17f1816 Mon Sep 17 00:00:00 2001
From: hmelder <service at hugomelder.com>
Date: Mon, 19 Jan 2026 15:27:04 +0000
Subject: [PATCH 2/2] [ObjC][SEH] Add regression test for finally cleanup
---
clang/test/CodeGenObjC/exceptions-seh.m | 65 +++++++++++++++++++++++++
1 file changed, 65 insertions(+)
create mode 100644 clang/test/CodeGenObjC/exceptions-seh.m
diff --git a/clang/test/CodeGenObjC/exceptions-seh.m b/clang/test/CodeGenObjC/exceptions-seh.m
new file mode 100644
index 0000000000000..e62414ee14117
--- /dev/null
+++ b/clang/test/CodeGenObjC/exceptions-seh.m
@@ -0,0 +1,65 @@
+// RUN: %clang_cc1 -triple aarch64-pc-windows -emit-llvm -fexceptions -fobjc-exceptions -fobjc-runtime=gnustep-2.2 -o - %s | FileCheck %s
+
+void may_throw(void);
+void puts(const char *);
+
+int main(void) {
+ @try {
+ may_throw();
+ // CHECK: invoke void @may_throw()
+ // CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[CATCH_DISPATCH:.*]]
+ }
+
+ // Check that the dispatch block has been emitted correctly. We capture the
+ // normal and unwind edge for later checks.
+ // CHECK: [[CATCH_DISPATCH]]:
+ // CHECK-NEXT: %[[CATCHSWITCH_OUTER:.*]] = catchswitch within none [label %[[CATCH_A:.*]], label %[[CATCH_B:.*]]] unwind label %[[EH_CLEANUP_OUTER_FINALLY:.*]]
+
+ // Check if normal edge leads to a call to the outer finally funclet
+ // CHECK: [[INVOKE_CONT]]:
+ // CHECK: call void @"?fin$0 at 0@main@@"
+
+ @catch(id a) {
+ // CHECK: %[[CATCHPAD_A:.*]] = catchpad within %[[CATCHSWITCH_OUTER]]
+ puts("catch");
+ @try {
+ may_throw();
+ // CHECK: invoke void @may_throw() [ "funclet"(token %{{.*}}) ]
+ // CHECK-NEXT: to label %[[INVOKE_CONT_INNER:.*]] unwind label %[[CATCH_DISPATCH_INNER:.*]]
+
+ // CHECK: [[CATCH_DISPATCH_INNER]]:
+ // CHECK-NEXT: %{{.*}} = catchswitch within %[[CATCHPAD_A]] [label %[[CATCHPAD_A_INNER:.*]]] unwind label %[[EH_CLEANUP_INNER_FINALLY:.*]]
+
+ // Check if normal edge leads to a call to the inner finally funclet and calls
+ // CHECK: [[INVOKE_CONT_INNER]]:
+ // CHECK: invoke void @"?fin$1 at 0@main@@"
+ } @catch(...) {
+ // CHECK: [[CATCHPAD_A_INNER]]:
+ // CHECK: to label %invoke.cont{{[0-9]+}} unwind label %[[EH_CLEANUP_INNER_FINALLY]]
+ puts("inner catch all");
+ } @finally {
+ // CHECK: [[EH_CLEANUP_INNER_FINALLY]]:
+ // CHECK: invoke void @"?fin$1 at 0@main@@"{{.*}}
+ // CHECK-NEXT: to label %invoke.cont{{[0-9]+}} unwind label %[[EH_CLEANUP_OUTER_FINALLY]]
+ puts("inner finally");
+ }
+ return 42;
+ }
+
+ @catch(id b) {
+ // CHECK: %[[CATCHPAD_B:.*]] = catchpad within %[[CATCHSWITCH_OUTER]]
+ // CHECK: to label %invoke.cont{{[0-9]+}} unwind label %[[EH_CLEANUP_OUTER_FINALLY]]
+ puts("catch 2");
+ return 43;
+ }
+
+ // Check that the cleanuppad from the SEH finally funclet was correctly emitted.
+ // CHECK: [[EH_CLEANUP_OUTER_FINALLY]]:
+ // CHECK: %[[CLEANUP_PAD:.*]] = cleanuppad
+ // CHECK: call void @"?fin$0 at 0@main@@"
+ // CHECK: cleanupret from %[[CLEANUP_PAD]] unwind to caller
+ @finally {
+ puts("cleanup");
+ }
+ return 0;
+}
More information about the cfe-commits
mailing list