Index: src/cxa_pnacl_sjlj_exception.cpp |
diff --git a/src/cxa_pnacl_sjlj_exception.cpp b/src/cxa_pnacl_sjlj_exception.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0b9f7cfaa07d8ea652d0eabf914a93ee4d863e84 |
--- /dev/null |
+++ b/src/cxa_pnacl_sjlj_exception.cpp |
@@ -0,0 +1,408 @@ |
+//===- cxa_pnacl_sjlj_exception.cpp - PNaCl SJLJ-based exception handling--===// |
+// |
+// The LLVM Compiler Infrastructure |
+// |
+// This file is dual licensed under the MIT and the University of Illinois Open |
+// Source Licenses. See LICENSE.TXT for details. |
+// |
+//===----------------------------------------------------------------------===// |
+// |
+// This implements setjmp()/longjmp()-based (SJLJ) C++ exception |
+// handling for PNaCl. This uses the C++ exception info tables |
+// generated by the PNaClSjLjEH LLVM pass. |
+// |
+// Each __pnacl_eh_sjlj_Unwind_*() function below provides the |
+// definition of _Unwind_*(). |
+// |
+// The "__pnacl_eh_sjlj" prefix is added so that PNaCl's SJLJ |
+// (setjmp()/longjmp()-based) implementation of C++ exception handling |
+// can coexist with other implementations in the same build of |
+// libc++/libcxxabi. When SJLJ EH is enabled, each |
+// __pnacl_eh_sjlj_Unwind_*() symbol will get renamed to _Unwind_*() |
+// when linking a PNaCl pexe. |
+// |
+//===----------------------------------------------------------------------===// |
+ |
+#include <setjmp.h> |
+#include <stdint.h> |
+#include <stdlib.h> |
+#include <string.h> |
+#include <typeinfo> |
+ |
+#include "cxa_exception.hpp" |
+#include "cxa_handlers.hpp" |
+#include "private_typeinfo.h" |
+ |
+using namespace __cxxabiv1; |
+ |
+ |
+// Exception info written by ExceptionInfoWriter.cpp. |
+ |
+struct action_table_entry { |
+ int32_t clause_id; |
+ uint32_t next_clause_list_id; |
+}; |
+ |
+extern const struct action_table_entry __pnacl_eh_action_table[]; |
+extern const __shim_type_info *const __pnacl_eh_type_table[]; |
+extern const int32_t __pnacl_eh_filter_table[]; |
+ |
+// Data structures used by PNaClSjLjEH.cpp. |
+ |
+struct landing_pad_result { |
+ void *exception_obj; |
+ uint32_t matched_clause_id; |
+}; |
+ |
+struct exception_frame { |
+ union { |
+ jmp_buf jmpbuf; |
+ struct landing_pad_result result; |
+ }; |
+ struct exception_frame *next; |
+ uint32_t clause_list_id; |
+}; |
+ |
+__thread struct exception_frame *__pnacl_eh_stack; |
+ |
+ |
+// Returns whether the thrown exception (specified by throw_type and |
+// obj) matches none of the exception types in a C++ exception |
+// specification (specified by filter_id). |
+static bool |
+exception_spec_can_catch(const __shim_type_info *throw_type, void *obj, |
+ int32_t filter_id) |
+{ |
+ const int32_t *filter_ptr = |
+ &__pnacl_eh_filter_table[-filter_id - 1]; |
+ for (; *filter_ptr != 0; ++filter_ptr) |
+ { |
+ const __shim_type_info *catch_type = |
+ __pnacl_eh_type_table[*filter_ptr - 1]; |
+ // We ignore the modified value of obj here. |
+ if (catch_type->can_catch(throw_type, obj)) |
+ return false; |
+ } |
+ // No type matched, so we have an exception specification error. |
+ return true; |
+} |
+ |
+// Returns whether the thrown exception (specified by throw_type and |
+// obj) matches the given landingpad clause (clause_id). |
+// |
+// If the exception matches and the clause is a "catch" clause, this |
+// adjusts *obj to upcast it to the type specified in the "catch" |
+// clause. (For example, if throw_type uses multiple inheritance and |
+// derives from multiple base classes, this might involve adding a |
+// constant offset to *obj.) |
+static bool |
+does_clause_match(const __shim_type_info *throw_type, void **obj, |
+ int32_t clause_id) |
+{ |
+ // Handle "cleanup" clause. |
+ if (clause_id == 0) |
+ return true; |
+ |
+ // Handle "filter" clause. |
+ if (clause_id < 0) |
+ return exception_spec_can_catch(throw_type, obj, clause_id); |
+ |
+ // Handle "catch" clause. |
+ const __shim_type_info *catch_type = __pnacl_eh_type_table[clause_id - 1]; |
+ if (catch_type == NULL) |
+ return true; |
+ return catch_type->can_catch(throw_type, *obj); |
+} |
+ |
+// Returns whether the given frame should be entered in order to |
+// handle the thrown exception (specified by throw_type and *obj). If |
+// so, this adjusts *obj (see does_clause_match()) and sets |
+// *result_clause_id. |
+static bool |
+does_frame_match(const __shim_type_info *throw_type, void **obj, |
+ struct exception_frame *frame, int32_t *result_clause_id) |
+{ |
+ for (int32_t clause_list_id = frame->clause_list_id; clause_list_id != 0; ) |
+ { |
+ const struct action_table_entry *list_node = |
+ &__pnacl_eh_action_table[clause_list_id - 1]; |
+ if (does_clause_match(throw_type, obj, list_node->clause_id)) |
+ { |
+ *result_clause_id = list_node->clause_id; |
+ return true; |
+ } |
+ clause_list_id = list_node->next_clause_list_id; |
+ } |
+ return false; |
+} |
+ |
+// Search for a stack frame that will handle the given exception, |
+// starting from frame. The exception is specified by throw_type and |
+// *obj. |
+// |
+// If a frame is found that will handle the exception, this adjusts |
+// *obj (to upcast it to the "catch" type, if there is one), sets |
+// *result_frame and *result_clause_id to the frame and clause ID that |
+// matched the exception, and returns true. |
+static bool |
+find_match(const __shim_type_info *throw_type, void **obj, |
+ struct exception_frame *frame, |
+ struct exception_frame **result_frame, int32_t *result_clause_id) |
+{ |
+ for (; frame != NULL; frame = frame->next) |
+ { |
+ if (does_frame_match(throw_type, obj, frame, result_clause_id)) |
+ { |
+ *result_frame = frame; |
+ return true; |
+ } |
+ } |
+ return false; |
+} |
+ |
+// Search for a non-cleanup stack frame that will handle the given |
+// exception, starting from frame. Returns whether a matching frame |
+// was found. |
+static bool |
+is_exception_caught(const __shim_type_info *throw_type, void *obj, |
+ struct exception_frame *frame) |
+{ |
+ for (; frame != NULL; frame = frame->next) |
+ { |
+ int32_t clause_id; |
+ if (does_frame_match(throw_type, &obj, frame, &clause_id) |
+ && clause_id != 0) |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+static __cxa_exception * |
+get_exception_header_from_ue(struct _Unwind_Exception *ue_header) |
+{ |
+ return (__cxa_exception *) (ue_header + 1) - 1; |
+} |
+ |
+static __cxa_dependent_exception * |
+get_dependent_exception_from_ue(struct _Unwind_Exception *ue_header) |
+{ |
+ return (__cxa_dependent_exception *) (ue_header + 1) - 1; |
+} |
+ |
+static void * |
+get_object_from_ue(struct _Unwind_Exception *ue_header) |
+{ |
+ if (ue_header->exception_class == kOurDependentExceptionClass) |
+ { |
+ return get_dependent_exception_from_ue(ue_header)->primaryException; |
+ } |
+ return ue_header + 1; |
+} |
+ |
+// handle_exception() is called by _Unwind_RaiseException(). It |
+// unwinds the stack, looking for the first C++ destructor or catch() |
+// block to pass control to. In LLVM terms, it searches for the first |
+// matching invoke/landingpad instruction. When it finds a match, |
+// this implementation passes control to the landingpad block by |
+// longjmp()'ing to it. |
+// |
+// In a traditional implementation (based on the Itanium ABI), |
+// _Unwind_RaiseException() is implemented in a library (libgcc_eh) |
+// that is separate from libcxxabi. It calls back to libcxxabi's |
+// personality function (__gxx_personality_v0()) to determine whether |
+// a call on the stack has a handler for the exception. |
+// __gxx_personality_v0() knows that ue_header was allocated by |
+// libcxxabi's __cxa_allocate_exception() and can downcast it to |
+// libcxxabi's __cxa_exception type. |
+// |
+// In contrast, in the implementation below, the functionality of the |
+// personality function is folded into _Unwind_RaiseException(), so |
+// this implements C++-specific matching of exceptions and downcasts |
+// ue_header to __cxa_exception immediately. |
+// |
+// This function returns if stack unwinding did not find any stack |
+// frames that match the exception being thrown. |
+static void |
+handle_exception(struct _Unwind_Exception *ue_header, bool check_for_catch) |
+{ |
+ __cxa_exception *xh = get_exception_header_from_ue(ue_header); |
+ |
+ void *obj = get_object_from_ue(ue_header); |
+ struct exception_frame *frame; |
+ int32_t clause_id; |
+ if (!find_match((__shim_type_info *) xh->exceptionType, &obj, |
+ __pnacl_eh_stack, &frame, &clause_id)) |
+ return; |
+ |
+ // Check that there is a non-cleanup handler for the exception. |
+ // If not, we should abort before running cleanup handlers |
+ // (i.e. destructors). |
+ // |
+ // This is mainly a convenience for debugging. It means that if |
+ // the program throws an uncaught exception, the location of the |
+ // "throw" will be on the stack when the program aborts. If we |
+ // ran cleanup handlers before aborting, this context would be |
+ // lost. |
+ // |
+ // This is optional in the C++ standard, which says "If no |
+ // matching handler is found, the function std::terminate() is |
+ // called; whether or not the stack is unwound before this call to |
+ // std::terminate() is implementation-defined". |
+ if (check_for_catch && clause_id == 0 && |
+ !is_exception_caught((__shim_type_info *) xh->exceptionType, obj, |
+ frame->next)) |
+ return; |
+ |
+ __pnacl_eh_stack = frame->next; |
+ |
+ // Save adjusted exception pointer so that it can be returned by |
+ // __cxa_begin_catch() when entering a catch() block. |
+ xh->adjustedPtr = obj; |
+ |
+ // Save the clause ID so that if the landingpad block calls |
+ // __cxa_call_unexpected() and the std::set_unexpected() handler |
+ // throws an exception, we can re-check that exception against the |
+ // exception specification. |
+ xh->handlerSwitchValue = clause_id; |
+ |
+ // exception_frame uses the same location for storing the jmp_buf |
+ // and the landing_pad_result, so we must make a copy of the |
+ // jmp_buf first. |
+ jmp_buf jmpbuf_copy; |
+ memcpy(&jmpbuf_copy, &frame->jmpbuf, sizeof(jmpbuf_copy)); |
+ |
+ // Return to the landingpad block, passing it two values. |
+ frame->result.exception_obj = ue_header; |
+ frame->result.matched_clause_id = clause_id; |
+ longjmp(jmpbuf_copy, 1); |
+} |
+ |
+ |
+// This implements _Unwind_RaiseException(). This is called when |
+// raising an exception for the first time, i.e. for the statement |
+// "throw EXPR;". The compiler lowers "throw EXPR;" to: |
+// * a call to __cxa_allocate_exception() to allocate memory; |
+// * a call to __cxa_throw() which throws the exception by calling |
+// _Unwind_RaiseException(). |
+extern "C" _Unwind_Reason_Code |
+__pnacl_eh_sjlj_Unwind_RaiseException(struct _Unwind_Exception *ue_header) |
+{ |
+ handle_exception(ue_header, true); |
+ return _URC_END_OF_STACK; |
+} |
+ |
+// This is the equivalent of _Unwind_Resume() from libgcc_eh, but we |
+// use a different name for PNaCl SJLJ to avoid accidental collisions |
+// with libgcc_eh. |
+// |
+// This is called by a landingpad block as a final step after it has |
+// run C++ destructors. This is only called by a landingpad if it did |
+// not enter a catch() block. |
+// |
+// This function never returns. |
+extern "C" void |
+__pnacl_eh_resume(struct _Unwind_Exception *ue_header) |
+{ |
+ // Pass check_for_catch=false so that unwinding does not take O(n^2) |
+ // time in the number of cleanup landingpads entered before entering |
+ // the catch() block. |
+ handle_exception(ue_header, false); |
+ |
+ // We've run C++ destructors (cleanup handlers), but no further |
+ // handlers were found, so abort. We should not reach here, because |
+ // __pnacl_eh_sjlj_Unwind_RaiseException() already checked that |
+ // there was a handler for this exception other than cleanup |
+ // handlers. |
+ __cxa_begin_catch(ue_header); |
+ std::terminate(); |
+} |
+ |
+// _Unwind_Resume_or_Rethrow() is called when rethrowing an |
+// exception, i.e. for the statement "throw;" (with no arguments). |
+// The compiler lowers "throw;" to a call to __cxa_rethrow(), which |
+// calls this function. |
+extern "C" _Unwind_Reason_Code |
+__pnacl_eh_sjlj_Unwind_Resume_or_Rethrow(struct _Unwind_Exception *ue_header) |
+{ |
+ return __pnacl_eh_sjlj_Unwind_RaiseException(ue_header); |
+} |
+ |
+// A convenience function that calls the exception_cleanup field. |
+// Based on the definition in libgcc_eh's unwind.inc. |
+// |
+// This is called when a catch() block that handles an exception exits |
+// without rethrowing the exception. This is called by |
+// __cxa_end_catch(). The compiler generates a call to |
+// __cxa_end_catch() at the end of a catch() block. |
+extern "C" void |
+__pnacl_eh_sjlj_Unwind_DeleteException(struct _Unwind_Exception *exc) |
+{ |
+ if (exc->exception_cleanup) |
+ (*exc->exception_cleanup)(_URC_FOREIGN_EXCEPTION_CAUGHT, exc); |
+} |
+ |
+// This function implements __cxa_call_unexpected(), which is called |
+// by a landingpad block when an exception is thrown that doesn't |
+// match a function's exception spec (i.e. a "throw(...)" attribute on |
+// a function). Calls to __cxa_call_unexpected() are generated by the |
+// C++ front end. |
+// |
+// This calls the handler registered with std::set_unexpected(). This |
+// handler is allowed to throw, in which case we must re-check the |
+// resulting exception against the original exception specification. |
+// |
+// The reason that __cxa_call_unexpected() is called by landingpad |
+// code rather than by the personality function is so that the |
+// landingpad code can run destructors first. |
+// |
+// This is loosely based on the __cxa_call_unexpected() implementation |
+// in cxa_personality.cpp. |
+// |
+// This function never returns. |
+extern "C" void |
+__pnacl_eh_sjlj_cxa_call_unexpected(struct _Unwind_Exception *ue_header) |
+{ |
+ // Mark the exception as being handled, so that the |
+ // set_unexpected() handler can rethrow it. |
+ __cxa_begin_catch(ue_header); |
+ |
+ // Ensure that the corresponding __cxa_end_catch() call happens on |
+ // all paths out of this function. |
+ struct do_end_catch |
+ { |
+ ~do_end_catch() { __cxa_end_catch(); } |
+ } do_end_catch_obj; |
+ |
+ __cxa_exception *old_exception_header = |
+ get_exception_header_from_ue(ue_header); |
+ int32_t filter_id = old_exception_header->handlerSwitchValue; |
+ std::unexpected_handler u_handler = old_exception_header->unexpectedHandler; |
+ std::terminate_handler t_handler = old_exception_header->terminateHandler; |
+ |
+ try |
+ { |
+ std::__unexpected(u_handler); |
+ } |
+ catch (...) |
+ { |
+ __cxa_eh_globals *globals = __cxa_get_globals_fast(); |
+ __cxa_exception *new_exception_header = globals->caughtExceptions; |
+ |
+ // If the handler threw an exception that is allowed by the |
+ // original exception spec, allow this exception to propagate. |
+ if (!exception_spec_can_catch( |
+ (const __shim_type_info *) new_exception_header->exceptionType, |
+ get_object_from_ue(&new_exception_header->unwindHeader), |
+ filter_id)) |
+ throw; |
+ |
+ // Otherwise, if the original exception spec allows |
+ // std::bad_exception, throw an exception of that type. |
+ std::bad_exception be; |
+ const __shim_type_info *be_type = |
+ (const __shim_type_info *) &typeid(be); |
+ if (!exception_spec_can_catch(be_type, &be, filter_id)) |
+ throw be; |
+ } |
+ std::__terminate(t_handler); |
+} |