gnustep / libobjc2

Objective-C runtime library intended for use with Clang.
http://www.gnustep.org/
MIT License
426 stars 116 forks source link

Use C++ exceptions unconditionally for Objective-C[++] on MinGW #267

Closed qmfrederik closed 6 months ago

qmfrederik commented 6 months ago

Alternative implementation of #260. Requires changes to clang - see https://github.com/qmfrederik/llvm-project/commit/88ce1ba8d714c5f56e3697151119a85a4fba

qmfrederik commented 6 months ago

Current status:

davidchisnall commented 6 months ago

I presume that we can use the implementation from here on MinGW for the first point?

https://github.com/gnustep/libobjc2/blob/3c42c64c14d912d5af31146d6dba435515b1defe/eh_win32_msvc.cc#L276

qmfrederik commented 6 months ago

OK, I've added a dummy implementation of objc_setUncaughtExceptionHandler. I assume we'll need a different implementation for _objc_unhandled_exception_filter?

A lot of tests are currently failing like this:

terminate called after throwing an instance of '@id'

For example:

# ./Test/ExceptionTest.exe
terminate called after throwing an instance of '@id'

Any thoughts on what could cause that?

davidchisnall commented 6 months ago

Can you trace into the C++ personality function? If clang is correctly generating the LSDA, then this should work. Actually, try sticking a breakpoint on:

https://github.com/gnustep/libobjc2/blob/3e115c522a8a544447d5a41cca4df14e1b398e87/objcxx_eh.cc#L319C1-L319C1

And:

https://github.com/gnustep/libobjc2/blob/3e115c522a8a544447d5a41cca4df14e1b398e87/objcxx_eh.cc#L353C22-L353C22

These should be being called by the C++ personality function to check for matches.

The error message you're seeing is the one that happens if the C++ runtime throws an exception and nothing catches it (unwind reaches the end of the stack).

qmfrederik commented 6 months ago

From https://github.com/gnustep/libobjc2/pull/260#issuecomment-1878568187

The try/catch clause in objc_msgSend.m seems to be working, although the assert assert((TestCls == e) && "Exceptions propagate out of +initialize"); is failing; if I disable that assert the test passes.

Thanks! Looks like we're getting close. Propagating out of +initialize requires that they're thrown through C frames with attribute((cleanup)). Can you check whether that works with MinGW with just C and C++ (call C++ -> C -> C++, use attribute((cleanup)) in C, check you can throw an exception out)? It's possible that we need to add an extra compiler flag to the compilation unit that handles initialize.

It's a bit surprising to me that the exception seems to be thrown and code continues executing.

This doesn't reproduce after rebuilding, but leaving the comment here for future reference.

qmfrederik commented 6 months ago

I added additional logging and this is the current output (contradicting my previous comment):


vagrant@DESKTOP-RNTVKUC MINGW64 ~/git/libobjc2/build
# ./Test/objc_msgSend.exe 
gnustep::libobjc::__objc_id_type_info::__do_catch: Entering
Assertion failed: (TestCls == e) && "Exceptions propagate out of +initialize", file C:/tools/msys64/home/vagrant/git/libobjc2/Test/objc_msgSend.m, line 186

vagrant@DESKTOP-RNTVKUC MINGW64 ~/git/libobjc2/build
# ./Test/ExceptionTest.exe
gnustep::libobjc::__objc_id_type_info::__do_catch: Entering
Assertion failed: object_getClass(x) == [Test class], file C:/tools/msys64/home/vagrant/git/libobjc2/Test/ExceptionTest.m, line 40
davidchisnall commented 6 months ago

Can you step through the do-catch methods and see where the match is failing?

qmfrederik commented 6 months ago

OK - some progress. The actual exception object wasn't being copied in objc_exception_throw - that should now be implemented, see https://github.com/gnustep/libobjc2/pull/267/files#diff-5803607d1fc7fe6f76ab1f6e98c4c6e5b373acb8bb6c103cd3fccaef42abd215R527-R528

With that, most tests seem to pass; only ExceptionTest and UnexpectedException fail:

94% tests passed, 10 tests failed out of 178

Total Test time (real) =  26.78 sec

The following tests FAILED:
          3 - alias_legacy (Failed)
          4 - alias_legacy_optimised (Failed)
         35 - ExceptionTest (Failed)
         36 - ExceptionTest_optimised (Failed)
         37 - ExceptionTest_legacy (Failed)
         38 - ExceptionTest_legacy_optimised (Failed)
        155 - UnexpectedException (Failed)
        156 - UnexpectedException_optimised (Failed)
        157 - UnexpectedException_legacy (Failed)
        158 - UnexpectedException_legacy_optimised (Failed)
qmfrederik commented 6 months ago

w.r.t. ExceptionTest failing, it looks like this may be related to rethrowing an exception

Here's the output (with a bunch of additional logging statements):

throw: Throwing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0x5465eea8 of type Test
gnustep::libobjc::__objc_id_type_info::__do_catch: Entering
gnustep::libobjc::__objc_id_type_info::__do_catch: Type info @id
gnustep::libobjc::__objc_id_type_info::__do_catch: got id type info for 0x5465eea8, returning true
eh_cleanup: Releasing exception 0x5465eea8
rethrow_id: Rethrowing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0x5465eea8 of type Test
gnustep::libobjc::__objc_class_type_info::__do_catch: Entering
gnustep::libobjc::__objc_class_type_info::__do_catch: Type info @id
gnustep::libobjc::__objc_class_type_info::__do_catch: object is an id type or class type and we're in Apple compat mode
gnustep::libobjc::__objc_class_type_info::__do_catch: got type Test
gnustep::libobjc::__objc_class_type_info::__do_catch: found is YES
gnustep::libobjc::__objc_class_type_info::__do_catch: found matching type
gnustep::libobjc::__objc_class_type_info::__do_catch: returning 1; *obj is 0x5465eea8
terminate called after throwing an instance of '@id'
__do_upcast: Entering
eh_cleanup: Releasing exception 0x5465eea8
qmfrederik commented 6 months ago

For objc_setUncaughtExceptionHandler: it looks like SetUnhandledExceptionFilter does not work on mingw64 but AddVectoredExceptionHandler does. There's not a lot of information on the subject; e.g. https://github.com/ocaml/ocaml/pull/938 or https://sourceforge.net/p/mingw-w64/support-requests/29/ (from 2010!)

davidchisnall commented 6 months ago

Okay, it looks as if the first throw and catch is working. That's a good start: throwing an ObjC object and catching id is the simplest case. The second throw looks as if it's going through the same code paths as first, which I don't think is correct. Rethrowing should be done via __cxa_rethrow. The simplest thing would probably be to modify this:

https://github.com/qmfrederik/llvm-project/blob/88ce1ba8d714c5f56e3697151119a85a4fbafcf0/clang/lib/CodeGen/CGObjCGNU.cpp#L822

