| /* The implementation of exception handling primitives for Objective-C. |
| Copyright (C) 2004-2022 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published by the |
| Free Software Foundation; either version 3, or (at your option) any |
| later version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
| License for more details. |
| |
| Under Section 7 of GPL version 3, you are granted additional |
| permissions described in the GCC Runtime Library Exception, version |
| 3.1, as published by the Free Software Foundation. |
| |
| You should have received a copy of the GNU General Public License and |
| a copy of the GCC Runtime Library Exception along with this program; |
| see the files COPYING3 and COPYING.RUNTIME respectively. If not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "objc-private/common.h" |
| #include <stdlib.h> |
| #include "config.h" |
| #include "objc/runtime.h" |
| #include "objc/objc-exception.h" |
| #include "unwind.h" |
| #include "unwind-pe.h" |
| #include <string.h> /* For memcpy */ |
| |
| /* 'is_kind_of_exception_matcher' is our default exception matcher - |
| it determines if the object 'exception' is of class 'catch_class', |
| or of a subclass. */ |
| static int |
| is_kind_of_exception_matcher (Class catch_class, id exception) |
| { |
| /* NULL catch_class is catch-all (eg, @catch (id object)). */ |
| if (catch_class == Nil) |
| return 1; |
| |
| /* If exception is nil (eg, @throw nil;), then it can only be |
| catched by a catch-all (eg, @catch (id object)). */ |
| if (exception != nil) |
| { |
| Class c; |
| |
| for (c = exception->class_pointer; c != Nil; |
| c = class_getSuperclass (c)) |
| if (c == catch_class) |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* The exception matcher currently in use. */ |
| static objc_exception_matcher |
| __objc_exception_matcher = is_kind_of_exception_matcher; |
| |
| objc_exception_matcher |
| objc_setExceptionMatcher (objc_exception_matcher new_matcher) |
| { |
| objc_exception_matcher old_matcher = __objc_exception_matcher; |
| __objc_exception_matcher = new_matcher; |
| return old_matcher; |
| } |
| |
| /* The uncaught exception handler currently in use. */ |
| static objc_uncaught_exception_handler |
| __objc_uncaught_exception_handler = NULL; |
| |
| objc_uncaught_exception_handler |
| objc_setUncaughtExceptionHandler (objc_uncaught_exception_handler |
| new_handler) |
| { |
| objc_uncaught_exception_handler old_handler |
| = __objc_uncaught_exception_handler; |
| __objc_uncaught_exception_handler = new_handler; |
| return old_handler; |
| } |
| |
| |
| |
| #ifdef __ARM_EABI_UNWINDER__ |
| |
| const _Unwind_Exception_Class __objc_exception_class |
| = {'G', 'N', 'U', 'C', 'O', 'B', 'J', 'C'}; |
| |
| #else |
| |
| /* This is the exception class we report -- "GNUCOBJC". */ |
| static const _Unwind_Exception_Class __objc_exception_class |
| = ((((((((_Unwind_Exception_Class) 'G' |
| << 8 | (_Unwind_Exception_Class) 'N') |
| << 8 | (_Unwind_Exception_Class) 'U') |
| << 8 | (_Unwind_Exception_Class) 'C') |
| << 8 | (_Unwind_Exception_Class) 'O') |
| << 8 | (_Unwind_Exception_Class) 'B') |
| << 8 | (_Unwind_Exception_Class) 'J') |
| << 8 | (_Unwind_Exception_Class) 'C'); |
| |
| #endif |
| |
| /* This is the object that is passed around by the Objective C runtime |
| to represent the exception in flight. */ |
| struct ObjcException |
| { |
| /* This bit is needed in order to interact with the unwind runtime. */ |
| struct _Unwind_Exception base; |
| |
| /* The actual object we want to throw. Note: must come immediately |
| after unwind header. */ |
| id value; |
| |
| #ifdef __ARM_EABI_UNWINDER__ |
| /* Note: we use the barrier cache defined in the unwind control |
| block for ARM EABI. */ |
| #else |
| /* Cache some internal unwind data between phase 1 and phase 2. */ |
| _Unwind_Ptr landingPad; |
| int handlerSwitchValue; |
| #endif |
| }; |
| |
| |
| |
| struct lsda_header_info |
| { |
| _Unwind_Ptr Start; |
| _Unwind_Ptr LPStart; |
| _Unwind_Ptr ttype_base; |
| const unsigned char *TType; |
| const unsigned char *action_table; |
| unsigned char ttype_encoding; |
| unsigned char call_site_encoding; |
| }; |
| |
| static const unsigned char * |
| parse_lsda_header (struct _Unwind_Context *context, const unsigned char *p, |
| struct lsda_header_info *info) |
| { |
| _uleb128_t tmp; |
| unsigned char lpstart_encoding; |
| |
| info->Start = (context ? _Unwind_GetRegionStart (context) : 0); |
| |
| /* Find @LPStart, the base to which landing pad offsets are |
| relative. */ |
| lpstart_encoding = *p++; |
| if (lpstart_encoding != DW_EH_PE_omit) |
| p = read_encoded_value (context, lpstart_encoding, p, &info->LPStart); |
| else |
| info->LPStart = info->Start; |
| |
| /* Find @TType, the base of the handler and exception spec type |
| data. */ |
| info->ttype_encoding = *p++; |
| if (info->ttype_encoding != DW_EH_PE_omit) |
| { |
| #if _GLIBCXX_OVERRIDE_TTYPE_ENCODING |
| /* Older ARM EABI toolchains set this value incorrectly, so use a |
| hardcoded OS-specific format. */ |
| info->ttype_encoding = _GLIBCXX_OVERRIDE_TTYPE_ENCODING; |
| #endif |
| p = read_uleb128 (p, &tmp); |
| info->TType = p + tmp; |
| } |
| else |
| info->TType = 0; |
| |
| /* The encoding and length of the call-site table; the action table |
| immediately follows. */ |
| info->call_site_encoding = *p++; |
| p = read_uleb128 (p, &tmp); |
| info->action_table = p + tmp; |
| |
| return p; |
| } |
| |
| static Class |
| get_ttype_entry (struct lsda_header_info *info, _Unwind_Word i) |
| { |
| _Unwind_Ptr ptr; |
| |
| i *= size_of_encoded_value (info->ttype_encoding); |
| read_encoded_value_with_base (info->ttype_encoding, info->ttype_base, |
| info->TType - i, &ptr); |
| |
| /* NULL ptr means catch-all. Note that if the class is not found, |
| this will abort the program. */ |
| if (ptr) |
| return objc_getRequiredClass ((const char *) ptr); |
| else |
| return 0; |
| } |
| |
| /* Using a different personality function name causes link failures |
| when trying to mix code using different exception handling |
| models. */ |
| #ifdef __USING_SJLJ_EXCEPTIONS__ |
| #define PERSONALITY_FUNCTION __gnu_objc_personality_sj0 |
| #define __builtin_eh_return_data_regno(x) x |
| #elif defined(__SEH__) |
| #define PERSONALITY_FUNCTION __gnu_objc_personality_imp |
| #else |
| #define PERSONALITY_FUNCTION __gnu_objc_personality_v0 |
| #endif |
| |
| #ifdef __ARM_EABI_UNWINDER__ |
| |
| #define CONTINUE_UNWINDING \ |
| do \ |
| { \ |
| if (__gnu_unwind_frame(ue_header, context) != _URC_OK) \ |
| return _URC_FAILURE; \ |
| return _URC_CONTINUE_UNWIND; \ |
| } \ |
| while (0) |
| |
| _Unwind_Reason_Code |
| __attribute__((target ("general-regs-only"))) |
| PERSONALITY_FUNCTION (_Unwind_State state, |
| struct _Unwind_Exception *ue_header, |
| struct _Unwind_Context *context) |
| #else |
| |
| #define CONTINUE_UNWINDING return _URC_CONTINUE_UNWIND |
| |
| #if defined (__SEH__) && !defined (__USING_SJLJ_EXCEPTIONS__) |
| static |
| #endif |
| _Unwind_Reason_Code |
| PERSONALITY_FUNCTION (int version, |
| _Unwind_Action actions, |
| _Unwind_Exception_Class exception_class, |
| struct _Unwind_Exception *ue_header, |
| struct _Unwind_Context *context) |
| #endif |
| { |
| struct ObjcException *xh = (struct ObjcException *) ue_header; |
| |
| struct lsda_header_info info; |
| const unsigned char *language_specific_data; |
| const unsigned char *action_record; |
| const unsigned char *p; |
| _Unwind_Ptr landing_pad, ip; |
| int handler_switch_value; |
| int saw_cleanup = 0, saw_handler, foreign_exception; |
| void *return_object; |
| int ip_before_insn = 0; |
| |
| #ifdef __ARM_EABI_UNWINDER__ |
| _Unwind_Action actions; |
| |
| switch (state & _US_ACTION_MASK) |
| { |
| case _US_VIRTUAL_UNWIND_FRAME: |
| actions = _UA_SEARCH_PHASE; |
| break; |
| |
| case _US_UNWIND_FRAME_STARTING: |
| actions = _UA_CLEANUP_PHASE; |
| if (!(state & _US_FORCE_UNWIND) |
| && ue_header->barrier_cache.sp == _Unwind_GetGR (context, 13)) |
| actions |= _UA_HANDLER_FRAME; |
| break; |
| |
| case _US_UNWIND_FRAME_RESUME: |
| CONTINUE_UNWINDING; |
| break; |
| |
| default: |
| abort(); |
| } |
| actions |= state & _US_FORCE_UNWIND; |
| |
| /* TODO: Foreign exceptions need some attention (e.g. rethrowing |
| doesn't work). */ |
| foreign_exception = 0; |
| |
| /* The dwarf unwinder assumes the context structure holds things |
| like the function and LSDA pointers. The ARM implementation |
| caches these in the exception header (UCB). To avoid rewriting |
| everything we make the virtual IP register point at the UCB. */ |
| ip = (_Unwind_Ptr) ue_header; |
| _Unwind_SetGR (context, 12, ip); |
| |
| #else /* !__ARM_EABI_UNWINDER. */ |
| /* Interface version check. */ |
| if (version != 1) |
| return _URC_FATAL_PHASE1_ERROR; |
| |
| foreign_exception = (exception_class != __objc_exception_class); |
| #endif |
| |
| /* Shortcut for phase 2 found handler for domestic exception. */ |
| if (actions == (_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME) |
| && !foreign_exception) |
| { |
| #ifdef __ARM_EABI_UNWINDER__ |
| handler_switch_value = (int) ue_header->barrier_cache.bitpattern[1]; |
| landing_pad = (_Unwind_Ptr) ue_header->barrier_cache.bitpattern[3]; |
| #else |
| handler_switch_value = xh->handlerSwitchValue; |
| landing_pad = xh->landingPad; |
| #endif |
| goto install_context; |
| } |
| |
| language_specific_data = (const unsigned char *) |
| _Unwind_GetLanguageSpecificData (context); |
| |
| /* If no LSDA, then there are no handlers or cleanups. */ |
| if (! language_specific_data) |
| CONTINUE_UNWINDING; |
| |
| /* Parse the LSDA header. */ |
| p = parse_lsda_header (context, language_specific_data, &info); |
| info.ttype_base = base_of_encoded_value (info.ttype_encoding, context); |
| #ifdef HAVE_GETIPINFO |
| ip = _Unwind_GetIPInfo (context, &ip_before_insn); |
| #else |
| ip = _Unwind_GetIP (context); |
| #endif |
| if (!ip_before_insn) |
| --ip; |
| landing_pad = 0; |
| action_record = 0; |
| handler_switch_value = 0; |
| |
| #ifdef __USING_SJLJ_EXCEPTIONS__ |
| /* The given "IP" is an index into the call-site table, with two |
| exceptions -- -1 means no-action, and 0 means terminate. But |
| since we're using uleb128 values, we've not got random access to |
| the array. */ |
| if ((int) ip < 0) |
| return _URC_CONTINUE_UNWIND; |
| else |
| { |
| _uleb128_t cs_lp, cs_action; |
| do |
| { |
| p = read_uleb128 (p, &cs_lp); |
| p = read_uleb128 (p, &cs_action); |
| } |
| while (--ip); |
| |
| /* Can never have null landing pad for sjlj -- that would have |
| been indicated by a -1 call site index. */ |
| landing_pad = cs_lp + 1; |
| if (cs_action) |
| action_record = info.action_table + cs_action - 1; |
| goto found_something; |
| } |
| #else |
| /* Search the call-site table for the action associated with this |
| IP. */ |
| while (p < info.action_table) |
| { |
| _Unwind_Ptr cs_start, cs_len, cs_lp; |
| _uleb128_t cs_action; |
| |
| /* Note that all call-site encodings are "absolute" |
| displacements. */ |
| p = read_encoded_value (0, info.call_site_encoding, p, &cs_start); |
| p = read_encoded_value (0, info.call_site_encoding, p, &cs_len); |
| p = read_encoded_value (0, info.call_site_encoding, p, &cs_lp); |
| p = read_uleb128 (p, &cs_action); |
| |
| /* The table is sorted, so if we've passed the ip, stop. */ |
| if (ip < info.Start + cs_start) |
| p = info.action_table; |
| else if (ip < info.Start + cs_start + cs_len) |
| { |
| if (cs_lp) |
| landing_pad = info.LPStart + cs_lp; |
| if (cs_action) |
| action_record = info.action_table + cs_action - 1; |
| goto found_something; |
| } |
| } |
| #endif /* __USING_SJLJ_EXCEPTIONS__ */ |
| |
| /* If ip is not present in the table, C++ would call terminate. */ |
| /* ??? As with Java, it's perhaps better to tweek the LSDA to that |
| no-action is mapped to no-entry. */ |
| CONTINUE_UNWINDING; |
| |
| found_something: |
| saw_cleanup = 0; |
| saw_handler = 0; |
| |
| if (landing_pad == 0) |
| { |
| /* If ip is present, and has a null landing pad, there are no |
| cleanups or handlers to be run. */ |
| } |
| else if (action_record == 0) |
| { |
| /* If ip is present, has a non-null landing pad, and a null |
| action table offset, then there are only cleanups present. |
| Cleanups use a zero switch value, as set above. */ |
| saw_cleanup = 1; |
| } |
| else |
| { |
| /* Otherwise we have a catch handler. */ |
| _sleb128_t ar_filter, ar_disp; |
| |
| while (1) |
| { |
| p = action_record; |
| p = read_sleb128 (p, &ar_filter); |
| read_sleb128 (p, &ar_disp); |
| |
| if (ar_filter == 0) |
| { |
| /* Zero filter values are cleanups. */ |
| saw_cleanup = 1; |
| } |
| |
| /* During forced unwinding, we only run cleanups. With a |
| foreign exception class, we have no class info to |
| match. */ |
| else if ((actions & _UA_FORCE_UNWIND) || foreign_exception) |
| ; |
| |
| else if (ar_filter > 0) |
| { |
| /* Positive filter values are handlers. */ |
| Class catch_type = get_ttype_entry (&info, ar_filter); |
| |
| if ((*__objc_exception_matcher) (catch_type, xh->value)) |
| { |
| handler_switch_value = ar_filter; |
| saw_handler = 1; |
| break; |
| } |
| } |
| else |
| { |
| /* Negative filter values are exception specifications, |
| which Objective-C does not use. */ |
| abort (); |
| } |
| |
| if (ar_disp == 0) |
| break; |
| action_record = p + ar_disp; |
| } |
| } |
| |
| if (! saw_handler && ! saw_cleanup) |
| CONTINUE_UNWINDING; |
| |
| if (actions & _UA_SEARCH_PHASE) |
| { |
| if (!saw_handler) |
| CONTINUE_UNWINDING; |
| |
| /* For domestic exceptions, we cache data from phase 1 for phase |
| 2. */ |
| if (!foreign_exception) |
| { |
| #ifdef __ARM_EABI_UNWINDER__ |
| ue_header->barrier_cache.sp = _Unwind_GetGR (context, 13); |
| ue_header->barrier_cache.bitpattern[1] = (_uw) handler_switch_value; |
| ue_header->barrier_cache.bitpattern[3] = (_uw) landing_pad; |
| #else |
| xh->handlerSwitchValue = handler_switch_value; |
| xh->landingPad = landing_pad; |
| #endif |
| } |
| return _URC_HANDLER_FOUND; |
| } |
| |
| install_context: |
| if (saw_cleanup == 0) |
| { |
| return_object = xh->value; |
| if (!(actions & _UA_SEARCH_PHASE)) |
| _Unwind_DeleteException(&xh->base); |
| } |
| |
| _Unwind_SetGR (context, __builtin_eh_return_data_regno (0), |
| __builtin_extend_pointer (saw_cleanup ? xh : return_object)); |
| _Unwind_SetGR (context, __builtin_eh_return_data_regno (1), |
| handler_switch_value); |
| _Unwind_SetIP (context, landing_pad); |
| return _URC_INSTALL_CONTEXT; |
| } |
| |
| static void |
| __objc_exception_cleanup (_Unwind_Reason_Code code __attribute__((unused)), |
| struct _Unwind_Exception *exc) |
| { |
| free (exc); |
| } |
| |
| void |
| objc_exception_throw (id exception) |
| { |
| struct ObjcException *header = calloc (1, sizeof (*header)); |
| |
| memcpy (&header->base.exception_class, &__objc_exception_class, |
| sizeof (__objc_exception_class)); |
| header->base.exception_cleanup = __objc_exception_cleanup; |
| header->value = exception; |
| |
| #ifdef __USING_SJLJ_EXCEPTIONS__ |
| _Unwind_SjLj_RaiseException (&header->base); |
| #else |
| _Unwind_RaiseException (&header->base); |
| #endif |
| |
| /* No exception handler was installed. Call the uncaught exception |
| handler if any is defined. */ |
| if (__objc_uncaught_exception_handler != 0) |
| { |
| (*__objc_uncaught_exception_handler) (exception); |
| } |
| |
| abort (); |
| } |
| |
| #if defined (__SEH__) && !defined (__USING_SJLJ_EXCEPTIONS__) |
| EXCEPTION_DISPOSITION |
| __gnu_objc_personality_seh0 (PEXCEPTION_RECORD ms_exc, void *this_frame, |
| PCONTEXT ms_orig_context, |
| PDISPATCHER_CONTEXT ms_disp) |
| { |
| return _GCC_specific_handler (ms_exc, this_frame, ms_orig_context, |
| ms_disp, __gnu_objc_personality_imp); |
| } |
| #endif |