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