To remove the not-MinGW condition and then implement objc_exception_rethrow to just call __cxa_throw.

Can you share the LLVM IR for compiling this with the modified clang?

qmfrederik commented 6 months ago

Here's the clang change: https://github.com/llvm/llvm-project/commit/d68db09a39222aac18e6a8e1e3e55b099d75b765 And the libobjc2 change: https://github.com/gnustep/libobjc2/pull/267/commits/4970a82f7a0ed37b0f87ffe42e4cdbc52df3d654

That gives this output:

throw: Throwing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0x9db0eea8 of type Test
objc_exception_rethrow: Entering
terminate called without an active exception

Looks like the application now halts earlier. I'll get you the intermediate representation in a follow-up.

qmfrederik commented 6 months ago

Here's the intermediate representation (assuming I did this right):

```ir ; ModuleID = '../Test/ExceptionTest.m' source_filename = "../Test/ExceptionTest.m" target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" target triple = "x86_64-w64-windows-gnu" %.objc_section_sentinel = type <{}> $.objcv2_load_function = comdat any $.objc_sel_name_new = comdat any $".objc_sel_types_\0116\010:8" = comdat any $".objc_selector_new_\0116\010:8" = comdat any $.objc_sel_name_class = comdat any $".objc_sel_types_#16\010:8" = comdat any $".objc_selector_class_#16\010:8" = comdat any $__objc_eh_typename_Test = comdat any $__objc_eh_typename_NSString = comdat any $.objc_sel_name_dealloc = comdat any $".objc_sel_types_v16\010:8" = comdat any $".objc_selector_dealloc_v16\010:8" = comdat any $"__start_.objcrt$SEL" = comdat any $"__stop.objcrt$SEL" = comdat any $"__start_.objcrt$CLS" = comdat any $"__stop.objcrt$CLS" = comdat any $"__start_.objcrt$CLR" = comdat any $"__stop.objcrt$CLR" = comdat any $"__start_.objcrt$CAT" = comdat any $"__stop.objcrt$CAT" = comdat any $"__start_.objcrt$PCL" = comdat any $"__stop.objcrt$PCL" = comdat any $"__start_.objcrt$PCR" = comdat any $"__stop.objcrt$PCR" = comdat any $"__start_.objcrt$CAL" = comdat any $"__stop.objcrt$CAL" = comdat any $"__start_.objcrt$STR" = comdat any $"__stop.objcrt$STR" = comdat any $.objc_init = comdat any $.objc_ctor = comdat any @finallyEntered = dso_local global i32 0, align 4 @cleanupRun = dso_local global i32 0, align 4 @idRethrown = dso_local global i32 0, align 4 @catchallRethrown = dso_local global i32 0, align 4 @testCaught = dso_local global i32 0, align 4 @wrongMatch = dso_local global i32 0, align 4 @.str = private unnamed_addr constant [17 x i8] c"cleanupRun == NO\00", align 1 @.str.1 = private unnamed_addr constant [24 x i8] c"../Test/ExceptionTest.m\00", align 1 @.str.2 = private unnamed_addr constant [27 x i8] c"throw: Throwing exception\0A\00", align 1 @"$_OBJC_REF_CLASS_Test" = external global ptr @.objc_sel_name_new = linkonce_odr hidden constant [4 x i8] c"new\00", comdat @".objc_sel_types_\0116\010:8" = linkonce_odr hidden constant [8 x i8] c"@16@0:8\00", comdat @".objc_selector_new_\0116\010:8" = linkonce_odr hidden global { ptr, ptr } { ptr @.objc_sel_name_new, ptr @".objc_sel_types_\0116\010:8" }, section ".objcrt$SEL$m", comdat, align 8 @__objc_id_type_info = external global ptr @.objc_sel_name_class = linkonce_odr hidden constant [6 x i8] c"class\00", comdat @".objc_sel_types_#16\010:8" = linkonce_odr hidden constant [8 x i8] c"#16@0:8\00", comdat @".objc_selector_class_#16\010:8" = linkonce_odr hidden global { ptr, ptr } { ptr @.objc_sel_name_class, ptr @".objc_sel_types_#16\010:8" }, section ".objcrt$SEL$m", comdat, align 8 @.str.3 = private unnamed_addr constant [35 x i8] c"object_getClass(x) == [Test class]\00", align 1 @.str.4 = private unnamed_addr constant [34 x i8] c"rethrow_id: Rethrowing exception\0A\00", align 1 @_ZTVN7gnustep7libobjc22__objc_class_type_infoE = external constant ptr @__objc_eh_typename_Test = linkonce_odr constant [5 x i8] c"Test\00", comdat @__objc_eh_typeinfo_Test = linkonce_odr global { ptr, ptr } { ptr getelementptr (ptr, ptr @_ZTVN7gnustep7libobjc22__objc_class_type_infoE, i32 2), ptr @__objc_eh_typename_Test }, align 8 @.str.5 = private unnamed_addr constant [36 x i8] c"rethrow_test: Rethrowing exception\0A\00", align 1 @.str.6 = private unnamed_addr constant [32 x i8] c"rethrow_test: in @catch (id x)\0A\00", align 1 @.str.7 = private unnamed_addr constant [30 x i8] c"0 && \22should not be reached!\22\00", align 1 @.str.8 = private unnamed_addr constant [31 x i8] c"rethrow_test: in @catch (...)\0A\00", align 1 @.str.9 = private unnamed_addr constant [11 x i8] c"testCaught\00", align 1 @.str.10 = private unnamed_addr constant [40 x i8] c"rethrow_catchall: Rethrowing exception\0A\00", align 1 @__objc_eh_typename_NSString = linkonce_odr constant [9 x i8] c"NSString\00", comdat @__objc_eh_typeinfo_NSString = linkonce_odr global { ptr, ptr } { ptr getelementptr (ptr, ptr @_ZTVN7gnustep7libobjc22__objc_class_type_infoE, i32 2), ptr @__objc_eh_typename_NSString }, align 8 @.str.11 = private unnamed_addr constant [22 x i8] c"finallyEntered == YES\00", align 1 @.str.12 = private unnamed_addr constant [18 x i8] c"cleanupRun == YES\00", align 1 @.str.13 = private unnamed_addr constant [18 x i8] c"idRethrown == YES\00", align 1 @.str.14 = private unnamed_addr constant [24 x i8] c"catchallRethrown == YES\00", align 1 @.str.15 = private unnamed_addr constant [17 x i8] c"wrongMatch == NO\00", align 1 @.objc_sel_name_dealloc = linkonce_odr hidden constant [8 x i8] c"dealloc\00", comdat @".objc_sel_types_v16\010:8" = linkonce_odr hidden constant [8 x i8] c"v16@0:8\00", comdat @".objc_selector_dealloc_v16\010:8" = linkonce_odr hidden global { ptr, ptr } { ptr @.objc_sel_name_dealloc, ptr @".objc_sel_types_v16\010:8" }, section ".objcrt$SEL$m", comdat, align 8 @"__start_.objcrt$SEL" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$SEL$a", comdat, align 8 @"__stop.objcrt$SEL" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$SEL$z", comdat, align 8 @"__start_.objcrt$CLS" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CLS$a", comdat, align 8 @"__stop.objcrt$CLS" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CLS$z", comdat, align 8 @"__start_.objcrt$CLR" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CLR$a", comdat, align 8 @"__stop.objcrt$CLR" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CLR$z", comdat, align 8 @"__start_.objcrt$CAT" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CAT$a", comdat, align 8 @"__stop.objcrt$CAT" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CAT$z", comdat, align 8 @"__start_.objcrt$PCL" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$PCL$a", comdat, align 8 @"__stop.objcrt$PCL" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$PCL$z", comdat, align 8 @"__start_.objcrt$PCR" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$PCR$a", comdat, align 8 @"__stop.objcrt$PCR" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$PCR$z", comdat, align 8 @"__start_.objcrt$CAL" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CAL$a", comdat, align 8 @"__stop.objcrt$CAL" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$CAL$z", comdat, align 8 @"__start_.objcrt$STR" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$STR$a", comdat, align 8 @"__stop.objcrt$STR" = linkonce_odr hidden global %.objc_section_sentinel zeroinitializer, section ".objcrt$STR$z", comdat, align 8 @.objc_init = linkonce_odr hidden global { i64, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr } { i64 0, ptr @"__start_.objcrt$SEL", ptr @"__stop.objcrt$SEL", ptr @"__start_.objcrt$CLS", ptr @"__stop.objcrt$CLS", ptr @"__start_.objcrt$CLR", ptr @"__stop.objcrt$CLR", ptr @"__start_.objcrt$CAT", ptr @"__stop.objcrt$CAT", ptr @"__start_.objcrt$PCL", ptr @"__stop.objcrt$PCL", ptr @"__start_.objcrt$PCR", ptr @"__stop.objcrt$PCR", ptr @"__start_.objcrt$CAL", ptr @"__stop.objcrt$CAL", ptr @"__start_.objcrt$STR", ptr @"__stop.objcrt$STR" }, comdat, align 8 @.objc_ctor = linkonce hidden global ptr @.objcv2_load_function, section ".CRT$XCLz", comdat @llvm.used = appending global [1 x ptr] [ptr @.objc_ctor], section "llvm.metadata" @llvm.compiler.used = appending global [1 x ptr] [ptr @.objcv2_load_function], section "llvm.metadata" ; Function Attrs: noinline optnone uwtable define dso_local void @runCleanup(ptr noundef %0) #0 { %2 = alloca ptr, align 8 store ptr %0, ptr %2, align 8 %3 = load i32, ptr @cleanupRun, align 4 %4 = icmp eq i32 %3, 0 br i1 %4, label %7, label %5 5: ; preds = %1 call void @_assert(ptr noundef @.str, ptr noundef @.str.1, i32 noundef 18) #7 unreachable 6: ; No predecessors! br label %7 7: ; preds = %6, %1 %8 = phi i1 [ true, %1 ], [ false, %6 ] %9 = zext i1 %8 to i32 store i32 1, ptr @cleanupRun, align 4 ret void } ; Function Attrs: noreturn declare dllimport void @_assert(ptr noundef, ptr noundef, i32 noundef) #1 ; Function Attrs: noinline optnone uwtable define dso_local i32 @throw() #0 { %1 = call ptr @__acrt_iob_func(i32 noundef 2) %2 = call i32 (ptr, ptr, ...) @fprintf(ptr noundef %1, ptr noundef @.str.2) %3 = load ptr, ptr @"$_OBJC_REF_CLASS_Test", align 8 %4 = call ptr @objc_msgSend(ptr noundef %3, ptr noundef @".objc_selector_new_\0116\010:8"), !GNUObjCMessageSend !5 call void @objc_exception_throw(ptr %4) #7 unreachable } ; Function Attrs: noinline nounwind optnone uwtable define internal i32 @fprintf(ptr noundef %0, ptr noundef nonnull %1, ...) #2 { %3 = alloca ptr, align 8 %4 = alloca ptr, align 8 %5 = alloca i32, align 4 %6 = alloca ptr, align 8 store ptr %0, ptr %3, align 8 store ptr %1, ptr %4, align 8 call void @llvm.va_start(ptr %6) %7 = load ptr, ptr %3, align 8 %8 = load ptr, ptr %4, align 8 %9 = load ptr, ptr %6, align 8 %10 = call i32 @__mingw_vfprintf(ptr noundef %7, ptr noundef %8, ptr noundef %9) #8 store i32 %10, ptr %5, align 4 call void @llvm.va_end(ptr %6) %11 = load i32, ptr %5, align 4 ret i32 %11 } declare dllimport ptr @__acrt_iob_func(i32 noundef) #3 declare dso_local ptr @objc_msgSend(ptr, ...) declare dso_local void @objc_exception_throw(ptr) ; Function Attrs: noinline optnone uwtable define dso_local i32 @finally() #0 personality ptr @__gxx_personality_seh0 { %1 = alloca i32, align 4 %2 = alloca i1, align 1 %3 = alloca ptr, align 8 %4 = alloca i32, align 4 %5 = alloca i32, align 4 %6 = load i32, ptr %1, align 4 store i1 false, ptr %2, align 1 %7 = invoke i32 @throw() to label %8 unwind label %17 8: ; preds = %0 store i32 0, ptr %5, align 4 br label %9 9: ; preds = %8, %21 %10 = load i32, ptr %5, align 4 store i32 1, ptr @finallyEntered, align 4 %11 = load i1, ptr %2, align 1 br i1 %11, label %12, label %14 12: ; preds = %9 invoke void @objc_exception_rethrow() to label %13 unwind label %22 13: ; preds = %12 unreachable 14: ; preds = %9 store i32 %10, ptr %5, align 4 %15 = load i32, ptr %5, align 4 switch i32 %15, label %34 [ i32 0, label %16 i32 2, label %34 ] 16: ; preds = %14 store i32 1, ptr %5, align 4 call void @runCleanup(ptr noundef %1) ret i32 0 17: ; preds = %0 %18 = landingpad { ptr, i32 } catch ptr null %19 = extractvalue { ptr, i32 } %18, 0 store ptr %19, ptr %3, align 8 %20 = extractvalue { ptr, i32 } %18, 1 store i32 %20, ptr %4, align 4 br label %21 21: ; preds = %17 store i1 true, ptr %2, align 1 store i32 2, ptr %5, align 4 br label %9 22: ; preds = %12 %23 = landingpad { ptr, i32 } cleanup %24 = extractvalue { ptr, i32 } %23, 0 store ptr %24, ptr %3, align 8 %25 = extractvalue { ptr, i32 } %23, 1 store i32 %25, ptr %4, align 4 invoke void @runCleanup(ptr noundef %1) to label %26 unwind label %32 26: ; preds = %22 br label %27 27: ; preds = %26 %28 = load ptr, ptr %3, align 8 %29 = load i32, ptr %4, align 4 %30 = insertvalue { ptr, i32 } poison, ptr %28, 0 %31 = insertvalue { ptr, i32 } %30, i32 %29, 1 resume { ptr, i32 } %31 32: ; preds = %22 %33 = landingpad { ptr, i32 } catch ptr null call void @abort() #9 unreachable 34: ; preds = %14, %14 unreachable } declare dso_local void @objc_exception_rethrow() declare dso_local i32 @__gxx_personality_seh0(...) declare dso_local void @abort() ; Function Attrs: noinline optnone uwtable define dso_local i32 @rethrow_id() #0 personality ptr @__gxx_personality_seh0 { %1 = alloca ptr, align 8 %2 = alloca i32, align 4 %3 = alloca ptr, align 8 %4 = invoke i32 @finally() to label %5 unwind label %7 5: ; preds = %0 br label %6 6: ; preds = %5 ret i32 0 7: ; preds = %0 %8 = landingpad { ptr, i32 } catch ptr @__objc_id_type_info %9 = extractvalue { ptr, i32 } %8, 0 store ptr %9, ptr %1, align 8 %10 = extractvalue { ptr, i32 } %8, 1 store i32 %10, ptr %2, align 4 br label %11 11: ; preds = %7 %12 = load i32, ptr %2, align 4 %13 = call i32 @llvm.eh.typeid.for(ptr @__objc_id_type_info) #8 %14 = icmp eq i32 %12, %13 br i1 %14, label %15, label %29 15: ; preds = %11 %16 = load ptr, ptr %1, align 8 store ptr %16, ptr %3, align 8 %17 = load ptr, ptr %3, align 8 %18 = call ptr @object_getClass(ptr noundef %17) %19 = load ptr, ptr @"$_OBJC_REF_CLASS_Test", align 8 %20 = call ptr @objc_msgSend(ptr noundef %19, ptr noundef @".objc_selector_class_#16\010:8"), !GNUObjCMessageSend !6 %21 = icmp eq ptr %18, %20 br i1 %21, label %24, label %22 22: ; preds = %15 call void @_assert(ptr noundef @.str.3, ptr noundef @.str.1, i32 noundef 42) #7 unreachable 23: ; No predecessors! br label %24 24: ; preds = %23, %15 %25 = phi i1 [ true, %15 ], [ false, %23 ] %26 = zext i1 %25 to i32 store i32 1, ptr @idRethrown, align 4 %27 = call ptr @__acrt_iob_func(i32 noundef 2) %28 = call i32 (ptr, ptr, ...) @fprintf(ptr noundef %27, ptr noundef @.str.4) call void @objc_exception_throw(ptr %16) #7 unreachable 29: ; preds = %11 %30 = load ptr, ptr %1, align 8 %31 = load i32, ptr %2, align 4 %32 = insertvalue { ptr, i32 } poison, ptr %30, 0 %33 = insertvalue { ptr, i32 } %32, i32 %31, 1 resume { ptr, i32 } %33 } ; Function Attrs: nounwind memory(none) declare i32 @llvm.eh.typeid.for(ptr) #4 declare dllimport ptr @object_getClass(ptr noundef) #3 ; Function Attrs: noinline optnone uwtable define dso_local i32 @rethrow_test() #0 personality ptr @__gxx_personality_seh0 { %1 = alloca i32, align 4 %2 = alloca ptr, align 8 %3 = alloca i32, align 4 %4 = alloca ptr, align 8 %5 = alloca ptr, align 8 %6 = invoke i32 @rethrow_id() to label %7 unwind label %10 7: ; preds = %0 br label %8 8: ; preds = %7 %9 = load i32, ptr %1, align 4 ret i32 %9 10: ; preds = %0 %11 = landingpad { ptr, i32 } catch ptr @__objc_eh_typeinfo_Test catch ptr @__objc_id_type_info catch ptr null %12 = extractvalue { ptr, i32 } %11, 0 store ptr %12, ptr %2, align 8 %13 = extractvalue { ptr, i32 } %11, 1 store i32 %13, ptr %3, align 4 br label %14 14: ; preds = %10 %15 = load i32, ptr %3, align 4 %16 = call i32 @llvm.eh.typeid.for(ptr @__objc_eh_typeinfo_Test) #8 %17 = icmp eq i32 %15, %16 br i1 %17, label %21, label %18 18: ; preds = %14 %19 = call i32 @llvm.eh.typeid.for(ptr @__objc_id_type_info) #8 %20 = icmp eq i32 %15, %19 br i1 %20, label %25, label %29 21: ; preds = %14 %22 = load ptr, ptr %2, align 8 store ptr %22, ptr %4, align 8 store i32 1, ptr @testCaught, align 4 %23 = call ptr @__acrt_iob_func(i32 noundef 2) %24 = call i32 (ptr, ptr, ...) @fprintf(ptr noundef %23, ptr noundef @.str.5) call void @objc_exception_throw(ptr %22) #7 unreachable 25: ; preds = %18 %26 = load ptr, ptr %2, align 8 store ptr %26, ptr %5, align 8 %27 = call ptr @__acrt_iob_func(i32 noundef 2) %28 = call i32 (ptr, ptr, ...) @fprintf(ptr noundef %27, ptr noundef @.str.6) call void @_assert(ptr noundef @.str.7, ptr noundef @.str.1, i32 noundef 61) #7 unreachable 29: ; preds = %18 %30 = load ptr, ptr %2, align 8 %31 = call ptr @__acrt_iob_func(i32 noundef 2) %32 = call i32 (ptr, ptr, ...) @fprintf(ptr noundef %31, ptr noundef @.str.8) call void @_assert(ptr noundef @.str.7, ptr noundef @.str.1, i32 noundef 66) #7 unreachable } ; Function Attrs: noinline optnone uwtable define dso_local i32 @rethrow_catchall() #0 personality ptr @__gxx_personality_seh0 { %1 = alloca ptr, align 8 %2 = alloca i32, align 4 %3 = invoke i32 @rethrow_test() to label %4 unwind label %6 4: ; preds = %0 br label %5 5: ; preds = %4 ret i32 0 6: ; preds = %0 %7 = landingpad { ptr, i32 } catch ptr null %8 = extractvalue { ptr, i32 } %7, 0 store ptr %8, ptr %1, align 8 %9 = extractvalue { ptr, i32 } %7, 1 store i32 %9, ptr %2, align 4 br label %10 10: ; preds = %6 %11 = load ptr, ptr %1, align 8 %12 = load i32, ptr @testCaught, align 4 %13 = icmp ne i32 %12, 0 br i1 %13, label %16, label %14 14: ; preds = %10 call void @_assert(ptr noundef @.str.9, ptr noundef @.str.1, i32 noundef 74) #7 unreachable 15: ; No predecessors! br label %16 16: ; preds = %15, %10 %17 = phi i1 [ true, %10 ], [ false, %15 ] %18 = zext i1 %17 to i32 store i32 1, ptr @catchallRethrown, align 4 %19 = call ptr @__acrt_iob_func(i32 noundef 2) %20 = call i32 (ptr, ptr, ...) @fprintf(ptr noundef %19, ptr noundef @.str.10) call void @objc_exception_throw(ptr %11) #7 unreachable } ; Function Attrs: noinline optnone uwtable define dso_local i32 @not_matched_catch() #0 personality ptr @__gxx_personality_seh0 { %1 = alloca ptr, align 8 %2 = alloca i32, align 4 %3 = alloca ptr, align 8 %4 = invoke i32 @rethrow_catchall() to label %5 unwind label %7 5: ; preds = %0 br label %6 6: ; preds = %5, %15 ret i32 0 7: ; preds = %0 %8 = landingpad { ptr, i32 } catch ptr @__objc_eh_typeinfo_NSString %9 = extractvalue { ptr, i32 } %8, 0 store ptr %9, ptr %1, align 8 %10 = extractvalue { ptr, i32 } %8, 1 store i32 %10, ptr %2, align 4 br label %11 11: ; preds = %7 %12 = load i32, ptr %2, align 4 %13 = call i32 @llvm.eh.typeid.for(ptr @__objc_eh_typeinfo_NSString) #8 %14 = icmp eq i32 %12, %13 br i1 %14, label %15, label %17 15: ; preds = %11 %16 = load ptr, ptr %1, align 8 store ptr %16, ptr %3, align 8 store i32 1, ptr @wrongMatch, align 4 br label %6 17: ; preds = %11 %18 = load ptr, ptr %1, align 8 %19 = load i32, ptr %2, align 4 %20 = insertvalue { ptr, i32 } poison, ptr %18, 0 %21 = insertvalue { ptr, i32 } %20, i32 %19, 1 resume { ptr, i32 } %21 } ; Function Attrs: noinline optnone uwtable define dso_local i32 @main() #0 personality ptr @__gxx_personality_seh0 { %1 = alloca i32, align 4 %2 = alloca ptr, align 8 %3 = alloca i32, align 4 %4 = alloca ptr, align 8 store i32 0, ptr %1, align 4 %5 = invoke i32 @rethrow_catchall() to label %6 unwind label %8 6: ; preds = %0 br label %7 7: ; preds = %6, %60 ret i32 0 8: ; preds = %0 %9 = landingpad { ptr, i32 } catch ptr @__objc_id_type_info %10 = extractvalue { ptr, i32 } %9, 0 store ptr %10, ptr %2, align 8 %11 = extractvalue { ptr, i32 } %9, 1 store i32 %11, ptr %3, align 4 br label %12 12: ; preds = %8 %13 = load i32, ptr %3, align 4 %14 = call i32 @llvm.eh.typeid.for(ptr @__objc_id_type_info) #8 %15 = icmp eq i32 %13, %14 br i1 %15, label %16, label %64 16: ; preds = %12 %17 = load ptr, ptr %2, align 8 store ptr %17, ptr %4, align 8 %18 = load i32, ptr @finallyEntered, align 4 %19 = icmp eq i32 %18, 1 br i1 %19, label %22, label %20 20: ; preds = %16 call void @_assert(ptr noundef @.str.11, ptr noundef @.str.1, i32 noundef 99) #7 unreachable 21: ; No predecessors! br label %22 22: ; preds = %21, %16 %23 = phi i1 [ true, %16 ], [ false, %21 ] %24 = zext i1 %23 to i32 %25 = load i32, ptr @cleanupRun, align 4 %26 = icmp eq i32 %25, 1 br i1 %26, label %29, label %27 27: ; preds = %22 call void @_assert(ptr noundef @.str.12, ptr noundef @.str.1, i32 noundef 100) #7 unreachable 28: ; No predecessors! br label %29 29: ; preds = %28, %22 %30 = phi i1 [ true, %22 ], [ false, %28 ] %31 = zext i1 %30 to i32 %32 = load i32, ptr @idRethrown, align 4 %33 = icmp eq i32 %32, 1 br i1 %33, label %36, label %34 34: ; preds = %29 call void @_assert(ptr noundef @.str.13, ptr noundef @.str.1, i32 noundef 101) #7 unreachable 35: ; No predecessors! br label %36 36: ; preds = %35, %29 %37 = phi i1 [ true, %29 ], [ false, %35 ] %38 = zext i1 %37 to i32 %39 = load i32, ptr @catchallRethrown, align 4 %40 = icmp eq i32 %39, 1 br i1 %40, label %43, label %41 41: ; preds = %36 call void @_assert(ptr noundef @.str.14, ptr noundef @.str.1, i32 noundef 102) #7 unreachable 42: ; No predecessors! br label %43 43: ; preds = %42, %36 %44 = phi i1 [ true, %36 ], [ false, %42 ] %45 = zext i1 %44 to i32 %46 = load i32, ptr @wrongMatch, align 4 %47 = icmp eq i32 %46, 0 br i1 %47, label %50, label %48 48: ; preds = %43 call void @_assert(ptr noundef @.str.15, ptr noundef @.str.1, i32 noundef 103) #7 unreachable 49: ; No predecessors! br label %50 50: ; preds = %49, %43 %51 = phi i1 [ true, %43 ], [ false, %49 ] %52 = zext i1 %51 to i32 %53 = load ptr, ptr %4, align 8 %54 = call ptr @object_getClass(ptr noundef %53) %55 = load ptr, ptr @"$_OBJC_REF_CLASS_Test", align 8 %56 = call ptr @objc_msgSend(ptr noundef %55, ptr noundef @".objc_selector_class_#16\010:8"), !GNUObjCMessageSend !6 %57 = icmp eq ptr %54, %56 br i1 %57, label %60, label %58 58: ; preds = %50 call void @_assert(ptr noundef @.str.3, ptr noundef @.str.1, i32 noundef 104) #7 unreachable 59: ; No predecessors! br label %60 60: ; preds = %59, %50 %61 = phi i1 [ true, %50 ], [ false, %59 ] %62 = zext i1 %61 to i32 %63 = load ptr, ptr %4, align 8 call void @objc_msgSend(ptr noundef %63, ptr noundef @".objc_selector_dealloc_v16\010:8"), !GNUObjCMessageSend !7 br label %7 64: ; preds = %12 %65 = load ptr, ptr %2, align 8 %66 = load i32, ptr %3, align 4 %67 = insertvalue { ptr, i32 } poison, ptr %65, 0 %68 = insertvalue { ptr, i32 } %67, i32 %66, 1 resume { ptr, i32 } %68 } ; Function Attrs: nocallback nofree nosync nounwind willreturn declare void @llvm.va_start(ptr) #5 ; Function Attrs: nounwind declare dso_local i32 @__mingw_vfprintf(ptr noundef, ptr noundef, ptr noundef) #6 ; Function Attrs: nocallback nofree nosync nounwind willreturn declare void @llvm.va_end(ptr) #5 define linkonce_odr hidden void @.objcv2_load_function() comdat { call void @__objc_load(ptr @.objc_init) ret void } declare dso_local void @__objc_load(ptr) attributes #0 = { noinline optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } attributes #1 = { noreturn "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } attributes #2 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } attributes #3 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } attributes #4 = { nounwind memory(none) } attributes #5 = { nocallback nofree nosync nounwind willreturn } attributes #6 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } attributes #7 = { noreturn } attributes #8 = { nounwind } attributes #9 = { noreturn nounwind } !llvm.module.flags = !{!0, !1, !2, !3} !llvm.ident = !{!4} !0 = !{i32 1, !"wchar_size", i32 2} !1 = !{i32 8, !"PIC Level", i32 2} !2 = !{i32 7, !"uwtable", i32 2} !3 = !{i32 1, !"MaxTLSAlign", i32 65536} !4 = !{!"clang version 18.0.0git"} !5 = !{!"new", !"Test", i1 true} !6 = !{!"class", !"Test", i1 true} !7 = !{!"dealloc", !"", i1 false} ```
davidchisnall commented 6 months ago

