From e0719a9c627068ead140478a879ded6bf9cabea4 Mon Sep 17 00:00:00 2001 From: theraven Date: Sun, 20 Mar 2011 20:38:12 +0000 Subject: [PATCH] Added Objective-C++ exception handling support. Allows throwing Objective-C objects with throw or @throw and catching them with catch() or @catch. --- GNUmakefile | 13 +++- class_table.c | 1 + dwarf_eh.h | 2 + eh_personality.c | 90 ++++++++++++++++++++--- objcxx_eh.cc | 181 +++++++++++++++++++++++++++++++++++++++++++++++ objcxx_eh.h | 19 +++++ 6 files changed, 294 insertions(+), 12 deletions(-) create mode 100644 objcxx_eh.cc create mode 100644 objcxx_eh.h diff --git a/GNUmakefile b/GNUmakefile index 9dd381f..d475197 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -1,6 +1,6 @@ PACKAGE_NAME = gnustep-objc2 -VERSION=1.2.0 +VERSION=1.3.0 SVN_MODULE_NAME = libobjc2 SVN_BASE_URL = svn+ssh://svn.gna.org/svn/gnustep/libs SVN_TAG_NAME=objc2 @@ -40,6 +40,12 @@ libobjc_C_FILES = \ statics_loader.c\ toydispatch.c +ifeq ($(objective-cxx), yes) +libobjc_CC_FILES = objcxx_eh.cc +#libobjc_LDFLAGS = -lc++sup +endif + + ifneq ($(enable_legacy), no) libobjc_C_FILES += legacy_malloc.c endif @@ -75,11 +81,14 @@ libobjc_CPPFLAGS += -D__OBJC_RUNTIME_INTERNAL__=1 -D_XOPEN_SOURCE=500 # useful on compilers that support C99 (currently only clang), so there is no # benefit from supporting platforms with no C99 compiler. libobjc_CFLAGS += -std=gnu99 -g -fexceptions #-fvisibility=hidden +libobjc_CFLAGS += -Wno-unused-function +libobjc_CCFLAGS += -std=c++98 -g -fexceptions #-fvisibility=hidden + # Uncomment this when debugging - it makes everything slow, but means that the # debugger actually works... #libobjc_CFLAGS += -fno-inline libobjc_OBJCFLAGS += $(libobjc_CFLAGS) $(libobjc_CFLAGS) -libobjc_LDFLAGS += -g +libobjc_LDFLAGS += -g libobjc_LIB_DIRS += -L toydispatch/obj libobjc_CFLAGS += -O3 diff --git a/class_table.c b/class_table.c index dd31947..8727b87 100644 --- a/class_table.c +++ b/class_table.c @@ -120,6 +120,7 @@ void class_table_insert(Class class) Class class_table_get_safe(const char *class_name) { + if (NULL == class_name) { return Nil; } return class_table_internal_table_get(class_table, class_name); } diff --git a/dwarf_eh.h b/dwarf_eh.h index 7f43c69..eada9b1 100644 --- a/dwarf_eh.h +++ b/dwarf_eh.h @@ -315,3 +315,5 @@ static struct dwarf_eh_action } return result; } + +#define EXCEPTION_CLASS(a,b,c,d,e,f,g,h) (((uint64_t)a << 56) + ((uint64_t)b << 48) + ((uint64_t)c << 40) + ((uint64_t)d << 32) + ((uint64_t)e << 24) + ((uint64_t)f << 16) + ((uint64_t)g << 8) + ((uint64_t)h)) diff --git a/eh_personality.c b/eh_personality.c index 0b97340..89b5c68 100644 --- a/eh_personality.c +++ b/eh_personality.c @@ -6,13 +6,15 @@ #include "objc/runtime.h" #include "objc/hooks.h" #include "class.h" +#include "objcxx_eh.h" #define fprintf(...) /** * Class of exceptions to distinguish between this and other exception types. */ -#define objc_exception_class (*(int64_t*)"GNUCOBJC") +const uint64_t objc_exception_class = EXCEPTION_CLASS('G','N','U','C','O','B','J','C'); +const uint64_t cxx_exception_class = EXCEPTION_CLASS('G','N','U','C','C','+','+','\0'); /** * Structure used as a header on thrown exceptions. @@ -31,6 +33,11 @@ struct objc_exception /** Thrown object. This is after the unwind header so that the C++ * exception handler can catch this as a foreign exception. */ id object; + /** C++ exception structure. Used for mixed exceptions. When we are in + * Objective-C++ code, we create this structure for passing to the C++ + * exception personality function. It will then handle installing + * exceptions for us. */ + struct _Unwind_Exception *cxx_exception; }; typedef enum @@ -44,8 +51,13 @@ typedef enum static void cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *e) { + /* + if (header->exceptionDestructor) + header->exceptionDestructor (e + 1); + free((struct objc_exception*) ((char*)e - offsetof(struct objc_exception, unwindHeader))); + */ } /** * Throws an Objective-C exception. This function is, unfortunately, used for @@ -189,7 +201,7 @@ static handler_type check_action_record(struct _Unwind_Context *context, } /** - * The exception personality function. + * The Objective-C exception personality function. */ _Unwind_Reason_Code __gnu_objc_personality_v0(int version, _Unwind_Action actions, @@ -207,18 +219,39 @@ _Unwind_Reason_Code __gnu_objc_personality_v0(int version, struct objc_exception *ex = 0; //char *cls = (char*)&exceptionClass; - fprintf(stderr, "Class: %c%c%c%c%c%c%c%c\n", cls[0], cls[1], cls[2], cls[3], cls[4], cls[5], cls[6], cls[7]); + 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]); // 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 // then we ignore it (for now) BOOL foreignException = exceptionClass != objc_exception_class; + // Is this a C++ exception containing an Objective-C++ object? + BOOL objcxxException = NO; + // The object to return + void *object = NULL; + + if (exceptionClass == cxx_exception_class) + { + id obj = objc_object_for_cxx_exception(exceptionObject); + if (obj != (id)-1) + { + object = obj; + objcxxException = YES; + // This is a foreign exception, buy for the purposes of exception + // matching, we pretend that it isn't. + foreignException = NO; + } + } Class thrown_class = Nil; + if (objcxxException) + { + thrown_class = (object == 0) ? Nil : ((id)object)->isa; + } // If it's not a foreign exception, then we know the layout of the // language-specific exception stuff. - if (!foreignException) + else if (!foreignException) { ex = (struct objc_exception*) ((char*)exceptionObject - offsetof(struct objc_exception, unwindHeader)); @@ -267,7 +300,6 @@ _Unwind_Reason_Code __gnu_objc_personality_v0(int version, // TODO: If this is a C++ exception, we can cache the lookup and cheat a // bit - void *object = nil; if (!(actions & _UA_HANDLER_FRAME)) { struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr); @@ -294,17 +326,26 @@ _Unwind_Reason_Code __gnu_objc_personality_v0(int version, object = exceptionObject; //selector = 0; } - else if (foreignException) + else if (foreignException || objcxxException) { fprintf(stderr, "Doing the foreign exception thing...\n"); struct dwarf_eh_lsda lsda = parse_lsda(context, lsda_addr); action = dwarf_eh_find_callsite(context, &lsda); check_action_record(context, foreignException, &lsda, action.action_record, thrown_class, &selector); - //[thrown_class exceptionWithForeignException: exceptionObject]; - SEL box_sel = sel_registerName("exceptionWithForeignException:"); - IMP boxfunction = objc_msg_lookup((id)thrown_class, box_sel); - object = boxfunction((id)thrown_class, box_sel, exceptionObject); + // If it's a foreign exception, then box it. If it's an Objective-C++ + // exception, then we need to delete the exception object. + if (foreignException) + { + //[thrown_class exceptionWithForeignException: exceptionObject]; + SEL box_sel = sel_registerName("exceptionWithForeignException:"); + IMP boxfunction = objc_msg_lookup((id)thrown_class, box_sel); + object = boxfunction((id)thrown_class, box_sel, exceptionObject); + } + else // ObjCXX exception + { + _Unwind_DeleteException(exceptionObject); + } fprintf(stderr, "Boxed as %p\n", object); } else @@ -323,3 +364,32 @@ _Unwind_Reason_Code __gnu_objc_personality_v0(int version, return _URC_INSTALL_CONTEXT; } + + +_Unwind_Reason_Code __gnustep_objcxx_personality_v0(int version, + _Unwind_Action actions, + uint64_t exceptionClass, + struct _Unwind_Exception *exceptionObject, + struct _Unwind_Context *context) +{ + if (exceptionClass == objc_exception_class) + { + struct objc_exception *ex = (struct objc_exception*) + ((char*)exceptionObject - offsetof(struct objc_exception, + unwindHeader)); + if (0 == ex->cxx_exception) + { + id *newEx = __cxa_allocate_exception(sizeof(id)); + *newEx = ex->object; + ex->cxx_exception = objc_init_cxx_exception(newEx); + ex->cxx_exception->exception_class = cxx_exception_class; + ex->cxx_exception->exception_cleanup = cleanup; + ex->cxx_exception->private_1 = exceptionObject->private_1; + ex->cxx_exception->private_2 = exceptionObject->private_2; + } + exceptionObject = ex->cxx_exception; + exceptionClass = cxx_exception_class; + } + return __gxx_personality_v0(version, actions, exceptionClass, + exceptionObject, context); +} diff --git a/objcxx_eh.cc b/objcxx_eh.cc new file mode 100644 index 0000000..d354f35 --- /dev/null +++ b/objcxx_eh.cc @@ -0,0 +1,181 @@ +#include +#include +#include "dwarf_eh.h" +#include +#include +#include "objcxx_eh.h" +extern "C" +{ +#include "objc/runtime.h" +}; + + +using namespace std; + + +static BOOL isKindOfClass(Class thrown, Class type) +{ + do + { + if (thrown == type) + { + return YES; + } + thrown = class_getSuperclass(thrown); + } while (Nil != thrown); + + return NO; +} + +/** + * C++ Exception structure. From the Itanium ABI spec + */ +struct __cxa_exception +{ + std::type_info *exceptionType; + void (*exceptionDestructor) (void *); + unexpected_handler unexpectedHandler; + terminate_handler terminateHandler; + __cxa_exception *nextException; + unsigned int handlerCount; + int handlerSwitchValue; + const char *actionRecord; + const char *languageSpecificData; + void *catchTemp; + void *adjustedPtr; + _Unwind_Exception unwindHeader; +}; + + + + +namespace gnustep +{ + namespace libobjc + { + struct __objc_id_type_info : std::type_info + { + __objc_id_type_info() : type_info("@id") {}; + virtual ~__objc_id_type_info(); + virtual bool __do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const; + }; + struct __objc_class_type_info : std::type_info + { + virtual ~__objc_class_type_info(); + virtual bool __do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const; + }; + } +}; + +gnustep::libobjc::__objc_class_type_info::~__objc_class_type_info() {} +gnustep::libobjc::__objc_id_type_info::~__objc_id_type_info() {} +bool gnustep::libobjc::__objc_class_type_info::__do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const +{ + id thrown = (id)obj; + bool found = false; + // Id throw matches any ObjC catch. This may be a silly idea! + if (dynamic_cast(thrownType)) + { + thrown = **(id**)obj; + // nil matches any handler + if (0 == thrown) + { + found = true; + } + else + { + // Check whether the real thrown object matches the catch type. + found = isKindOfClass(object_getClass(thrown), (Class)objc_getClass(name())); + } + } + if (dynamic_cast(thrownType)) + { + thrown = **(id**)obj; + found = isKindOfClass((Class)objc_getClass(thrownType->name()), + (Class)objc_getClass(name())); + } + if (found) + { + *obj = (void*)thrown; + } + return found; +}; + +bool gnustep::libobjc::__objc_id_type_info::__do_catch(const type_info *thrownType, + void **obj, + unsigned outer) const +{ + // Id catch matches any ObjC throw + if (dynamic_cast(thrownType)) + { + return true; + } + if (dynamic_cast(thrownType)) + { + return true; + } + return false; +}; + +//static gnustep::libobjc::__objc_id_type_info objc_id_type_info; +extern "C" +{ +//gnustep::libobjc::__objc_id_type_info *__objc_id_type_info = &objc_id_type_info; +gnustep::libobjc::__objc_id_type_info __objc_id_type_info; + +static void exception_cleanup(_Unwind_Reason_Code reason, + struct _Unwind_Exception *ex) +{ + __cxa_exception *cxxex = (__cxa_exception*) ((char*)ex - offsetof(struct __cxa_exception, unwindHeader)); + if (cxxex->exceptionType != &__objc_id_type_info) + { + delete cxxex->exceptionType; + } + __cxa_free_exception((void*)ex); +} + +struct _Unwind_Exception *objc_init_cxx_exception(void *thrown_exception) +{ + __cxa_exception *ex = ((__cxa_exception*)thrown_exception) - 1; + + std::type_info *tinfo = &__objc_id_type_info; + + ex->exceptionType = tinfo; + + ex->exceptionDestructor = 0; + + ex->unwindHeader.exception_class = EXCEPTION_CLASS('G','N','U','C','C','+','+','\0'); + ex->unwindHeader.exception_cleanup = exception_cleanup; + + return &ex->unwindHeader; +} + +void* objc_object_for_cxx_exception(void *thrown_exception) +{ + __cxa_exception *ex = (__cxa_exception*) ((char*)thrown_exception - + offsetof(struct __cxa_exception, unwindHeader)); + const std::type_info *thrownType = ex->exceptionType; + if (!dynamic_cast(thrownType) && + !dynamic_cast(thrownType)) + { + return (id)-1; + } + return (id)(ex-1); +} + +void print_type_info(void *thrown_exception) +{ + __cxa_exception *ex = (__cxa_exception*) ((char*)thrown_exception - + offsetof(struct __cxa_exception, unwindHeader)); + fprintf(stderr, "Type info: %s\n", ex->exceptionType->name()); + fprintf(stderr, "offset is: %d\n", offsetof(struct __cxa_exception, unwindHeader)); +} + +} // extern "C" + diff --git a/objcxx_eh.h b/objcxx_eh.h new file mode 100644 index 0000000..4c18232 --- /dev/null +++ b/objcxx_eh.h @@ -0,0 +1,19 @@ +#ifdef __cplusplus +extern "C" { +#endif +void *__cxa_allocate_exception(size_t thrown_size); +struct _Unwind_Exception *objc_init_cxx_exception(void *thrown_exception); +_Unwind_Reason_Code __gxx_personality_v0(int version, + _Unwind_Action actions, + uint64_t exceptionClass, + struct _Unwind_Exception *exceptionObject, + struct _Unwind_Context *context); +void __cxa_free_exception(void *thrown_exception); +void *objc_object_for_cxx_exception(void *thrown_exception); + +void print_type_info(void *thrown_exception); + + +#ifdef __cplusplus +} +#endif