Implemented the new exception ABI, and a load of tests for it. This is used by

clang trunk if -fobjc-runtime-1.7 is specified and provides significantly
better interoperability with foreign exceptions.

Note: Most of the exception tests will not pass with gcc or clang < 3.3.  They
test things that are impossible to implement quite correctly with the
GCC-compatible exception ABI.

Also updated the release notes to reflect recent improvements.
main
theraven 13 years ago
parent 7e5d437065
commit 6317b79a10

@ -3,20 +3,33 @@ GNUstep Objective-C Runtime 1.7
This is a point release to the eighth official release of the GNUstep
Objective-C runtime (a.k.a. libobjc2). This runtime was designed to support
the features of Objective-C 2 for use with GNUstep and other Objective-C
programs. Highlights of this release include:
the features of modern dialects of Objective-C for use with GNUstep and other
Objective-C programs. Highlights of this release include:
- A new CMake-based build system
- A new CMake-based build system. This makes all of the configurable options
available via a clean interface. CPack is supported for building RPM and DEB
packages out of the box.
- A new CTest-based test suite, replacing the old ad-hoc tests.
- Build a single libobjc with support for Objective-C++ on platforms where a
C++ ABI library (libcxxrt or libsupc++) is installed as a shared library.
- Added specialised property accessor functions and support for atomic
properties with C++ non-POD types.
- A new exception implementation providing better integration with foreign
exceptions (e.g. C++ exceptions) (TODO)
exceptions (e.g. C++ exceptions). The new ABI is supported by clang 3.3 when
compiling with -fobjc-runtime=gnustep-1.7 (or higher). The old ABI is still
supported and both can be used within the same program, however code compiled
with the old ABI remains unreliable in the presence of foreign exceptions.
It is strongly recommended that anyone using exceptions with Objective-C++
switches to the new version.
- MIPS64 support in the assembly routines. (TODO)
- MIPS64 support in the assembly routines (TODO)
- Updated optimisation passes to work with LLVM 3.2 and recent LLVM trunk.
- Updated optimisation passes to work with LLVM 3.2
You may obtain the code for this release from subversion at the following
subversion branch:

@ -0,0 +1,108 @@
#include "../unwind.h"
#include "Test.h"
#include <objc/hooks.h>
#include <stdlib.h>
#include <stdio.h>
struct foreign_exception
{
struct _Unwind_Exception header;
int x;
};
BOOL finally_called = NO;
int throw(void)
{
struct foreign_exception *foreign_exception = calloc(sizeof(struct foreign_exception), 1);
foreign_exception->header.exception_class = 42;
foreign_exception->x = 12;
_Unwind_RaiseException(&foreign_exception->header);
assert(0);
}
void finally(void)
{
@try
{
throw();
}
@finally
{
finally_called = YES;
}
finally_called = NO;
}
@interface BoxedException : Test
{
struct foreign_exception *exception;
}
- (int)value;
@end
@implementation BoxedException
+ (id) exceptionWithForeignException: (struct _Unwind_Exception*)ex
{
BoxedException *b = [BoxedException new];
fprintf(stderr, "Foreign exception allocated b: %p (isa: %p)\n", b, b->isa);
b->exception = (struct foreign_exception*)ex;
return b;
}
- (void)dealloc
{
fprintf(stderr, "Foreign exception deallocated\n");
free(exception);
[super dealloc];
}
- (int)value
{
if (exception)
{
return exception->x;
}
return -1;
}
- (void)rethrow
{
struct _Unwind_Exception *ex = &exception->header;
exception = 0;
[self dealloc];
fprintf(stderr, "Foreign exception rethrown\n");
_Unwind_Resume_or_Rethrow(ex);
abort();
}
@end
Class boxer(int64_t class)
{
assert(class == 42);
return [BoxedException class];
}
int main(void)
{
_objc_class_for_boxing_foreign_exception = boxer;
BOOL catchall = NO;
BOOL catchboxed = NO;
@try
{
finally();
}
@catch (BoxedException *x)
{
assert(x != nil);
assert([x value] == 12);
[x dealloc];
catchboxed = YES;
}
@catch(...)
{
catchall = YES;
}
assert(finally_called == YES);
assert(catchall == NO);
assert(catchboxed == YES);
return 0;
}