Hmm, it looks as if clang isn't emitting the begin and end catch calls. In a finally block, I think it should be calling the _Unwind_rethrow thing (I think it was before the last change?). If I'm reading the IR correctly, @finally is emitted as a cleanup (which seems right) and so isn't rethrowing, it's resuming unwinding.

I'm a bit confused because it looks as if the finally code should be emitting the begin / end carch blocks:

https://github.com/llvm/llvm-project/blob/ab073cbccb6e79d8b65a286e8948bc1f07c7c09b/clang/lib/CodeGen/CGObjCRuntime.cpp#L157

Can you see what's going on there?

qmfrederik commented 6 months ago

OK - so we were entering the if(usesSEHExceptions) block which does not emit the try/catch blocks. Looks like we want a slightly different setup from the existing ones, where try/catch blocks are emitted and objc_exception_rethrow is called, like this: https://github.com/llvm/llvm-project/commit/b98340685303f18931b2e197aea3d13192373c48 ?

This causes the test to proceed a bit further:

throw: Throwing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0xfe10eea8 of type Test
objc_exception_rethrow: Entering
gnustep::libobjc::__objc_id_type_info::__do_catch: Entering
gnustep::libobjc::__objc_id_type_info::__do_catch: Type info @id
gnustep::libobjc::__objc_id_type_info::__do_catch: got id type info for 0xfe10eea8, returning true
rethrow_id: Rethrowing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0xfe10eea8 of type Test
gnustep::libobjc::__objc_class_type_info::__do_catch: Entering
gnustep::libobjc::__objc_class_type_info::__do_catch: Type info @id
gnustep::libobjc::__objc_class_type_info::__do_catch: object is an id type or class type and we're in Apple compat mode
gnustep::libobjc::__objc_class_type_info::__do_catch: got type Test
gnustep::libobjc::__objc_class_type_info::__do_catch: found is YES
gnustep::libobjc::__objc_class_type_info::__do_catch: found matching type
gnustep::libobjc::__objc_class_type_info::__do_catch: returning 1; *obj is 0xfe10eea8
eh_cleanup: Releasing exception 0xfe10eea8
rethrow_test: Rethrowing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0xfe10eea8 of type Test
eh_cleanup: Releasing exception 0xfe10eea8
rethrow_catchall: Rethrowing exception
objc_exception_throw: Entering
objc_exception_throw: Throwing exception 0xfe10ef70 of type (null)
Segmentation fault

