summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Schleifer <js@webkeks.org>2012-07-03 01:33:53 +0200
committerJonathan Schleifer <js@webkeks.org>2012-07-03 17:44:31 +0200
commit81b06c8142b1c73e1e6a1ac27d10056a6b00ff9e (patch)
tree16a46e41ee4f5284341db4384d23e3c5946dd7cd
parentdd8ffa1f5b79620c04e45853506dfbcbb4d08be7 (diff)
runtime: Add exception handling.runtime
-rw-r--r--src/OFObject.m4
-rw-r--r--src/runtime/Makefile1
-rw-r--r--src/runtime/exception.m487
-rw-r--r--src/runtime/runtime.h5
4 files changed, 495 insertions, 2 deletions
diff --git a/src/OFObject.m b/src/OFObject.m
index 5a2c6982..37baff20 100644
--- a/src/OFObject.m
+++ b/src/OFObject.m
@@ -95,7 +95,7 @@ extern BOOL objc_sync_init();
extern BOOL objc_properties_init();
#endif
-#if defined(OF_APPLE_RUNTIME) && __OBJC2__
+#if !defined(OF_APPLE_RUNTIME) || defined(__OBJC2__)
static void
uncaught_exception_handler(id exception)
{
@@ -195,7 +195,7 @@ void _references_to_categories_of_OFObject(void)
}
#endif
-#if defined(OF_APPLE_RUNTIME) && __OBJC2__
+#if !defined(OF_APPLE_RUNTIME) || defined(__OBJC2__)
objc_setUncaughtExceptionHandler(uncaught_exception_handler);
#endif
diff --git a/src/runtime/Makefile b/src/runtime/Makefile
index a61ad7b1..a0772b7c 100644
--- a/src/runtime/Makefile
+++ b/src/runtime/Makefile
@@ -5,6 +5,7 @@ STATIC_LIB_NOINST = ${RUNTIME_A}
SRCS = category.m \
class.m \
+ exception.m \
hashtable.m \
init.m \
lookup.m \
diff --git a/src/runtime/exception.m b/src/runtime/exception.m
new file mode 100644
index 00000000..d70d48ed
--- /dev/null
+++ b/src/runtime/exception.m
@@ -0,0 +1,487 @@
+/*
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012
+ * Jonathan Schleifer <js@webkeks.org>
+ *
+ * All rights reserved.
+ *
+ * This file is part of ObjFW. It may be distributed under the terms of the
+ * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
+ * the packaging of this file.
+ *
+ * Alternatively, it may be distributed under the terms of the GNU General
+ * Public License, either version 2 or 3, which can be found in the file
+ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
+ * file.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#import "runtime.h"
+
+static const uint64_t objc_exception_class = 0x474E55434F424A43; /* GNUCOBJC */
+
+#define _UA_SEARCH_PHASE 0x01
+#define _UA_CLEANUP_PHASE 0x02
+#define _UA_HANDLER_FRAME 0x04
+#define _UA_FORCE_UNWIND 0x08
+
+#define DW_EH_PE_absptr 0x00
+
+#define DW_EH_PE_uleb128 0x01
+#define DW_EH_PE_udata2 0x02
+#define DW_EH_PE_udata4 0x03
+#define DW_EH_PE_udata8 0x04
+
+#define DW_EH_PE_signed 0x08
+#define DW_EH_PE_sleb128 (DW_EH_PE_signed | DW_EH_PE_uleb128)
+#define DW_EH_PE_sdata2 (DW_EH_PE_signed | DW_EH_PE_udata2)
+#define DW_EH_PE_sdata4 (DW_EH_PE_signed | DW_EH_PE_udata4)
+#define DW_EH_PE_sdata8 (DW_EH_PE_signed | DW_EH_PE_udata8)
+
+#define DW_EH_PE_pcrel 0x10
+#define DW_EH_PE_textrel 0x20
+#define DW_EH_PE_datarel 0x30
+#define DW_EH_PE_funcrel 0x40
+#define DW_EH_PE_aligned 0x50
+
+#define DW_EH_PE_indirect 0x80
+
+#define DW_EH_PE_omit 0xFF
+
+#define CLEANUP_FOUND 0x01
+#define HANDLER_FOUND 0x02
+
+struct _Unwind_Context;
+
+typedef enum
+{
+ _URC_FATAL_PHASE1_ERROR = 3,
+ _URC_END_OF_STACK = 5,
+ _URC_HANDLER_FOUND = 6,
+ _URC_INSTALL_CONTEXT = 7,
+ _URC_CONTINUE_UNWIND = 8
+} _Unwind_Reason_Code;
+
+struct objc_exception {
+ struct _Unwind_Exception {
+ uint64_t class;
+ void (*cleanup)(_Unwind_Reason_Code, struct _Unwind_Exception*);
+ /*
+ * The Itanium Exception ABI says to have those and never touch
+ * them.
+ */
+ uintptr_t private1, private2;
+ } exception;
+ id object;
+ uintptr_t landingpad;
+ intptr_t filter;
+};
+
+struct lsda {
+ uintptr_t region_start, landingpads_start;
+ uint8_t typestable_enc;
+ const uint8_t *typestable;
+ uintptr_t typestable_base;
+ uint8_t callsites_enc;
+ const uint8_t *callsites, *actiontable;
+};
+
+extern _Unwind_Reason_Code _Unwind_RaiseException(struct _Unwind_Exception*);
+extern void* _Unwind_GetLanguageSpecificData(struct _Unwind_Context*);
+extern uint64_t _Unwind_GetRegionStart(struct _Unwind_Context*);
+extern uint64_t _Unwind_GetDataRelBase(struct _Unwind_Context*);
+extern uint64_t _Unwind_GetTextRelBase(struct _Unwind_Context*);
+extern uint64_t _Unwind_GetIP(struct _Unwind_Context*);
+extern void _Unwind_SetIP(struct _Unwind_Context*, uint64_t);
+extern void _Unwind_SetGR(struct _Unwind_Context*, int, uint64_t);
+extern void _Unwind_DeleteException(struct _Unwind_Exception*);
+
+static objc_uncaught_exception_handler uncaught_exception_handler;
+
+static inline uint64_t
+read_leb128(const uint8_t **ptr, uint8_t *bits)
+{
+ uint64_t value = 0;
+ uint8_t shift = 0;
+
+ do {
+ value |= (**ptr & 0x7F) << shift;
+ (*ptr)++;
+ shift += 7;
+ } while (*(*ptr - 1) & 0x80);
+
+ if (bits != NULL)
+ *bits = shift;
+
+ return value;
+}
+
+static uint64_t
+read_uleb128(const uint8_t **ptr)
+{
+ return read_leb128(ptr, NULL);
+}
+
+static int64_t
+read_sleb128(const uint8_t **ptr)
+{
+ uint8_t bits;
+ int64_t value;
+
+ value = read_leb128(ptr, &bits);
+
+ if (bits < 64 && value & (1 << (bits - 1)))
+ value |= -(1 << bits);
+
+ return value;
+}
+
+static uintptr_t
+get_base(struct _Unwind_Context *ctx, uint8_t enc)
+{
+ if (enc == DW_EH_PE_omit)
+ return 0;
+
+ switch (enc & 0x70) {
+ case DW_EH_PE_absptr:
+ case DW_EH_PE_pcrel:
+ case DW_EH_PE_aligned:
+ return 0;
+ case DW_EH_PE_funcrel:
+ return _Unwind_GetRegionStart(ctx);
+ case DW_EH_PE_datarel:
+ return _Unwind_GetDataRelBase(ctx);
+ case DW_EH_PE_textrel:
+ return _Unwind_GetTextRelBase(ctx);
+ }
+
+ abort();
+}
+
+static size_t
+size_for_encoding(uint8_t enc)
+{
+ if (enc == DW_EH_PE_omit)
+ return 0;
+
+ switch (enc & 0x07) {
+ case DW_EH_PE_absptr:
+ return sizeof(void*);
+ case DW_EH_PE_udata2:
+ return 2;
+ case DW_EH_PE_udata4:
+ return 4;
+ case DW_EH_PE_udata8:
+ return 8;
+ }
+
+ abort();
+}
+
+static uint64_t
+read_value(uint8_t enc, const uint8_t **ptr)
+{
+ uint64_t value;
+
+ if (enc == DW_EH_PE_aligned)
+ /* Not implemented */
+ abort();
+
+#define READ_TYPE(type, size) \
+ { \
+ value = *(type*)(void*)*ptr; \
+ *ptr += size; \
+ break; \
+ }
+
+ switch (enc & 0x0F) {
+ case DW_EH_PE_absptr:
+ READ_TYPE(uintptr_t, sizeof(void*))
+ case DW_EH_PE_uleb128:
+ value = read_uleb128(ptr);
+ break;
+ case DW_EH_PE_udata2:
+ READ_TYPE(uint16_t, 2)
+ case DW_EH_PE_udata4:
+ READ_TYPE(uint32_t, 4)
+ case DW_EH_PE_udata8:
+ READ_TYPE(uint64_t, 8)
+ case DW_EH_PE_sleb128:
+ value = read_sleb128(ptr);
+ break;
+ case DW_EH_PE_sdata2:
+ READ_TYPE(int16_t, 2)
+ case DW_EH_PE_sdata4:
+ READ_TYPE(int32_t, 4)
+ case DW_EH_PE_sdata8:
+ READ_TYPE(int64_t, 8)
+ default:
+ abort();
+ }
+
+#undef READ_TYPE
+
+ return value;
+}
+
+static uint64_t
+resolve_value(uint64_t value, uint8_t enc, const uint8_t *start, uint64_t base)
+{
+ if (value == 0)
+ return 0;
+
+ value += ((enc & 0x70) == DW_EH_PE_pcrel ? (uintptr_t)start : base);
+
+ if (enc & DW_EH_PE_indirect)
+ value = *(uint64_t*)value;
+
+ return value;
+}
+
+static void
+read_lsda(struct _Unwind_Context *ctx, const uint8_t *ptr, struct lsda *lsda)
+{
+ uint8_t landingpads_start_enc;
+ uintptr_t callsites_size;
+
+ lsda->region_start = _Unwind_GetRegionStart(ctx);
+ lsda->landingpads_start = lsda->region_start;
+ lsda->typestable = 0;
+
+ if ((landingpads_start_enc = *ptr++) != DW_EH_PE_omit)
+ lsda->landingpads_start =
+ read_value(landingpads_start_enc, &ptr);
+
+ if ((lsda->typestable_enc = *ptr++) != DW_EH_PE_omit) {
+ uintptr_t tmp = read_uleb128(&ptr);
+ lsda->typestable = ptr + tmp;
+ }
+
+ lsda->typestable_base = get_base(ctx, lsda->typestable_enc);
+
+ lsda->callsites_enc = *ptr++;
+ callsites_size = read_uleb128(&ptr);
+ lsda->callsites = ptr;
+
+ lsda->actiontable = lsda->callsites + callsites_size;
+}
+
+static BOOL
+find_callsite(struct _Unwind_Context *ctx, struct lsda *lsda,
+ uintptr_t *landingpad, const uint8_t **actionrecords)
+{
+ uintptr_t ip = _Unwind_GetIP(ctx);
+ const uint8_t *ptr;
+
+ *landingpad = 0;
+ *actionrecords = NULL;
+
+ ptr = lsda->callsites;
+ while (ptr < lsda->actiontable) {
+ uintptr_t callsite_start, callsite_len, callsite_landingpad;
+ uintptr_t callsite_action;
+
+ callsite_start = lsda->region_start +
+ read_value(lsda->callsites_enc, &ptr);
+ callsite_len = read_value(lsda->callsites_enc, &ptr);
+ callsite_landingpad = read_value(lsda->callsites_enc, &ptr);
+ callsite_action = read_uleb128(&ptr);
+
+ /* We can stop if we passed IP, as the table is sorted */
+ if (callsite_start >= ip)
+ break;
+
+ if (callsite_start + callsite_len >= ip) {
+ if (callsite_landingpad != 0)
+ *landingpad = lsda->landingpads_start +
+ callsite_landingpad;
+ if (callsite_action != 0)
+ *actionrecords = lsda->actiontable +
+ callsite_action - 1;
+
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+static BOOL
+class_matches(Class class, id object)
+{
+ Class iter;
+
+ if (class == Nil)
+ return YES;
+
+ if (object == nil)
+ return NO;
+
+ for (iter = object->isa; iter != Nil; iter = class_getSuperclass(iter))
+ if (iter == class)
+ return YES;
+
+ return NO;
+}
+
+static uint8_t
+find_actionrecord(const uint8_t *actionrecords, struct lsda *lsda, int actions,
+ BOOL foreign, struct objc_exception *e, intptr_t *filtervalue)
+{
+ uint8_t found = 0;
+ const uint8_t *ptr;
+ intptr_t filter, displacement;
+
+ do {
+ ptr = actionrecords;
+ filter = read_sleb128(&ptr);
+
+ /*
+ * Get the next action record. Since read_sleb128 modifies ptr,
+ * we first set the actionrecord to the current ptr and then
+ * add the displacement.
+ */
+ actionrecords = ptr;
+ displacement = read_sleb128(&ptr);
+ actionrecords += displacement;
+
+ if (filter > 0 && !(actions & _UA_FORCE_UNWIND) && !foreign) {
+ Class class;
+ uintptr_t i, c;
+ const uint8_t *tmp;
+
+ i = filter * size_for_encoding(lsda->typestable_enc);
+ tmp = lsda->typestable - i;
+ c = read_value(lsda->typestable_enc, &tmp);
+ c = resolve_value(c, lsda->typestable_enc,
+ lsda->typestable - i, lsda->typestable_base);
+
+ class = (c != 0 ? objc_get_class((const char*)c) : Nil);
+
+ if (class_matches(class, e->object)) {
+ *filtervalue = filter;
+ return (found | HANDLER_FOUND);
+ }
+ } else if (filter == 0)
+ found |= CLEANUP_FOUND;
+ else
+ abort();
+ } while (displacement != 0);
+
+ return found;
+}
+
+_Unwind_Reason_Code
+__gnu_objc_personality_v0(int version, int actions, uint64_t ex_class,
+ struct _Unwind_Exception *ex, struct _Unwind_Context *ctx)
+{
+ struct objc_exception *e = (struct objc_exception*)ex;
+ BOOL foreign = (ex_class != objc_exception_class);
+ const uint8_t *lsda_addr, *actionrecords;
+ struct lsda lsda;
+ uintptr_t landingpad = 0;
+ uint8_t found = 0;
+ intptr_t filter = 0;
+
+ if (version != 1)
+ return _URC_FATAL_PHASE1_ERROR;
+
+ if (ctx == NULL)
+ abort();
+
+ /*
+ * We already cached everything we found in phase 1, so we only need
+ * to install the context in phase 2.
+ */
+ if (actions & _UA_HANDLER_FRAME && !foreign) {
+ /*
+ * For handlers, reg #0 must be the exception's object and reg
+ * #1 the filter.
+ */
+ _Unwind_SetGR(ctx, __builtin_eh_return_data_regno(0),
+ __builtin_extend_pointer(e->object));
+ _Unwind_SetGR(ctx, __builtin_eh_return_data_regno(1),
+ e->filter);
+ _Unwind_SetIP(ctx, e->landingpad);
+
+ return _URC_INSTALL_CONTEXT;
+ }
+
+ /* No LSDA -> nothing to handle */
+ if ((lsda_addr = _Unwind_GetLanguageSpecificData(ctx)) == NULL)
+ return _URC_CONTINUE_UNWIND;
+
+ read_lsda(ctx, lsda_addr, &lsda);
+
+ if (!find_callsite(ctx, &lsda, &landingpad, &actionrecords))
+ return _URC_CONTINUE_UNWIND;
+
+ if (landingpad != 0 && actionrecords == NULL)
+ found = CLEANUP_FOUND;
+ else if (landingpad != 0)
+ found = find_actionrecord(actionrecords, &lsda, actions,
+ foreign, e, &filter);
+
+ if (!found)
+ return _URC_CONTINUE_UNWIND;
+
+ if (actions & _UA_SEARCH_PHASE) {
+ if (!(found & HANDLER_FOUND))
+ return _URC_CONTINUE_UNWIND;
+
+ /* Cache it so we don't have to search it again in phase 2 */
+ if (!foreign) {
+ e->landingpad = landingpad;
+ e->filter = filter;
+ }
+
+ return _URC_HANDLER_FOUND;
+ } else if (actions & _UA_CLEANUP_PHASE) {
+ /* For cleanup, reg #0 must be the exception and reg #1 zero */
+ _Unwind_SetGR(ctx, __builtin_eh_return_data_regno(0),
+ __builtin_extend_pointer(e));
+ _Unwind_SetGR(ctx, __builtin_eh_return_data_regno(1), 0);
+ _Unwind_SetIP(ctx, landingpad);
+
+ return _URC_INSTALL_CONTEXT;
+ }
+
+ abort();
+}
+
+static void
+cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *ex)
+{
+ free(ex);
+}
+
+void
+objc_exception_throw(id object)
+{
+ struct objc_exception *e;
+
+ if ((e = malloc(sizeof(*e))) == NULL)
+ abort();
+
+ e->exception.class = objc_exception_class;
+ e->exception.cleanup = cleanup;
+ e->exception.private1 = e->exception.private2 = 0;
+ e->object = object;
+
+ if (_Unwind_RaiseException(&e->exception) == _URC_END_OF_STACK &&
+ uncaught_exception_handler != NULL)
+ uncaught_exception_handler(object);
+
+ abort();
+}
+
+objc_uncaught_exception_handler
+objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler handler)
+{
+ objc_uncaught_exception_handler old = uncaught_exception_handler;
+ uncaught_exception_handler = handler;
+
+ return old;
+}
diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h
index ddd128ea..c575af68 100644
--- a/src/runtime/runtime.h
+++ b/src/runtime/runtime.h
@@ -106,6 +106,8 @@ struct objc_protocol_list {
#define YES (BOOL)1
#define NO (BOOL)0
+typedef void (*objc_uncaught_exception_handler)(id);
+
extern SEL sel_registerName(const char*);
extern const char* sel_getName(SEL);
extern BOOL sel_isEqual(SEL, SEL);
@@ -128,4 +130,7 @@ extern BOOL protocol_conformsToProtocol(Protocol*, Protocol*);
extern void objc_thread_add(void);
extern void objc_thread_remove(void);
extern void objc_exit(void);
+extern objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(
+ objc_uncaught_exception_handler);
+
#endif