[clang] [ObjC][SEH] Fix clang crash when using finally statements (PR #176779)

Eli Friedman via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 28 13:53:56 PDT 2026


https://github.com/efriedma-quic updated https://github.com/llvm/llvm-project/pull/176779

>From 07877a6890340f3adeb4e963ca7f868df64151f2 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 | 29 +++++++++++++++++++++++++++--
 1 file changed, 27 insertions(+), 2 deletions(-)

diff --git a/clang/lib/CodeGen/CGObjCRuntime.cpp b/clang/lib/CodeGen/CGObjCRuntime.cpp
index 099622f690678..3935987144756 100644
--- a/clang/lib/CodeGen/CGObjCRuntime.cpp
+++ b/clang/lib/CodeGen/CGObjCRuntime.cpp
@@ -179,6 +179,26 @@ void CGObjCRuntime::EmitTryCatchStmt(CodeGenFunction &CGF,
     }
   }
 
+  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;
 
   // Enter the catch, if there is one.
@@ -312,8 +332,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 f406cbbc8b02563ea7b17a283f0f5bb0f2f90e4f 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 | 53 +++++++++++++++++++++++++
 1 file changed, 53 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..326eef0da3a78
--- /dev/null
+++ b/clang/test/CodeGenObjC/exceptions-seh.m
@@ -0,0 +1,53 @@
+// 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:.*]]
+
+	@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:.*]]
+		} @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: 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]]:
+	@finally {
+		puts("cleanup");
+	}
+	return 0;
+}



More information about the cfe-commits mailing list