From 9e2213ddd5b3510fa48e3175e9d098db4224cc30 Mon Sep 17 00:00:00 2001
From: John McCall <rjmccall@apple.com>
Date: Mon, 4 Oct 2010 23:42:51 +0000
Subject: [PATCH] In the fragile ObjC ABI, save the caught exception to the
 side if there are both @catches and a @finally, because the second call to
 @objc_exception_try_enter will clobber the exception slot.  Fixes
 rdar://problem/8440970.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@115575 91177308-0d34-0410-b5e6-96231b3b80d8
---
 lib/CodeGen/CGObjCMac.cpp     | 39 ++++++++++++++++++++----
 test/CodeGenObjC/exceptions.m | 56 +++++++++++++++++++++++++++++++++++
 2 files changed, 89 insertions(+), 6 deletions(-)

diff --git a/lib/CodeGen/CGObjCMac.cpp b/lib/CodeGen/CGObjCMac.cpp
index 766703d9e7..c7e4c5ffcf 100644
--- a/lib/CodeGen/CGObjCMac.cpp
+++ b/lib/CodeGen/CGObjCMac.cpp
@@ -2973,6 +2973,10 @@ void CGObjCMac::EmitTryOrSynchronizedStmt(CodeGen::CodeGenFunction &CGF,
   llvm::Value *CallTryExitVar = CGF.CreateTempAlloca(CGF.Builder.getInt1Ty(),
                                                      "_call_try_exit");
 
+  // A slot containing the exception to rethrow.  Only needed when we
+  // have both a @catch and a @finally.
+  llvm::Value *PropagatingExnVar = 0;
+
   // Push a normal cleanup to leave the try scope.
   CGF.EHStack.pushCleanup<PerformFragileFinally>(NormalCleanup, &S,
                                                  SyncArgSlot,
@@ -3044,6 +3048,12 @@ void CGObjCMac::EmitTryOrSynchronizedStmt(CodeGen::CodeGenFunction &CGF,
     llvm::BasicBlock *CatchBlock = 0;
     llvm::BasicBlock *CatchHandler = 0;
     if (HasFinally) {
+      // Save the currently-propagating exception before
+      // objc_exception_try_enter clears the exception slot.
+      PropagatingExnVar = CGF.CreateTempAlloca(Caught->getType(),
+                                               "propagating_exception");
+      CGF.Builder.CreateStore(Caught, PropagatingExnVar);
+
       // Enter a new exception try block (in case a @catch block
       // throws an exception).
       CGF.Builder.CreateCall(ObjCTypes.getExceptionTryEnterFn(), ExceptionData)
@@ -3178,6 +3188,15 @@ void CGObjCMac::EmitTryOrSynchronizedStmt(CodeGen::CodeGenFunction &CGF,
       // the try's write hazard and here.
       //Hazards.emitWriteHazard();
 
+      // Extract the new exception and save it to the
+      // propagating-exception slot.
+      assert(PropagatingExnVar);
+      llvm::CallInst *NewCaught =
+        CGF.Builder.CreateCall(ObjCTypes.getExceptionExtractFn(),
+                               ExceptionData, "caught");
+      NewCaught->setDoesNotThrow();
+      CGF.Builder.CreateStore(NewCaught, PropagatingExnVar);
+
       // Don't pop the catch handler; the throw already did.
       CGF.Builder.CreateStore(CGF.Builder.getFalse(), CallTryExitVar);
       CGF.EmitBranchThroughCleanup(FinallyRethrow);
@@ -3198,13 +3217,21 @@ void CGObjCMac::EmitTryOrSynchronizedStmt(CodeGen::CodeGenFunction &CGF,
   CGBuilderTy::InsertPoint SavedIP = CGF.Builder.saveAndClearIP();
   CGF.EmitBlock(FinallyRethrow.getBlock(), true);
   if (CGF.HaveInsertPoint()) {
-    // Just look in the buffer for the exception to throw.
-    llvm::CallInst *Caught =
-      CGF.Builder.CreateCall(ObjCTypes.getExceptionExtractFn(),
-                             ExceptionData);
-    Caught->setDoesNotThrow();
+    // If we have a propagating-exception variable, check it.
+    llvm::Value *PropagatingExn;
+    if (PropagatingExnVar) {
+      PropagatingExn = CGF.Builder.CreateLoad(PropagatingExnVar);
+
+    // Otherwise, just look in the buffer for the exception to throw.
+    } else {
+      llvm::CallInst *Caught =
+        CGF.Builder.CreateCall(ObjCTypes.getExceptionExtractFn(),
+                               ExceptionData);
+      Caught->setDoesNotThrow();
+      PropagatingExn = Caught;
+    }
 
-    CGF.Builder.CreateCall(ObjCTypes.getExceptionThrowFn(), Caught)
+    CGF.Builder.CreateCall(ObjCTypes.getExceptionThrowFn(), PropagatingExn)
       ->setDoesNotThrow();
     CGF.Builder.CreateUnreachable();
   }
diff --git a/test/CodeGenObjC/exceptions.m b/test/CodeGenObjC/exceptions.m
index b431e37124..7cc0c1f250 100644
--- a/test/CodeGenObjC/exceptions.m
+++ b/test/CodeGenObjC/exceptions.m
@@ -133,3 +133,59 @@ void f3() {
   // CHECK-NEXT: ret void
   f3_helper(4, &x);
 }
+
+// rdar://problem/8440970
+void f4() {
+  extern void f4_help(int);
+
+  // CHECK: define void @f4()
+  // CHECK:      [[EXNDATA:%.*]] = alloca [[EXNDATA_T:%.*]], align
+  // CHECK:      call void @objc_exception_try_enter([[EXNDATA_T]]* [[EXNDATA]])
+  // CHECK:      call i32 @_setjmp
+  @try {
+  // CHECK:      call void @f4_help(i32 0)
+    f4_help(0);
+
+  // The finally cleanup has two threaded entrypoints after optimization:
+
+  // finally.no-call-exit:  Predecessor is when the catch throws.
+  // CHECK:      call i8* @objc_exception_extract([[EXNDATA_T]]* [[EXNDATA]])
+  // CHECK-NEXT: call void @f4_help(i32 2)
+  // CHECK-NEXT: br label
+  //   -> rethrow
+
+  // finally.call-exit:  Predecessors are the @try and @catch fallthroughs
+  // as well as the no-match case in the catch mechanism.  The i1 is whether
+  // to rethrow and should be true only in the last case.
+  // CHECK:      phi i1
+  // CHECK-NEXT: phi i8*
+  // CHECK-NEXT: call void @objc_exception_try_exit([[EXNDATA_T]]* [[EXNDATA]])
+  // CHECK-NEXT: call void @f4_help(i32 2)
+  // CHECK-NEXT: br i1
+  //   -> ret, rethrow
+
+  // ret:
+  // CHECK:      ret void
+
+  // Catch mechanism:
+  // CHECK:      call i8* @objc_exception_extract([[EXNDATA_T]]* [[EXNDATA]])
+  // CHECK-NEXT: call void @objc_exception_try_enter([[EXNDATA_T]]* [[EXNDATA]])
+  // CHECK:      call i32 @_setjmp
+  //   -> next, finally.no-call-exit
+  // CHECK:      call i32 @objc_exception_match
+  //   -> finally.call-exit, match
+  } @catch (NSArray *a) {
+  // match:
+  // CHECK:      call void @f4_help(i32 1)
+  // CHECK-NEXT: br label
+  //   -> finally.call-exit
+    f4_help(1);
+  } @finally {
+    f4_help(2);
+  }
+
+  // rethrow:
+  // CHECK:      phi i8*
+  // CHECK-NEXT: call void @objc_exception_throw(i8*
+  // CHECK-NEXT: unreachable
+}
-- 
2.40.0