@ -3,8 +3,13 @@
# the installed version
# List of single-file tests.
set(TESTS
BlockImpTest.m
BoxedForeignException.m
ExceptionTest.m
ForeignException.m
NestedExceptions.m
PropertyAttributeTest.m
PropertyIntrospectionTest.m
ProtocolCreation.m
@ -12,23 +17,29 @@ set(TESTS
objc_msgSend.m
)
#ExceptionTest.m
#PropertyAttributeTest.m
#RuntimeTest.m
foreach(TEST_SOURCE ${TESTS})
get_filename_component(TEST ${TEST_SOURCE} NAME_WE)
# Function for adding a test. This takes the name of the test and the list of
# source files as arguments.
function(addtest_flags TEST FLAGS TEST_SOURCE)
add_executable(${TEST} ${TEST_SOURCE})
add_test(${TEST} ${TEST})
set_target_properties(${TEST} PROPERTIES
INCLUDE_DIRECTORIES ".."
COMPILE_FLAGS "-fobjc-runtime=gnustep-1.6 -fblocks"
LANGUAGE C
COMPILE_FLAGS "-fobjc-runtime=gnustep-1.7 -fblocks ${FLAGS}"
LINKER_LANGUAGE C
)
set_property(TEST ${TEST} PROPERTY
ENVIRONMENT "LD_LIBRARY_PATH="
)
target_link_libraries(${TEST} objc)
endfunction(addtest_flags)
foreach(TEST_SOURCE ${TESTS})
get_filename_component(TEST ${TEST_SOURCE} NAME_WE)
addtest_flags(${TEST} "-O0" ${TEST_SOURCE})
addtest_flags("${TEST}_optimised" "-O3" ${TEST_SOURCE})
endforeach()
# Tests that are more than a single file.
addtest_flags(CXXExceptions "-O0" "CXXException.m;CXXException.cc")
addtest_flags(CXXExceptions_optimised "-O3" "CXXException.m;CXXException.cc")

@ -0,0 +1,72 @@
#include "Test.h"
#include "../unwind.h"
#if __cplusplus
#error This is not an ObjC++ test!
#endif
struct
{
struct _Unwind_Exception header;
id x;
} foreign_exception;
BOOL finally_called = NO;
int id_catchall;
id e1;
void throw_id(void)
{
@throw e1;
}
void throw_int(void);
int catchall(void);
void finally(void)
{
@try
{
throw_int();
}
@finally
{
finally_called = YES;
}
finally_called = NO;
}
int main(void)
{
BOOL catchall_entered = NO;
BOOL catchid = YES;
e1 = [Test new];
@try
{
finally();
}
@catch (id x)
{
assert(0);
}
@catch(...)
{
catchall_entered = YES;
}
assert(finally_called == YES);
assert(catchall_entered == YES);
@try
{
catchall();
}
@catch (id x)
{
assert(x == e1);
}
assert(catchid == YES);
assert(id_catchall == 1);
[e1 dealloc];
return 0;
}

@ -0,0 +1,81 @@
#include "Test.h"
#if __cplusplus
#error This is not an ObjC++ test!
#endif
BOOL finallyEntered = NO;
BOOL cleanupRun = NO;
BOOL idRethrown = NO;
BOOL catchallRethrown = NO;
BOOL wrongMatch = NO;
@interface NSString : Test @end
void runCleanup(void *x)
{
assert(cleanupRun == NO);
cleanupRun = YES;
}
int throw(void)
{
@throw [Test new];
}
int finally(void)
{
__attribute__((cleanup(runCleanup)))
int x;
@try { throw(); }
@finally { finallyEntered = YES; }
return 0;
}
int rethrow_id(void)
{
@try { finally(); }
@catch(id x)
{
assert(object_getClass(x) == [Test class]);
idRethrown = YES;
@throw;
}
return 0;
}
int rethrow_catchall(void)
{
@try { rethrow_id(); }
@catch(...)
{
catchallRethrown = YES;
@throw;
}
return 0;
}
int not_matched_catch(void)
{
@try { rethrow_catchall(); }
@catch(NSString *s)
{
wrongMatch = YES;
}
return 0;
}
int main(void)
{
@try
{
rethrow_catchall();
}
@catch (id x)
{
assert(finallyEntered == YES);
assert(cleanupRun == YES);
assert(idRethrown == YES);
assert(catchallRethrown == YES);
assert(wrongMatch == NO);
assert(object_getClass(x) == [Test class]);
[x dealloc];
}
return 0;
}

@ -0,0 +1,65 @@
#include "Test.h"
#include "../unwind.h"
#if __cplusplus
#error This is not an ObjC++ test!
#endif
struct
{
struct _Unwind_Exception header;
id x;
} foreign_exception;
BOOL cleanup_called = NO;
BOOL finally_called = NO;
static void cleanup(_Unwind_Reason_Code i,struct _Unwind_Exception *e)
{
assert(e == &foreign_exception.header);
cleanup_called = YES;
}
int throw(void)
{
foreign_exception.header.exception_class = 42;
foreign_exception.header.exception_cleanup = cleanup;
foreign_exception.x = (id)12;
_Unwind_RaiseException(&foreign_exception.header);
assert(0);
}
void finally(void)
{
@try
{
throw();
}
@finally
{
finally_called = YES;
}
finally_called = NO;
}
int main(void)
{
BOOL catchall = NO;
@try
{
finally();
}
@catch (id x)
{
assert(0);
}
@catch(...)
{
catchall = YES;
}
assert(finally_called == YES);
assert(catchall == YES);
assert(cleanup_called == YES);
return 0;
}

@ -0,0 +1,38 @@
#include "Test.h"
#if __cplusplus
#error This is not an ObjC++ test!
#endif
id a;
int throw(void)
{
@throw a;
}
int main(void)
{
id e1 = [Test new];
id e2 = [Test new];
@try
{
a = e1;
throw();
}
@catch (id x)
{
assert(x == e1);
@try {
a = e2;
}
@catch (id y)
{
assert(y == e2);
}
}
[e1 dealloc];
[e2 dealloc];
return 0;
}

@ -1,6 +1,7 @@
#include <stdio.h>
#import <objc/runtime.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#ifdef __has_attribute

@ -274,7 +274,6 @@ static inline struct dwarf_eh_lsda parse_lsda(struct _Unwind_Context *context, u
struct dwarf_eh_action
{
dw_eh_ptr_t landing_pad;
dw_eh_ptr_t cleanup_landing_pad;
dw_eh_ptr_t action_record;
};
@ -285,7 +284,7 @@ __attribute__((unused))
static struct dwarf_eh_action
dwarf_eh_find_callsite(struct _Unwind_Context *context, struct dwarf_eh_lsda *lsda)
{
struct dwarf_eh_action result = { 0, 0};
struct dwarf_eh_action result = { 0, 0 };
uint64_t ip = _Unwind_GetIP(context) - _Unwind_GetRegionStart(context);
unsigned char *callsite_table = (unsigned char*)lsda->call_site_table;
while (callsite_table <= lsda->action_table)
@ -309,12 +308,6 @@ static struct dwarf_eh_action
// record can be stored.
result.action_record = lsda->action_table + action - 1;
}
else
{
// We've found a cleanup, but we may also find something else...
result.cleanup_landing_pad = lsda->landing_pads + landing_pad;
continue;
}
// No landing pad means keep unwinding.
if (landing_pad)
{
@ -324,8 +317,6 @@ static struct dwarf_eh_action
break;
}
}
if (result.cleanup_landing_pad && result.landing_pad)
fprintf(stderr, "Found both cleanup and catch\n");
return result;
}