Looks like the final block as a wrong pointer to the exception (I assume it should have been 0xfe10eea8 but we have 0xfe10ef70 instead)? Also, I've seen to different definitions for objc_exception_rethrow : void objc_exception_rethrow(void*) and void objc_exception_rethrow(void)?

qmfrederik commented 6 months ago

The segfault comes from the logging code which calls object_getClass; if I disable the logging code the test will fail on assert(object_getClass(x) == [Test class]); all of this I assume because something goes wrong here:

    @catch(...)
    {
        @throw;
    }

resulting in an invalid exception object being thrown?

qmfrederik commented 6 months ago

OK - so with https://github.com/llvm/llvm-project/commit/53132dab6c8dfcfd0d35efa53f4208b1b6dac388 I get all tests, except for UnexpectedException, to pass. ExceptionReThrowFn was set to objc_exception_throw instead of objc_exception_rethrow, which probably explained the behavior we've seen earlier.

davidchisnall commented 6 months ago

Nice! Can you add a clang test and raise a PR? I’ll review it.

You can base the test on this:

https://github.com/llvm/llvm-project/blob/main/clang/test/CodeGenObjC/exceptions-nonfragile.m

Pass some different target triples and runtime versions and make sure that it does the right thing in each case. You can run a single test with llvm-lit.

