diff --git a/CMakeLists.txt b/CMakeLists.txt index 5009fd5..48a1436 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,9 +117,11 @@ set(libobjc_CXX_SRCS # Windows does not use DWARF EH, except when using the GNU ABI (MinGW) if (WIN32 AND NOT MINGW) list(APPEND libobjc_CXX_SRCS eh_win32_msvc.cc) +elseif (MINGW) + list(APPEND libobjc_CXX_SRCS eh_win32_mingw.m) else () list(APPEND libobjc_C_SRCS eh_personality.c) -endif (WIN32 AND NOT MINGW) +endif () find_package(tsl-robin-map) @@ -132,7 +134,7 @@ if (NOT tls-robin-map_FOUND) FetchContent_MakeAvailable(robinmap) endif() -if (WIN32 AND NOT MINGW) +if (WIN32) set(OLD_ABI_COMPAT_DEFAULT false) else() set(OLD_ABI_COMPAT_DEFAULT true) @@ -211,6 +213,9 @@ endif() if (WIN32 AND NOT MINGW) message(STATUS "Using MSVC-compatible exception model") +elseif (MINGW) + message(STATUS "Using MinGW-compatible exception model") + list(APPEND libobjc_CXX_SRCS objcxx_eh.cc) else () separate_arguments(EH_PERSONALITY_FLAGS NATIVE_COMMAND ${CMAKE_CXX_FLAGS}) if (CMAKE_CXX_COMPILER_TARGET) diff --git a/Test/UnexpectedException.m b/Test/UnexpectedException.m index 61b9d89..10d210d 100644 --- a/Test/UnexpectedException.m +++ b/Test/UnexpectedException.m @@ -8,19 +8,20 @@ #include #endif -id exceptionObj = @"Exception"; +id expectedExceptionObj = @"ExpectedException"; +id unexpectedExceptionObj = @"UnexpectedException"; void _UncaughtExceptionHandler(id exception) { - assert(exception == exceptionObj); -#ifdef _WIN32 + assert(exception == unexpectedExceptionObj); +#if defined(_WIN32) && !defined(__MINGW32__) // on Windows we will exit in _UnhandledExceptionFilter() below #else exit(0); #endif } -#ifdef _WIN32 +#if defined(_WIN32) && !defined(__MINGW32__) LONG WINAPI _UnhandledExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { assert(exceptionInfo != NULL); @@ -30,18 +31,26 @@ LONG WINAPI _UnhandledExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) int main(void) { - #if !(defined(__arm__) || defined(__ARM_ARCH_ISA_A64)) -#ifdef _WIN32 +#if !(defined(__arm__) || defined(__ARM_ARCH_ISA_A64)) +#if defined(_WIN32) && !defined(__MINGW32__) // also verify that an existing handler still gets called after we set ours SetUnhandledExceptionFilter(&_UnhandledExceptionFilter); #endif + @try + { + @throw expectedExceptionObj; + } + @catch(id exception) + { + assert(exception == expectedExceptionObj); + } objc_setUncaughtExceptionHandler(_UncaughtExceptionHandler); - @throw exceptionObj; + @throw unexpectedExceptionObj; assert(0 && "should not be reached!"); return -1; - #endif +#endif // FIXME: Test currently fails on ARM and AArch64 return 77; // Skip test } diff --git a/eh_win32_mingw.m b/eh_win32_mingw.m new file mode 100644 index 0000000..687913c --- /dev/null +++ b/eh_win32_mingw.m @@ -0,0 +1,59 @@ +#include "objc/runtime.h" +#include "objc/objc-exception.h" +#include "objc/hooks.h" +#include + +#include + +#define STATUS_GCC_THROW 0x20474343 + +extern void *__cxa_current_exception_type(void); +extern void __cxa_rethrow(); + +BOOL handler_installed = NO; +_Thread_local BOOL in_handler = NO; + +// This vectored exception handler is the last handler to get invoke for every exception (Objective C or foreign). +// It calls _objc_unexpected_exception only when the exception is a C++ exception (ex->ExceptionCode == STATUS_GCC_THROW) +// and the exception is an Objective C exception. +// It always returns EXCEPTION_CONTINUE_SEARCH, so Windows will continue handling the exception. +static LONG CALLBACK _objc_vectored_exception_handler(EXCEPTION_POINTERS* exceptionInfo) +{ + const EXCEPTION_RECORD* ex = exceptionInfo->ExceptionRecord; + + if (_objc_unexpected_exception != 0 + && ex->ExceptionCode == STATUS_GCC_THROW + && !in_handler) + { + // Rethrow the current exception and use the @catch clauses to determine whether it's an Objective C exception + // or a foreign exception. + if (__cxa_current_exception_type()) { + in_handler = YES; + @try { + __cxa_rethrow(); + } @catch (id e) { + // Invoke _objc_unexpected_exception for Objective C exceptions + (*_objc_unexpected_exception)((id)e); + } @catch (...) { + // Ignore foreign exceptions. + } + in_handler = NO; + } + } + + // EXCEPTION_CONTINUE_SEARCH instructs the exception handler to continue searching for appropriate exception handlers. + return EXCEPTION_CONTINUE_SEARCH; +} + +OBJC_PUBLIC extern objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler handler) +{ + objc_uncaught_exception_handler previousHandler = __atomic_exchange_n(&_objc_unexpected_exception, handler, __ATOMIC_SEQ_CST); + + // Add a vectored exception handler to support the hook. We only need to do this once. + if (!handler_installed) { + AddVectoredExceptionHandler(0 /* The handler is the last handler to be called */ , _objc_vectored_exception_handler); + handler_installed = YES; + } + + return previousHandler; +} diff --git a/objcxx_eh.cc b/objcxx_eh.cc index a8b7570..f8c0f6d 100644 --- a/objcxx_eh.cc +++ b/objcxx_eh.cc @@ -6,6 +6,7 @@ typedef struct objc_object* id; #include "objcxx_eh.h" #include "visibility.h" #include "objc/runtime.h" +#include "objc/objc-arc.h" /** * Helper function that has a custom personality function. @@ -99,6 +100,9 @@ namespace std }; } +extern "C" void __cxa_throw(void*, std::type_info*, void(*)(void*)); +extern "C" void __cxa_rethrow(); + namespace { /** @@ -340,6 +344,7 @@ bool gnustep::libobjc::__objc_class_type_info::__do_catch(const type_info *throw { *obj = (void*)thrown; } + return found; }; @@ -369,7 +374,7 @@ extern "C" /** * The public symbol that the compiler uses to indicate the Objective-C id type. */ -gnustep::libobjc::__objc_id_type_info __objc_id_type_info; +OBJC_PUBLIC gnustep::libobjc::__objc_id_type_info __objc_id_type_info; struct _Unwind_Exception *objc_init_cxx_exception(id obj) { @@ -468,6 +473,7 @@ BEGIN_PERSONALITY_FUNCTION(test_eh_personality) * personality function, allowing us to inspect a C++ exception that is in a * known state. */ +#ifndef __MINGW32__ extern "C" void test_cxx_eh_implementation() { if (done_setup) @@ -485,4 +491,19 @@ extern "C" void test_cxx_eh_implementation() } assert(caught); } +#else +static void eh_cleanup(void *exception) +{ + objc_release(*(id*)exception); +} +extern "C" +OBJC_PUBLIC +void objc_exception_throw(id object) +{ + id *exc = (id *)__cxa_allocate_exception(sizeof(id)); + *exc = object; + objc_retain(object); + __cxa_throw(exc, & __objc_id_type_info, eh_cleanup); +} +#endif