| /* |
| * Copyright (c) 2013 The Native Client Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <assert.h> |
| #include <setjmp.h> |
| #include <stdio.h> |
| |
| #include <exception> |
| |
| #include "native_client/src/include/nacl_compiler_annotations.h" |
| |
| |
| class MyException { |
| public: |
| explicit MyException(int value) : value(value) {} |
| int value; |
| }; |
| |
| class MyExceptionDerived : public MyException { |
| public: |
| explicit MyExceptionDerived(int value) : MyException(value) {} |
| }; |
| |
| void thrower() { |
| throw MyException(123); |
| } |
| |
| // Test throwing and catching a simple exception. |
| void test_simple_throw_and_catch() { |
| bool caught = false; |
| try { |
| thrower(); |
| } catch (MyException &exc) { |
| assert(exc.value == 123); |
| caught = true; |
| } |
| assert(caught); |
| } |
| |
| |
| // Test that catch() blocks are checked in order. |
| void test_catch_type_matching(bool derived) { |
| bool caught = false; |
| try { |
| if (derived) { |
| throw MyExceptionDerived(100); |
| } else { |
| throw MyException(200); |
| } |
| } catch (MyExceptionDerived &exc) { |
| assert(derived); |
| assert(exc.value == 100); |
| caught = true; |
| } catch (MyException &exc) { |
| assert(!derived); |
| assert(exc.value == 200); |
| caught = true; |
| } |
| assert(caught); |
| } |
| |
| |
| // Test that a catch(T) block matches subtypes of T. |
| void test_catch_subtype_matching() { |
| bool caught = false; |
| try { |
| throw MyExceptionDerived(123); |
| } catch (MyException &exc) { |
| assert(exc.value == 123); |
| caught = true; |
| } |
| assert(caught); |
| } |
| |
| |
| // Test that "catch(...)" (catch-all) blocks work. |
| void test_catch_all() { |
| bool caught = false; |
| try { |
| throw MyException(0); |
| } catch (...) { |
| caught = true; |
| } |
| assert(caught); |
| } |
| |
| |
| // Prevent inlining in order to test that the unwinder properly |
| // searches upwards through multiple frames, skipping this |
| // non-matching frame. |
| __attribute__((noinline)) |
| void non_catcher() { |
| try { |
| thrower(); |
| } catch (int) { |
| assert(false); |
| } |
| } |
| |
| // Test a try+catch in which none of the catch blocks match. Prevent |
| // inlining otherwise the inliner reduces this to a try+catch with |
| // multiple catches, which is already tested by |
| // test_catch_type_matching(). |
| void test_nested_try_blocks() { |
| bool caught = false; |
| try { |
| non_catcher(); |
| } catch (MyException &exc) { |
| assert(exc.value == 123); |
| caught = true; |
| } |
| assert(caught); |
| } |
| |
| |
| void throw_in_catch_block() { |
| try { |
| throw MyException(100); |
| } catch (MyException &exc) { |
| throw MyException(200); |
| } |
| } |
| |
| // Test throwing inside a catch() block. This tests that the SJLJ EH |
| // unwinder updates the frame list correctly before longjmp()ing to |
| // the catch block. |
| void test_throw_in_catch_block() { |
| bool caught = false; |
| try { |
| throw_in_catch_block(); |
| } catch (MyException &exc2) { |
| assert(exc2.value == 200); |
| caught = true; |
| } |
| assert(caught); |
| } |
| |
| |
| class Dtor { |
| bool *ptr_; |
| public: |
| explicit Dtor(bool *ptr) : ptr_(ptr) {} |
| ~Dtor() { *ptr_ = true; } |
| }; |
| |
| // Without "noinline", this can get inlined into test_dtor(), which |
| // means we are no longer testing the "cleanup" attribute of |
| // landingpads. |
| __attribute__((noinline)) |
| void func_with_dtor(bool *ptr) { |
| Dtor dtor(ptr); |
| throw MyException(0); |
| } |
| |
| // Test that destructors get called. This tests the LLVM "resume" |
| // instruction. |
| void test_dtor() { |
| bool caught = false; |
| try { |
| func_with_dtor(&caught); |
| } catch (MyException &) { |
| assert(caught); |
| } |
| assert(caught); |
| } |
| |
| |
| void rethrow_func() { |
| try { |
| throw MyException(300); |
| } catch (...) { |
| throw; |
| } |
| assert(false); |
| } |
| |
| // Test re-throwing the current exception with "throw;". |
| void test_rethrow() { |
| bool caught = false; |
| try { |
| rethrow_func(); |
| } catch (MyException &exc) { |
| assert(exc.value == 300); |
| caught = true; |
| } |
| assert(caught); |
| } |
| |
| |
| // Test that throw+catch does not clobber the current exception, which |
| // can be re-thrown with "throw;". |
| void test_stack_of_exceptions() { |
| try { |
| try { |
| throw MyException(10); |
| } catch (...) { |
| // This try+catch block should have no net effect. |
| try { |
| throw MyException(20); |
| } catch (MyException &exc1) { |
| assert(exc1.value == 20); |
| } |
| // Rethrowing should throw the original exception. The C++ |
| // runtime's __cxa_begin_catch() and __cxa_end_catch() functions |
| // need to maintain a thread-local stack of exceptions in order |
| // to implement this. |
| throw; |
| } |
| } catch (MyException &exc2) { |
| assert(exc2.value == 10); |
| } |
| } |
| |
| |
| class ExcBase1 { |
| public: |
| ExcBase1() : base1_val(100) {} |
| int base1_val; |
| }; |
| |
| class ExcBase2 { |
| public: |
| ExcBase2() : base2_val(200) {} |
| int base2_val; |
| }; |
| |
| class ExcDerived : public ExcBase1, public ExcBase2 { |
| }; |
| |
| // Test that the pointer to the exception is adjusted correctly when |
| // passing it to the catch block involves upcasting. Non-zero |
| // adjustments are required when using multiple inheritance: ExcBase2 |
| // is allocated inside ExcDerived at a non-zero offset. |
| void test_multiple_inheritance_exception() { |
| bool caught = false; |
| try { |
| throw ExcDerived(); |
| } catch (ExcBase2 &exc) { |
| assert(exc.base2_val == 200); |
| caught = true; |
| } |
| assert(caught); |
| } |
| |
| // This is like test_multiple_inheritance_exception(), but it tests |
| // throwing and catching a pointer-to-class type rather than a class |
| // type. The same pointer upcasting adjustment for multiple |
| // inheritance is required in both cases. |
| void test_multiple_inheritance_exception_ptr() { |
| bool caught = false; |
| ExcDerived *exc = new ExcDerived(); |
| try { |
| throw exc; |
| } catch (ExcBase2 *exc) { |
| assert(exc->base2_val == 200); |
| caught = true; |
| } |
| assert(caught); |
| delete exc; |
| } |
| |
| |
| class DtorThrowCatch { |
| public: |
| ~DtorThrowCatch() { |
| // This try+catch block should have no net effect. |
| try { |
| throw MyException(40); |
| } catch (MyException &exc) { |
| assert(exc.value == 40); |
| } |
| } |
| }; |
| |
| // This needs to be a separate, non-inlined function so that we are |
| // testing its landingpad executing _Unwind_Resume() (or its SJLJ EH |
| // equivalent). |
| __attribute__((noinline)) |
| void nested_throw_catch() { |
| DtorThrowCatch obj; |
| UNREFERENCED_PARAMETER(obj); |
| throw MyException(50); |
| } |
| |
| // Test that throw+catch inside a destructor does not clobber the |
| // current exception. |
| // |
| // This tests that _Unwind_Resume() (or its SJLJ EH equivalent) uses |
| // the correct exception. _Unwind_Resume() needs to take an exception |
| // as an argument and cannot refer to a thread-local "current |
| // exception". |
| void test_throw_catch_nested_in_dtor() { |
| bool caught = false; |
| try { |
| nested_throw_catch(); |
| } catch (MyException &exc) { |
| assert(exc.value == 50); |
| caught = true; |
| } |
| assert(caught); |
| } |
| |
| |
| // If setjmp() is declared without __attribute__((nothrow)) (as it is |
| // currently in the newlib headers) and is used in an |
| // exception-catching context, Clang will call it via an "invoke" |
| // instruction rather than a "call" instruction. Check that this |
| // works. |
| // |
| // This tests for a bug in PNaClSjLjEH in which the setjmp() call is |
| // moved into a helper function. |
| void test_setjmp_called_via_invoke() { |
| try { |
| jmp_buf buf; |
| if (!setjmp(buf)) { |
| longjmp(buf, 1); |
| // When the bug applies, longjmp() returns to an old stack frame |
| // where the return address has been overwritten with |
| // longjmp()'s return address, so we return here. |
| assert(false); |
| } |
| } catch (...) { |
| assert(false); |
| } |
| } |
| |
| |
| void allowed_exception_spec() throw(MyException) { |
| throw MyException(600); |
| } |
| |
| // Test a case in which an exception propagates through an exception |
| // spec which allows the exception type. |
| void test_exception_spec_allowed() { |
| bool caught = false; |
| try { |
| allowed_exception_spec(); |
| } catch (MyException &exc) { |
| assert(exc.value == 600); |
| caught = true; |
| } |
| assert(caught); |
| } |
| |
| |
| class VirtualBase { |
| public: |
| VirtualBase() : value(321) {} |
| int value; |
| }; |
| |
| class VirtualDerived : virtual public VirtualBase {}; |
| |
| void allowed_exception_spec_virtual_base() throw(VirtualBase) { |
| throw VirtualDerived(); |
| } |
| |
| // Test that exception types with virtual base classes work when the |
| // base class appears in an exception spec. |
| // |
| // In this case, the C++ runtime library must dereference the |
| // exception's vtable to find the offset of VirtualBase within |
| // VirtualDerived. That is not the case with non-virtual bases, for |
| // which the std::type_info data is enough to adjust the pointer to |
| // the exception without dereferencing it. |
| void test_exception_spec_allowed_with_virtual_base() { |
| bool caught = false; |
| try { |
| allowed_exception_spec_virtual_base(); |
| } catch (VirtualBase &exc) { |
| assert(exc.value == 321); |
| caught = true; |
| } |
| assert(caught); |
| } |
| |
| |
| class RethrowExc {}; |
| |
| bool g_dtor_called; |
| |
| void rethrow_unexpected_handler() { |
| // func_with_exception_spec()'s destructors should be run before the |
| // exception spec (a "filter" clause in LLVM) is checked and we are |
| // called. |
| // |
| // This means that the std::set_unexpected() handler should be |
| // called from a landingpad inside the function with the exception |
| // spec (by calling __cxa_call_unexpected()), rather than by the |
| // personality function (or the SJLJ EH equivalent) which checks the |
| // exception against the exception spec. |
| assert(g_dtor_called); |
| |
| throw RethrowExc(); |
| } |
| |
| // Example of a function with an exception spec, i.e. the "throw(...)" |
| // attribute on the function. |
| void func_with_exception_spec() throw(RethrowExc) { |
| Dtor dtor(&g_dtor_called); |
| throw MyException(100); |
| } |
| |
| // Test that exception specs work. Test that if an exception doesn't |
| // match the exception spec of a function, the current |
| // std::set_unexpected() handler is called. The handler is allowed to |
| // throw a new exception that matches the exception spec -- we test |
| // this case to avoid aborting execution. |
| void test_exception_spec_calls_handler() { |
| g_dtor_called = false; |
| std::unexpected_handler old_unexpected_handler = |
| std::set_unexpected(rethrow_unexpected_handler); |
| bool caught = false; |
| try { |
| func_with_exception_spec(); |
| } catch (RethrowExc &) { |
| caught = true; |
| } |
| assert(caught); |
| // Clean up. |
| std::set_unexpected(old_unexpected_handler); |
| } |
| |
| |
| void unexpected_handler_throwing_virtual_base() { |
| throw VirtualDerived(); |
| } |
| |
| void disallowed_exception_spec_virtual_base() throw(VirtualBase) { |
| throw MyException(0); |
| } |
| |
| // Test that if a std::set_unexpected() handler throws an exception |
| // with a virtual base class, this new exception is checked against |
| // the original exception spec correctly. |
| void test_exception_spec_handler_throws_virtual_base() { |
| std::unexpected_handler old_unexpected_handler = |
| std::set_unexpected(unexpected_handler_throwing_virtual_base); |
| bool caught = false; |
| try { |
| disallowed_exception_spec_virtual_base(); |
| } catch (VirtualBase &exc) { |
| assert(exc.value == 321); |
| caught = true; |
| } |
| assert(caught); |
| // Clean up. |
| std::set_unexpected(old_unexpected_handler); |
| } |
| |
| |
| void rethrowing_unexpected_handler() { |
| try { |
| throw; |
| } catch (MyException &exc) { |
| assert(exc.value == 100); |
| } |
| throw RethrowExc(); |
| } |
| |
| // Test that the "current exception" inside the std::set_unexpected() |
| // handler is the exception that didn't match the exception spec. |
| // This allows the handler to inspect this exception by rethrowing and |
| // catching it. |
| void test_exception_spec_rethrow_inside_unexpected_handler() { |
| std::unexpected_handler old_unexpected_handler = |
| std::set_unexpected(rethrowing_unexpected_handler); |
| bool caught = false; |
| try { |
| func_with_exception_spec(); |
| } catch (RethrowExc &) { |
| caught = true; |
| } |
| assert(caught); |
| // Clean up. |
| std::set_unexpected(old_unexpected_handler); |
| } |
| |
| |
| void exception_spec_allowing_bad_exception() throw(std::bad_exception) { |
| throw MyException(100); |
| } |
| |
| // Test that if: |
| // 1) a std::set_unexpected() handler throws an exception that does |
| // not match the exception spec, and |
| // 2) an exception spec allows "std::bad_exception", |
| // then the exception thrown by the set_unexpected() handler gets |
| // converted to a std::bad_exception exception. |
| void test_exception_spec_convert_to_bad_exception() { |
| std::unexpected_handler old_unexpected_handler = |
| std::set_unexpected(rethrow_unexpected_handler); |
| bool caught = false; |
| try { |
| exception_spec_allowing_bad_exception(); |
| } catch (std::bad_exception &) { |
| caught = true; |
| } |
| assert(caught); |
| // Clean up. |
| std::set_unexpected(old_unexpected_handler); |
| } |
| |
| |
| jmp_buf g_jmp_buf; |
| |
| void terminate_handler() { |
| longjmp(g_jmp_buf, 999); |
| } |
| |
| void bad_unexpected_handler() { |
| throw MyException(123); |
| } |
| |
| // Test that if: |
| // 1) a std::set_unexpected() handler throws an exception that does |
| // not match the exception spec, and |
| // 2) an exception spec does *not* allow "std::bad_exception", |
| // then the C++ runtime calls std::terminate(). |
| void test_exception_spec_bad_throw_from_unexpected_handler() { |
| std::unexpected_handler old_unexpected_handler = |
| std::set_unexpected(bad_unexpected_handler); |
| // To test that std::terminate() is called, but avoid aborting |
| // execution, we register a terminate handler but longjmp() out of |
| // it to continue execution. |
| std::terminate_handler old_terminate_handler = |
| std::set_terminate(terminate_handler); |
| int val = setjmp(g_jmp_buf); |
| if (val == 0) { |
| try { |
| func_with_exception_spec(); |
| } catch (MyException &) { |
| // We should not be able to reach here by catching MyException, |
| // because it does not meet func_with_exception_spec()'s |
| // exception spec. The std::set_unexpected() handler should not |
| // be able to throw MyException to here. |
| assert(false); |
| } |
| // We certainly don't expect func_with_exception_spec() to return. |
| assert(false); |
| } |
| assert(val == 999); |
| // Clean up. |
| std::set_unexpected(old_unexpected_handler); |
| std::set_terminate(old_terminate_handler); |
| } |
| |
| |
| #if SUPPORTS_CXX11 |
| |
| // Test std::current_exception() and std::rethrow_exception(), which |
| // were added in C++11. The std::exception_ptr type allows capturing |
| // a reference to an exception, which can have a lifetime outside a |
| // catch() block and so is refcounted. This is called a "dependent |
| // exception" (__cxa_dependent_exception) inside libsupc++/libcxxabi. |
| void test_dependent_exception() { |
| assert(!std::current_exception()); |
| |
| std::exception_ptr exc_ptr; |
| int *ptr_to_value; |
| try { |
| throw MyException(400); |
| } catch (MyException &exc) { |
| ptr_to_value = &exc.value; |
| exc_ptr = std::current_exception(); |
| } |
| |
| try { |
| std::rethrow_exception(exc_ptr); |
| } catch (MyException &exc) { |
| assert(exc.value == 400); |
| // The exception is refcounted and not copied. |
| assert(&exc.value == ptr_to_value); |
| } |
| } |
| |
| |
| __attribute__((noinline)) |
| void rethrow_through_dtor(bool *ptr) { |
| Dtor dtor(ptr); |
| std::rethrow_exception(std::current_exception()); |
| } |
| |
| // Similar to test_dtor(), but testing a dependent exception (i.e. one |
| // thrown with std::rethrow_exception()). |
| // |
| // This tests that the C++ runtime library handles dependent |
| // exceptions correctly when the first matching landingpad is a |
| // cleanup handler. |
| void test_dependent_exception_and_dtor() { |
| try { |
| throw MyException(500); |
| } catch (MyException &exc) { |
| bool caught = false; |
| try { |
| rethrow_through_dtor(&caught); |
| } catch (MyException &exc2) { |
| assert(caught); |
| return; |
| } |
| } |
| assert(false); |
| } |
| |
| |
| void throw_dependent_exception() { |
| try { |
| throw RethrowExc(); |
| } catch (...) { |
| std::rethrow_exception(std::current_exception()); |
| } |
| } |
| |
| // Test the case in which a std::set_unexpected() handler throws an |
| // exception using std::rethrow_exception(). |
| // |
| // This tests that __cxa_call_unexpected() handles dependent |
| // exceptions correctly. |
| void test_dependent_exception_and_exception_spec() { |
| std::unexpected_handler old_unexpected_handler = |
| std::set_unexpected(throw_dependent_exception); |
| bool caught = false; |
| try { |
| func_with_exception_spec(); |
| } catch (RethrowExc &) { |
| caught = true; |
| } |
| assert(caught); |
| // Clean up. |
| std::set_unexpected(old_unexpected_handler); |
| } |
| |
| #endif |
| |
| |
| #define RUN_TEST(CALL) printf("Running %s\n", #CALL); CALL; |
| |
| int main() { |
| RUN_TEST(test_simple_throw_and_catch()); |
| RUN_TEST(test_catch_type_matching(false)); |
| RUN_TEST(test_catch_type_matching(true)); |
| RUN_TEST(test_catch_subtype_matching()); |
| RUN_TEST(test_catch_all()); |
| RUN_TEST(test_nested_try_blocks()); |
| RUN_TEST(test_throw_in_catch_block()); |
| RUN_TEST(test_dtor()); |
| RUN_TEST(test_rethrow()); |
| RUN_TEST(test_stack_of_exceptions()); |
| RUN_TEST(test_multiple_inheritance_exception()); |
| RUN_TEST(test_multiple_inheritance_exception_ptr()); |
| RUN_TEST(test_throw_catch_nested_in_dtor()); |
| RUN_TEST(test_setjmp_called_via_invoke()); |
| RUN_TEST(test_exception_spec_allowed()); |
| RUN_TEST(test_exception_spec_allowed_with_virtual_base()); |
| RUN_TEST(test_exception_spec_calls_handler()); |
| RUN_TEST(test_exception_spec_handler_throws_virtual_base()); |
| RUN_TEST(test_exception_spec_rethrow_inside_unexpected_handler()); |
| RUN_TEST(test_exception_spec_convert_to_bad_exception()); |
| #if SUPPORTS_CXX11 |
| RUN_TEST(test_dependent_exception()); |
| RUN_TEST(test_dependent_exception_and_dtor()); |
| RUN_TEST(test_dependent_exception_and_exception_spec()); |
| #endif |
| |
| // The ARM EABI version of upstream libstdc++ has a bug that bites this case. |
| // See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59392 for details. |
| // TODO(mcgrathr): If/when that gets fixed upstream, merge the fix into |
| // arm-nacl-gcc and remove this conditionalization. |
| #if defined(__pnacl__) || !defined(__arm__) |
| // This leaves behind an active exception because of its use of |
| // longjmp() to exit from a std::set_terminate() handler, so put it |
| // last, just in case that accidentally affects other tests. |
| RUN_TEST(test_exception_spec_bad_throw_from_unexpected_handler()); |
| #endif |
| |
| // Indicate success. |
| return 55; |
| } |