qmfrederik commented 6 months ago

Will do, thanks. Here's all current changes squashed into a single commit: https://github.com/llvm/llvm-project/commit/fa97083a5e87929fc1cf652cb9563e31a74457dd

qmfrederik commented 6 months ago

@davidchisnall Here you go: https://github.com/llvm/llvm-project/pull/77255

davidchisnall commented 6 months ago

Any ideas why the uncaught exception tests are failing? Does the VEH hook not do the right thing?

qmfrederik commented 6 months ago

The hook is not implemented yet, all it does (for now) is to return EXCEPTION_CONTINUE_SEARCH.

I haven't had the time yet to read up on the semantics of Vectored Exception Handlers (the documentation seems sparse) and the expected behavior of objc_setUncaughtExceptionHandler.

Where I left it at the moment:

davidchisnall commented 6 months ago

Seems VEHs are invoked whenever an exception is raised (not just when it is unhandled); so probably need to do some filtering?

I think they're called before SEH, which is why I was unsure how it worked. I'm not sure if you get an unhandled-exception exception that you can catch with VEH?

Should the callback provided by objc_setUncaughtExceptionHandler be called for all exceptions (including C++) or only ObjC exceptions?

Well, that gets complicated. Probably only for ObjC ones, because C++ already has it defined that it calls std::terminate on unwind failure.