@ -7,7 +7,23 @@
#include "class.h"
#include "objcxx_eh.h"
#define fprintf(...)
#ifndef NO_PTHREADS
#include <pthread.h>
#endif
#ifndef DEBUG_EXCEPTIONS
#define DEBUG_LOG(...)
#else
#define DEBUG_LOG(str, ...) fprintf(stderr, str, ## __VA_ARGS__)
#endif
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#if !__has_builtin(__builtin_unreachable)
#define __builtin_unreachable abort
#endif
/**
* Class of exceptions to distinguish between this and other exception types.
@ -26,7 +42,15 @@ struct objc_exception
int handlerSwitchValue;
/** The cached landing pad for the catch handler.*/
void *landingPad;
/**
* Next pointer for chained exceptions.
*/
struct objc_exception *next;
/**
* The number of nested catches that may hold this exception. This is
* negative while an exception is being rethrown.
*/
int catch_count;
/** The language-agnostic part of the exception header. */
struct _Unwind_Exception unwindHeader;
/** Thrown object. This is after the unwind header so that the C++
@ -39,6 +63,12 @@ struct objc_exception
struct _Unwind_Exception *cxx_exception;
};
struct objc_exception *objc_exception_from_header(struct _Unwind_Exception *ex)
{
return (struct objc_exception*)((char*)ex -
offsetof(struct objc_exception, unwindHeader));
}
typedef enum
{
handler_none,
@ -132,14 +162,14 @@ void objc_exception_throw(id object)
if ((nil != object) &&
(class_respondsToSelector(classForObject(object), rethrow_sel)))
{
fprintf(stderr, "Rethrowing\n");
DEBUG_LOG("Rethrowing\n");
IMP rethrow = objc_msg_lookup(object, rethrow_sel);
rethrow(object, rethrow_sel);
// Should not be reached! If it is, then the rethrow method actually
// didn't, so we throw it normally.
}
fprintf(stderr, "Throwing %p\n", object);
DEBUG_LOG("Throwing %p\n", object);
struct objc_exception *ex = calloc(1, sizeof(struct objc_exception));
@ -154,7 +184,7 @@ void objc_exception_throw(id object)
{
_objc_unexpected_exception(object);
}
fprintf(stderr, "Throw returned %d\n",(int) err);
DEBUG_LOG("Throw returned %d\n",(int) err);
abort();
}
@ -175,7 +205,7 @@ static Class get_type_table_entry(struct _Unwind_Context *context,
if (0 == class_name) { return Nil; }
fprintf(stderr, "Class name: %s\n", class_name);
DEBUG_LOG("Class name: %s\n", class_name);
if (strcmp("@id", class_name) == 0) { return (Class)1; }
@ -211,11 +241,11 @@ static handler_type check_action_record(struct _Unwind_Context *context,
dw_eh_ptr_t action_record_offset_base = action_record;
int displacement = read_sleb128(&action_record);
*selector = filter;
fprintf(stderr, "Filter: %d\n", filter);
DEBUG_LOG("Filter: %d\n", filter);
if (filter > 0)
{
Class type = get_type_table_entry(context, lsda, filter);
fprintf(stderr, "%p type: %d\n", type, !foreignException);
DEBUG_LOG("%p type: %d\n", type, !foreignException);
// Catchall
if (Nil == type)
{
@ -225,7 +255,7 @@ static handler_type check_action_record(struct _Unwind_Context *context,
// nothing when a foreign exception is thrown
else if ((Class)1 == type)
{
fprintf(stderr, "Found id catch\n");
DEBUG_LOG("Found id catch\n");
if (!foreignException)
{
return handler_catchall_id;
@ -233,7 +263,7 @@ static handler_type check_action_record(struct _Unwind_Context *context,
}
else if (!foreignException && isKindOfClass(thrown_class, type))
{
fprintf(stderr, "found handler for %s\n", type->name);
DEBUG_LOG("found handler for %s\n", type->name);
return handler_class;
}
else if (thrown_class == type)
@ -243,14 +273,14 @@ static handler_type check_action_record(struct _Unwind_Context *context,
}
else if (filter == 0)
{
fprintf(stderr, "0 filter\n");
DEBUG_LOG("0 filter\n");
// Cleanup? I think the GNU ABI doesn't actually use this, but it
// would be a good way of indicating a non-id catchall...
return handler_cleanup;
}
else
{
fprintf(stderr, "Filter value: %d\n"
DEBUG_LOG("Filter value: %d\n"
"Your compiler and I disagree on the correct layout of EH data.\n",
filter);
abort();
@ -263,10 +293,20 @@ static handler_type check_action_record(struct _Unwind_Context *context,
}
/**
* The Objective-C exception personality function.
* The Objective-C exception personality function implementation. This is
* shared by the GCC-compatible and the new implementation.
*
* The key difference is that the new implementation always returns the
* exception object and boxes it.
*/
BEGIN_PERSONALITY_FUNCTION(__gnu_objc_personality_v0)
fprintf(stderr, "Personality function called\n");
static inline _Unwind_Reason_Code internal_objc_personality(int version,
_Unwind_Action actions,
uint64_t exceptionClass,
struct _Unwind_Exception *exceptionObject,
struct _Unwind_Context *context,
BOOL isNew)
{
DEBUG_LOG("%s personality function called %p\n", isNew ? "New" : "Old", exceptionObject);
// This personality function is for version 1 of the ABI. If you use it
// with a future version of the ABI, it won't know what to do, so it
@ -276,10 +316,10 @@ BEGIN_PERSONALITY_FUNCTION(__gnu_objc_personality_v0)
return _URC_FATAL_PHASE1_ERROR;
}
struct objc_exception *ex = 0;
#ifndef fprintf
#ifdef DEBUG_EXCEPTIONS
char *cls = (char*)&exceptionClass;
#endif
fprintf(stderr, "Class: %c%c%c%c%c%c%c%c\n", cls[7], cls[6], cls[5], cls[4], cls[3], cls[2], cls[1], cls[0]);
DEBUG_LOG("Class: %c%c%c%c%c%c%c%c\n", cls[7], cls[6], cls[5], cls[4], cls[3], cls[2], cls[1], cls[0]);
// Check if this is a foreign exception. If it is a C++ exception, then we
// have to box it. If it's something else, like a LanguageKit exception
@ -293,12 +333,13 @@ BEGIN_PERSONALITY_FUNCTION(__gnu_objc_personality_v0)
#ifdef NO_OBJCXX
if (exceptionClass == cxx_exception_class)
{
id obj = objc_object_for_cxx_exception(exceptionObject);
if (obj != (id)-1)
int objcxx;
id obj = objc_object_for_cxx_exception(exceptionObject, &objcxx);
objcxxException = objcxx;
if (objcxxException)
{
object = obj;
fprintf(stderr, "ObjC++ object exception %p\n", object);
objcxxException = YES;
DEBUG_LOG("ObjC++ object exception %p\n", object);
// This is a foreign exception, buy for the purposes of exception
// matching, we pretend that it isn't.
foreignException = NO;
@ -316,18 +357,16 @@ BEGIN_PERSONALITY_FUNCTION(__gnu_objc_personality_v0)
// language-specific exception stuff.
else if (!foreignException)
{
ex = (struct objc_exception*) ((char*)exceptionObject -
offsetof(struct objc_exception, unwindHeader));
ex = objc_exception_from_header(exceptionObject);
thrown_class = classForObject(ex->object);
}
else if (_objc_class_for_boxing_foreign_exception)
{
thrown_class = _objc_class_for_boxing_foreign_exception(exceptionClass);
fprintf(stderr, "Foreign class: %p\n", thrown_class);
DEBUG_LOG("Foreign class: %p\n", thrown_class);
}
unsigned char *lsda_addr = (void*)_Unwind_GetLanguageSpecificData(context);
fprintf(stderr, "LSDA: %p\n", lsda_addr);
DEBUG_LOG("LSDA: %p\n", lsda_addr);
// No LSDA implies no landing pads - try the next frame
if (0 == lsda_addr) { return _URC_CONTINUE_UNWIND; }
@ -338,12 +377,12 @@ BEGIN_PERSONALITY_FUNCTION(__gnu_objc_personality_v0)
if (actions & _UA_SEARCH_PHASE)
{
fprintf(stderr, "Search phase...\n");
DEBUG_LOG("Search phase...\n");
struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr);
action = dwarf_eh_find_callsite(context, &lsda);
handler_type handler = check_action_record(context, foreignException,
&lsda, action.action_record, thrown_class, &selector);
fprintf(stderr, "handler: %d\n", handler);
DEBUG_LOG("handler: %d\n", handler);
// If there's no action record, we've only found a cleanup, so keep
// searching for something real
if (handler == handler_class ||
@ -351,17 +390,18 @@ BEGIN_PERSONALITY_FUNCTION(__gnu_objc_personality_v0)
(handler == handler_catchall))
{
saveLandingPad(context, exceptionObject, ex, selector, action.landing_pad);
fprintf(stderr, "Found handler! %d\n", handler);
DEBUG_LOG("Found handler! %d\n", handler);
return _URC_HANDLER_FOUND;
}
return _URC_CONTINUE_UNWIND;
}
fprintf(stderr, "Phase 2: Fight!\n");
DEBUG_LOG("Phase 2: Fight!\n");
// TODO: If this is a C++ exception, we can cache the lookup and cheat a
// bit
if (!(actions & _UA_HANDLER_FRAME))
{
DEBUG_LOG("Not the handler frame, looking up the cleanup again\n");
struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr);
action = dwarf_eh_find_callsite(context, &lsda);
// If there's no cleanup here, continue unwinding.
@ -371,20 +411,19 @@ BEGIN_PERSONALITY_FUNCTION(__gnu_objc_personality_v0)
}
handler_type handler = check_action_record(context, foreignException,
&lsda, action.action_record, thrown_class, &selector);
fprintf(stderr, "handler! %d %d\n", (int)handler, (int)selector);
DEBUG_LOG("handler! %d %d\n", (int)handler, (int)selector);
// If this is not a cleanup, ignore it and keep unwinding.
//if (check_action_record(context, foreignException, &lsda,
//action.action_record, thrown_class, &selector) != handler_cleanup)
if (handler != handler_cleanup)
{
fprintf(stderr, "Ignoring handler! %d\n",handler);
DEBUG_LOG("Ignoring handler! %d\n",handler);
return _URC_CONTINUE_UNWIND;
}
fprintf(stderr, "Installing cleanup...\n");
DEBUG_LOG("Installing cleanup...\n");
// If there is a cleanup, we need to return the exception structure
// (not the object) to the calling frame. The exception object
object = exceptionObject;
//selector = 0;
}
else if (foreignException || objcxxException)
{
@ -396,42 +435,57 @@ BEGIN_PERSONALITY_FUNCTION(__gnu_objc_personality_v0)
// exception, then we need to delete the exception object.
if (foreignException)
{
fprintf(stderr, "Doing the foreign exception thing...\n");
DEBUG_LOG("Doing the foreign exception thing...\n");
//[thrown_class exceptionWithForeignException: exceptionObject];
SEL box_sel = sel_registerName("exceptionWithForeignException:");
IMP boxfunction = objc_msg_lookup((id)thrown_class, box_sel);
if (!isNew)
{
object = boxfunction((id)thrown_class, box_sel, exceptionObject);
fprintf(stderr, "Boxed as %p\n", object);
DEBUG_LOG("Boxed as %p\n", object);
}
else // ObjCXX exception
}
else if (!isNew) // ObjCXX exception
{
_Unwind_DeleteException(exceptionObject);
}
// In the new EH ABI, we call objc_begin_catch() / and
// objc_end_catch(), which will wrap their __cxa* versions.
}
else
{
// Restore the saved info if we saved some last time.
loadLandingPad(context, exceptionObject, ex, &selector, &action.landing_pad);
object = ex->object;
if (!isNew)
{
free(ex);
}
}
_Unwind_SetIP(context, (unsigned long)action.landing_pad);
_Unwind_SetGR(context, __builtin_eh_return_data_regno(0),
(unsigned long)object);
(unsigned long)(isNew ? exceptionObject : object));
_Unwind_SetGR(context, __builtin_eh_return_data_regno(1), selector);
DEBUG_LOG("Installing context, selector %d\n", (int)selector);
return _URC_INSTALL_CONTEXT;
}
BEGIN_PERSONALITY_FUNCTION(__gnu_objc_personality_v0)
return internal_objc_personality(version, actions, exceptionClass,
exceptionObject, context, NO);
}
BEGIN_PERSONALITY_FUNCTION(__gnustep_objc_personality_v0)
return internal_objc_personality(version, actions, exceptionClass,
exceptionObject, context, YES);
}
// FIXME!
#ifndef __arm__
BEGIN_PERSONALITY_FUNCTION(__gnustep_objcxx_personality_v0)
if (exceptionClass == objc_exception_class)
{
struct objc_exception *ex = (struct objc_exception*)
((char*)exceptionObject - offsetof(struct objc_exception,
unwindHeader));
struct objc_exception *ex = objc_exception_from_header(exceptionObject);
if (0 == ex->cxx_exception)
{
id *newEx = __cxa_allocate_exception(sizeof(id));
@ -448,3 +502,230 @@ BEGIN_PERSONALITY_FUNCTION(__gnustep_objcxx_personality_v0)
return CALL_PERSONALITY_FUNCTION(__gxx_personality_v0);
}
#endif
// Weak references to C++ runtime functions. We don't bother testing that
// these are 0 before calling them, because if they are not resolved then we
// should not be in a code path that involves a C++ exception.
__attribute__((weak)) void *__cxa_begin_catch(void *e);
__attribute__((weak)) void __cxa_end_catch(void);
__attribute__((weak)) void __cxa_rethrow(void);
enum exception_type
{
NONE,
CXX,
OBJC,
FOREIGN,
BOXED_FOREIGN
};
struct thread_data
{
enum exception_type current_exception_type;
struct objc_exception *caughtExceptions;
};
// IF we don't have pthreads, then we fall back to using a per-thread
// structure. This will leak memory if we terminate any threads with
// exceptions in-flight.
#ifdef NO_PTHREADS
static __thread struct thread_data thread_data;
#else
void clean_tls(void *td)
{
struct thread_data *data = td;
}
static pthread_key_t key;
void init_key(void)
{
pthread_key_create(&key, clean_tls);
}
#endif
struct thread_data *get_thread_data(void)
{
#ifndef NO_PTHREADS
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
pthread_once(&once_control, init_key);
struct thread_data *td = pthread_getspecific(key);
if (td == NULL)
{
td = calloc(sizeof(struct thread_data), 1);
pthread_setspecific(key, td);
if (pthread_getspecific(key) == NULL)
{
fprintf(stderr, "Unable to allocate thread-local storage for exceptions\n");
}
}
return td;
#else
return &td;
#endif
}
struct thread_data *get_thread_data_fast(void)
{
#ifndef NO_PTHREADS
struct thread_data *td = pthread_getspecific(key);
return td;
#else
return &td;
#endif
}
id objc_begin_catch(struct _Unwind_Exception *exceptionObject)
{
struct thread_data *td = get_thread_data();
DEBUG_LOG("Beginning catch %p\n", exceptionObject);
if (exceptionObject->exception_class == objc_exception_class)
{
td->current_exception_type = OBJC;
struct objc_exception *ex = objc_exception_from_header(exceptionObject);
if (ex->catch_count == 0)
{
// If this is the first catch, add it to the list.
ex->catch_count = 1;
ex->next = td->caughtExceptions;
td->caughtExceptions = ex;
}
else if (ex->catch_count < 0)
{
// If this is being thrown, mark it as caught again and increment
// the refcount
ex->catch_count = -ex->catch_count + 1;
}
else
{
// Otherwise, just increment the catch count
ex->catch_count++;
}
DEBUG_LOG("objc catch\n");
return ex->object;
}
// If we have a foreign exception while we have stacked exceptions, we have
// a problem. We can't chain them, so we follow the example of C++ and
// just abort.
if (td->caughtExceptions != 0)
{
// FIXME: Actually, we can handle a C++ exception if only ObjC
// exceptions are in-flight
abort();
}
// If this is a C++ exception, let the C++ runtime handle it.
if (exceptionObject->exception_class == cxx_exception_class)
{
DEBUG_LOG("c++ catch\n");
td->current_exception_type = CXX;
return __cxa_begin_catch(exceptionObject);
}
DEBUG_LOG("foreign exception catch\n");
// Box if we have a boxing function.
if (_objc_class_for_boxing_foreign_exception)
{
Class thrown_class =
_objc_class_for_boxing_foreign_exception(exceptionObject->exception_class);
SEL box_sel = sel_registerName("exceptionWithForeignException:");
IMP boxfunction = objc_msg_lookup((id)thrown_class, box_sel);
if (boxfunction != 0)
{
id boxed = boxfunction((id)thrown_class, box_sel, exceptionObject);
td->caughtExceptions = (struct objc_exception*)boxed;
td->current_exception_type = BOXED_FOREIGN;
return boxed;
}
}
td->current_exception_type = FOREIGN;
td->caughtExceptions = (struct objc_exception*)exceptionObject;
// If this is some other kind of exception, then assume that the value is
// at the end of the exception header.
return (id)((char*)exceptionObject + sizeof(struct _Unwind_Exception));
}
void objc_end_catch(void)
{
struct thread_data *td = get_thread_data_fast();
// If this is a boxed foreign exception then the boxing class is
// responsible for cleaning it up
if (td->current_exception_type == BOXED_FOREIGN)
{
td->caughtExceptions = 0;
td->current_exception_type = NONE;
return;
}
DEBUG_LOG("Ending catch\n");
// If this is a C++ exception, then just let the C++ runtime handle it.
if (td->current_exception_type == CXX)
{
__cxa_end_catch();
td->current_exception_type = OBJC;
return;
}
if (td->current_exception_type == FOREIGN)
{
struct _Unwind_Exception *e = ((struct _Unwind_Exception*)td->caughtExceptions);
e->exception_cleanup(_URC_FOREIGN_EXCEPTION_CAUGHT, e);
td->current_exception_type = NONE;
td->caughtExceptions = 0;
return;
}
// Otherwise we should do the cleanup thing. Nested catches are possible,
// so we only clean up the exception if this is the last reference.
assert(td->caughtExceptions != 0);
struct objc_exception *ex = td->caughtExceptions;
// If this is being rethrown decrement its (negated) catch count, but don't
// delete it even if its catch count would be 0.
if (ex->catch_count < 0)
{
ex->catch_count++;
return;
}
ex->catch_count--;
if (ex->catch_count == 0)
{
td->caughtExceptions = ex->next;
free(ex);
}
}
void objc_exception_rethrow(struct _Unwind_Exception *e)
{
struct thread_data *td = get_thread_data_fast();
// If this is an Objective-C exception, then
if (td->current_exception_type == OBJC)
{
struct objc_exception *ex = objc_exception_from_header(e);
assert(e->exception_class == objc_exception_class);
assert(ex == td->caughtExceptions);
assert(ex->catch_count > 0);
// Negate the catch count, so that we can detect that this is a
// rethrown exception in objc_end_catch
ex->catch_count = -ex->catch_count;
_Unwind_Reason_Code err = _Unwind_Resume_or_Rethrow(e);
free(ex);
if (_URC_END_OF_STACK == err && 0 != _objc_unexpected_exception)
{
_objc_unexpected_exception(ex->object);
}
abort();
}
else if (td->current_exception_type == CXX)
{
assert(e->exception_class == cxx_exception_class);
__cxa_rethrow();
}
if (td->current_exception_type == BOXED_FOREIGN)
{
SEL rethrow_sel = sel_registerName("rethrow");
id object = (id)td->caughtExceptions;
if ((nil != object) &&
(class_respondsToSelector(classForObject(object), rethrow_sel)))
{
DEBUG_LOG("Rethrowing boxed exception\n");
IMP rethrow = objc_msg_lookup(object, rethrow_sel);
rethrow(object, rethrow_sel);
}
}
assert(e == (struct _Unwind_Exception*)td->caughtExceptions);
_Unwind_Resume_or_Rethrow(e);
abort();
}

@ -209,6 +209,57 @@ typedef struct
#include "slot.h"
#if defined(__x86_64) || defined(__i386) || defined(__arm__) || \
defined(__mips_n64) || defined(__mips_n32)
/**
* Standard message sending function. This function must be cast to the
* correct types for the function before use. The first argument is the
* receiver and the second the selector.
*
* Note that this function is not available on all architectures. For a more
* portable solution to sending arbitrary messages, consider using
* objc_msg_lookup_sender() and then calling the returned IMP directly.
*
* This version of the function is used for all messages that return either an
* integer, a pointer, or a small structure value that is returned in
* registers. Be aware that calling conventions differ between operating
* systems even within the same architecture, so take great care if using this
* function for small (two integer) structures.
*/
id objc_msgSend(id self, SEL _cmd, ...);
/**
* Standard message sending function. This function must be cast to the
* correct types for the function before use. The first argument is the
* receiver and the second the selector.
*
* Note that this function is not available on all architectures. For a more
* portable solution to sending arbitrary messages, consider using
* objc_msg_lookup_sender() and then calling the returned IMP directly.
*
* This version of the function is used for all messages that return a
* structure that is not returned in registers. Be aware that calling
* conventions differ between operating systems even within the same
* architecture, so take great care if using this function for small (two
* integer) structures.
*/
void objc_msgSend_stret(id self, SEL _cmd, ...);
/**
* Standard message sending function. This function must be cast to the
* correct types for the function before use. The first argument is the
* receiver and the second the selector.
*
* Note that this function is not available on all architectures. For a more
* portable solution to sending arbitrary messages, consider using
* objc_msg_lookup_sender() and then calling the returned IMP directly.
*
* This version of the function is used for all messages that return floating
* point values.
*/
long double objc_msgSend_fpret(id self, SEL _cmd, ...);
#endif
/**
* Adds an instance variable to the named class. The class must not have been
* registered by the runtime. The alignment must be the base-2 logarithm of

@ -214,7 +214,7 @@ struct _Unwind_Exception *objc_init_cxx_exception(void *thrown_exception)
return &ex->unwindHeader;
}
void* objc_object_for_cxx_exception(void *thrown_exception)
void* objc_object_for_cxx_exception(void *thrown_exception, int *isValid)
{
__cxa_exception *ex = (__cxa_exception*) ((char*)thrown_exception -
offsetof(struct __cxa_exception, unwindHeader));
@ -222,8 +222,10 @@ void* objc_object_for_cxx_exception(void *thrown_exception)
if (!dynamic_cast<const gnustep::libobjc::__objc_id_type_info*>(thrownType) &&
!dynamic_cast<const gnustep::libobjc::__objc_class_type_info*>(thrownType))
{
return (id)-1;
*isValid = 0;
return 0;
}
*isValid = 1;
return *(id*)(ex+1);
}

@ -28,11 +28,11 @@ __attribute__((weak))
void __cxa_free_exception(void *thrown_exception);
/**
* Tests whether a C++ exception contains an Objective-C object, and returns if
* if it does. Returns -1 if it doesn't. -1 is used instead of 0, because
* throwing nil is allowed, but throwing non-nil, invalid objects is not.
* if it does. The second argument is a pointer to a boolean value indicating
* whether this is a valid object.
*/
__attribute__((weak))
void *objc_object_for_cxx_exception(void *thrown_exception);
void *objc_object_for_cxx_exception(void *thrown_exception, int *isValid);
/**
* Prints the type info associated with an exception. Used only when

Loading…
Cancel
Save