Skip to content

Commit

Permalink
Use C++ exceptions unconditionally for Objective-C[++] on MinGW (#267)
Browse files Browse the repository at this point in the history
  • Loading branch information
qmfrederik authored Jan 10, 2024
1 parent 3c42c64 commit 6528090
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 11 deletions.
9 changes: 7 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
25 changes: 17 additions & 8 deletions Test/UnexpectedException.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@
#include <Windows.h>
#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);
Expand All @@ -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
}
59 changes: 59 additions & 0 deletions eh_win32_mingw.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#include "objc/runtime.h"
#include "objc/objc-exception.h"
#include "objc/hooks.h"
#include <stdio.h>

#include <windows.h>

#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;
}
23 changes: 22 additions & 1 deletion objcxx_eh.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -99,6 +100,9 @@ namespace std
};
}

extern "C" void __cxa_throw(void*, std::type_info*, void(*)(void*));
extern "C" void __cxa_rethrow();

namespace
{
/**
Expand Down Expand Up @@ -340,6 +344,7 @@ bool gnustep::libobjc::__objc_class_type_info::__do_catch(const type_info *throw
{
*obj = (void*)thrown;
}

return found;
};

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand All @@ -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

0 comments on commit 6528090

Please sign in to comment.