It would be nice to call std::set_terminate and wrap the existing handler in one that calls the ObjC handler if we're in the middle of an ObjC exception throw, but I don't think that's actually possible.

I assume only for ObjC exceptions as it has an id exception argument? How can we reliably know the exception is an ObjC exception? Do we need to pass additional metadata to __cxa_throw (not just an id)

I think the type info is embedded in the thrown object. It's a std::type_info subclass and, for ObjC, we use exactly one so you can just check if the id type info is thrown.

The corner case here is that you can use throw in Objective-C++ to throw an Objective-C object. This will throw it with one of the other type infos. I don't know whether I'd expect that to call the unhandled handler or not. Worth checking what macOS does.

qmfrederik commented 6 months ago

@davidchisnall Looks like the easiest way to do is to:

I've pushed a commit which implements this.

Feels a bit clunky (but it's similar to what the Apple runtime does); but I couldn't find an easier way to get this information. I believe ex->ExceptionInformation[0] is a _Unwind_Exception, but that only exposes an exception_class and not the actual exception?

davidchisnall commented 6 months ago

That’s probably easier. The unwind exception holds a pointer to the C++ exception, which holds the type info and has the object appended, but different C++ runtimes have subtly different versions of this structure. Given that this is a very slow path (basically only ever used to pop up a diagnostic window when a thread crashes) it’ probably fine for it to do the rethrow thing.

qmfrederik commented 6 months ago

@davidchisnall I've made some last changes to this PR:

With that, all tests on MinGW now pass (100% tests passed, 0 tests failed out of 98):

``` Test project C:/tools/msys64/home/vagrant/git/libobjc2/build Start 1: alias 1/98 Test #1: alias ...................................... Passed 0.12 sec Start 2: alias_optimised 2/98 Test #2: alias_optimised ............................ Passed 0.02 sec Start 3: alignTest 3/98 Test #3: alignTest .................................. Passed 0.02 sec Start 4: alignTest_optimised 4/98 Test #4: alignTest_optimised ........................ Passed 0.02 sec Start 5: AllocatePair 5/98 Test #5: AllocatePair ............................... Passed 0.02 sec Start 6: AllocatePair_optimised 6/98 Test #6: AllocatePair_optimised ..................... Passed 0.03 sec Start 7: AssociatedObject 7/98 Test #7: AssociatedObject ........................... Passed 0.03 sec Start 8: AssociatedObject_optimised 8/98 Test #8: AssociatedObject_optimised ................. Passed 0.03 sec Start 9: AssociatedObject2 9/98 Test #9: AssociatedObject2 .......................... Passed 0.03 sec Start 10: AssociatedObject2_optimised 10/98 Test #10: AssociatedObject2_optimised ................ Passed 0.03 sec Start 11: BlockImpTest 11/98 Test #11: BlockImpTest ............................... Passed 0.03 sec Start 12: BlockImpTest_optimised 12/98 Test #12: BlockImpTest_optimised ..................... Passed 0.03 sec Start 13: BlockTest_arc 13/98 Test #13: BlockTest_arc .............................. Passed 0.03 sec Start 14: BlockTest_arc_optimised 14/98 Test #14: BlockTest_arc_optimised .................... Passed 0.07 sec Start 15: ConstantString 15/98 Test #15: ConstantString ............................. Passed 0.05 sec Start 16: ConstantString_optimised 16/98 Test #16: ConstantString_optimised ................... Passed 0.05 sec Start 17: Category 17/98 Test #17: Category ................................... Passed 0.02 sec 18/98 Test #18: Category_optimised ......................... Passed 0.02 sec Start 19: ExceptionTest 19/98 Test #19: ExceptionTest .............................. Passed 0.03 sec Start 20: ExceptionTest_optimised 20/98 Test #20: ExceptionTest_optimised .................... Passed 0.03 sec Start 21: FastARC 21/98 Test #21: FastARC .................................... Passed 0.03 sec Start 22: FastARC_optimised 22/98 Test #22: FastARC_optimised .......................... Passed 0.04 sec Start 23: FastARCPool 23/98 Test #23: FastARCPool ................................ Passed 0.03 sec Start 24: FastARCPool_optimised 24/98 Test #24: FastARCPool_optimised ...................... Passed 0.03 sec Start 25: FastRefCount 25/98 Test #25: FastRefCount ............................... Passed 0.03 sec Start 26: FastRefCount_optimised 26/98 Test #26: FastRefCount_optimised ..................... Passed 0.02 sec Start 27: Forward 27/98 Test #27: Forward .................................... Passed 0.03 sec Start 28: Forward_optimised 28/98 Test #28: Forward_optimised .......................... Passed 0.03 sec Start 29: ManyManySelectors 29/98 Test #29: ManyManySelectors .......................... Passed 1.46 sec Start 30: ManyManySelectors_optimised 30/98 Test #30: ManyManySelectors_optimised ................ Passed 1.45 sec Start 31: NestedExceptions 31/98 Test #31: NestedExceptions ........................... Passed 0.02 sec Start 32: NestedExceptions_optimised 32/98 Test #32: NestedExceptions_optimised ................. Passed 0.02 sec Start 33: PropertyAttributeTest 33/98 Test #33: PropertyAttributeTest ...................... Passed 0.02 sec Start 34: PropertyAttributeTest_optimised 34/98 Test #34: PropertyAttributeTest_optimised ............ Passed 0.03 sec Start 35: ProtocolExtendedProperties 35/98 Test #35: ProtocolExtendedProperties ................. Passed 0.02 sec Start 36: ProtocolExtendedProperties_optimised 36/98 Test #36: ProtocolExtendedProperties_optimised ....... Passed 0.03 sec Start 37: PropertyIntrospectionTest 37/98 Test #37: PropertyIntrospectionTest .................. Passed 0.09 sec Start 38: PropertyIntrospectionTest_optimised 38/98 Test #38: PropertyIntrospectionTest_optimised ........ Passed 0.03 sec Start 39: ProtocolCreation 39/98 Test #39: ProtocolCreation ........................... Passed 0.04 sec Start 40: ProtocolCreation_optimised 40/98 Test #40: ProtocolCreation_optimised ................. Passed 0.03 sec Start 41: ResurrectInDealloc_arc 41/98 Test #41: ResurrectInDealloc_arc ..................... Passed 0.02 sec Start 42: ResurrectInDealloc_arc_optimised 42/98 Test #42: ResurrectInDealloc_arc_optimised ........... Passed 0.02 sec Start 43: RuntimeTest 43/98 Test #43: RuntimeTest ................................ Passed 0.02 sec Start 44: RuntimeTest_optimised 44/98 Test #44: RuntimeTest_optimised ...................... Passed 0.02 sec Start 45: SuperMethodMissing 45/98 Test #45: SuperMethodMissing ......................... Passed 0.05 sec Start 46: SuperMethodMissing_optimised 46/98 Test #46: SuperMethodMissing_optimised ............... Passed 0.04 sec Start 47: WeakBlock_arc 47/98 Test #47: WeakBlock_arc .............................. Passed 0.04 sec Start 48: WeakBlock_arc_optimised 48/98 Test #48: WeakBlock_arc_optimised .................... Passed 0.05 sec Start 49: WeakRefLoad 49/98 Test #49: WeakRefLoad ................................ Passed 0.02 sec Start 50: WeakRefLoad_optimised 50/98 Test #50: WeakRefLoad_optimised ...................... Passed 0.02 sec Start 51: WeakReferences_arc 51/98 Test #51: WeakReferences_arc ......................... Passed 2.52 sec Start 52: WeakReferences_arc_optimised 52/98 Test #52: WeakReferences_arc_optimised ............... Passed 2.84 sec Start 53: WeakImportClass 53/98 Test #53: WeakImportClass ............................ Passed 0.02 sec Start 54: WeakImportClass_optimised 54/98 Test #54: WeakImportClass_optimised .................. Passed 0.02 sec Start 55: ivar_arc 55/98 Test #55: ivar_arc ................................... Passed 0.02 sec Start 56: ivar_arc_optimised 56/98 Test #56: ivar_arc_optimised ......................... Passed 0.02 sec Start 57: ivar_atomic 57/98 Test #57: ivar_atomic ................................ Passed 0.02 sec Start 58: ivar_atomic_optimised 58/98 Test #58: ivar_atomic_optimised ...................... Passed 0.02 sec Start 59: IVarOverlap 59/98 Test #59: IVarOverlap ................................ Passed 0.03 sec Start 60: IVarOverlap_optimised 60/98 Test #60: IVarOverlap_optimised ...................... Passed 0.03 sec Start 61: IVarSuperclassOverlap 61/98 Test #61: IVarSuperclassOverlap ...................... Passed 0.04 sec Start 62: IVarSuperclassOverlap_optimised 62/98 Test #62: IVarSuperclassOverlap_optimised ............ Passed 0.02 sec Start 63: objc_msgSend 63/98 Test #63: objc_msgSend ............................... Passed 0.03 sec Start 64: objc_msgSend_optimised 64/98 Test #64: objc_msgSend_optimised ..................... Passed 0.02 sec Start 65: msgInterpose 65/98 Test #65: msgInterpose ............................... Passed 0.03 sec Start 66: msgInterpose_optimised 66/98 Test #66: msgInterpose_optimised ..................... Passed 0.02 sec Start 67: NilException 67/98 Test #67: NilException ............................... Passed 0.02 sec Start 68: NilException_optimised 68/98 Test #68: NilException_optimised ..................... Passed 0.03 sec Start 69: MethodArguments 69/98 Test #69: MethodArguments ............................ Passed 0.02 sec Start 70: MethodArguments_optimised 70/98 Test #70: MethodArguments_optimised .................. Passed 0.03 sec Start 71: zeroSizedIVar 71/98 Test #71: zeroSizedIVar .............................. Passed 0.02 sec Start 72: zeroSizedIVar_optimised 72/98 Test #72: zeroSizedIVar_optimised .................... Passed 0.02 sec Start 73: exchange 73/98 Test #73: exchange ................................... Passed 0.02 sec Start 74: exchange_optimised 74/98 Test #74: exchange_optimised ......................... Passed 0.03 sec Start 75: hash_table_delete 75/98 Test #75: hash_table_delete .......................... Passed 0.02 sec Start 76: hash_table_delete_optimised 76/98 Test #76: hash_table_delete_optimised ................ Passed 0.02 sec Start 77: hash_test 77/98 Test #77: hash_test .................................. Passed 2.02 sec Start 78: hash_test_optimised 78/98 Test #78: hash_test_optimised ........................ Passed 0.58 sec Start 79: setSuperclass 79/98 Test #79: setSuperclass .............................. Passed 0.02 sec Start 80: setSuperclass_optimised 80/98 Test #80: setSuperclass_optimised .................... Passed 0.03 sec Start 81: UnexpectedException 81/98 Test #81: UnexpectedException ........................ Passed 0.05 sec Start 82: UnexpectedException_optimised 82/98 Test #82: UnexpectedException_optimised .............. Passed 0.08 sec Start 83: ARCTest_arc 83/98 Test #83: ARCTest_arc ................................ Passed 0.02 sec Start 84: ARCTest_arc_optimised 84/98 Test #84: ARCTest_arc_optimised ...................... Passed 0.02 sec Start 85: PropertyIntrospectionTest2_arc 85/98 Test #85: PropertyIntrospectionTest2_arc ............. Passed 0.02 sec Start 86: PropertyIntrospectionTest2_arc_optimised 86/98 Test #86: PropertyIntrospectionTest2_arc_optimised ... Passed 0.02 sec Start 87: category_properties 87/98 Test #87: category_properties ........................ Passed 0.02 sec Start 88: category_properties_optimised 88/98 Test #88: category_properties_optimised .............. Passed 0.03 sec Start 89: CXXExceptions 89/98 Test #89: CXXExceptions .............................. Passed 0.02 sec Start 90: CXXExceptions_optimised 90/98 Test #90: CXXExceptions_optimised .................... Passed 0.02 sec Start 91: ForwardDeclareProtocolAccess 91/98 Test #91: ForwardDeclareProtocolAccess ............... Passed 0.02 sec Start 92: ForwardDeclareProtocolAccess_optimised 92/98 Test #92: ForwardDeclareProtocolAccess_optimised ..... Passed 0.02 sec Start 93: ObjCXXEHInterop 93/98 Test #93: ObjCXXEHInterop ............................ Passed 0.02 sec Start 94: ObjCXXEHInterop_optimised 94/98 Test #94: ObjCXXEHInterop_optimised .................. Passed 0.02 sec Start 95: ObjCXXEHInteropTwice 95/98 Test #95: ObjCXXEHInteropTwice ....................... Passed 0.02 sec Start 96: ObjCXXEHInteropTwice_optimised 96/98 Test #96: ObjCXXEHInteropTwice_optimised ............. Passed 0.02 sec Start 97: ObjCXXEHInterop_arc 97/98 Test #97: ObjCXXEHInterop_arc ........................ Passed 0.02 sec Start 98: ObjCXXEHInterop_arc_optimised 98/98 Test #98: ObjCXXEHInterop_arc_optimised .............. Passed 0.02 sec 100% tests passed, 0 tests failed out of 98 Total Test time (real) = 14.17 sec ```

I think this PR is now in a pretty good shape, let me know if there's anything else you need from me.

davidchisnall commented 6 months ago

Please can you squash before you merge?

qmfrederik commented 6 months ago

@davidchisnall I left some additional notes, and I updated UnexpectedException.m to assert that handler is invoked for unhandled exceptions only.

qmfrederik commented 6 months ago

Please can you squash before you merge?

I squashed but don't have merge permissions in this repo ;-)

davidchisnall commented 6 months ago

Please can you squash before you merge?

I squashed but don't have merge permissions in this repo ;-)

Fixed.

qmfrederik commented 6 months ago

Cool, thanks!