[EH] Handle dependent exceptions in refcounting In libcxxabi used in Wasm EH, `__cxa_exception` is used to represent a normal primary exception, and `__cxa_dependent_exception` is to a dependent exception created by `std::rethrow_exception`. `std::rethrow_exception` allocates and throws a `__cxa_dependent_exception` whose `primaryException` field points to its primary exception. `__cxa_exception` and `__cxa_dependent_exception`'s layouts are identical except where `referenceCount` is in `__cxa_exception`, there is `primaryException` in `__cxa_dependent_exception`: https://github.com/emscripten-core/emscripten/blob/30bbb45939d0136eae1637dd17146cc02ee1658f/system/lib/libcxxabi/src/cxa_exception.h#L100-L101 But `__cxa_in/decrementExceptionRefcount` assumes all it gets is primary exceptions, and tries to increment the `referenceCount` field, which ends up incrementing `primaryException` pointer in `__cxa_dependent_exception`. This is not a problem within libcxxabi because all callers to those methods extract a primary exception from a dependent exception before calling. But these methods are also called from JS `in/decrementExceptionRefcount`, which doesn't know about dependent exceptions: https://github.com/emscripten-core/emscripten/blob/30bbb45939d0136eae1637dd17146cc02ee1658f/src/lib/libexceptions.js#L381-L391 This checks whether an exception is a dependent exception, and if it is, extracts the primary exception from it, and in/decrement its refcount. And if it reaches 0, also frees the dependent exception in addition to the primary one. We also have similar code to handle dependent exceptions in `__get_exception_message`: https://github.com/emscripten-core/emscripten/blob/30bbb45939d0136eae1637dd17146cc02ee1658f/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp#L89-L95 This is a part of fixes for #27705.
diff --git a/system/lib/libcxxabi/src/cxa_exception.cpp b/system/lib/libcxxabi/src/cxa_exception.cpp index 443ac28..b06112d 100644 --- a/system/lib/libcxxabi/src/cxa_exception.cpp +++ b/system/lib/libcxxabi/src/cxa_exception.cpp
@@ -689,6 +689,15 @@ if (thrown_object != NULL ) { __cxa_exception* exception_header = cxa_exception_from_thrown_object(thrown_object); +#ifdef __WASM_EXCEPTIONS__ + if (isDependentException(&exception_header->unwindHeader)) + { + __cxa_dependent_exception* dep_exception_header = + reinterpret_cast<__cxa_dependent_exception*>(exception_header); + thrown_object = dep_exception_header->primaryException; + exception_header = cxa_exception_from_thrown_object(thrown_object); + } +#endif std::__libcpp_atomic_add(&exception_header->referenceCount, size_t(1)); } } @@ -706,11 +715,25 @@ if (thrown_object != NULL ) { __cxa_exception* exception_header = cxa_exception_from_thrown_object(thrown_object); +#ifdef __WASM_EXCEPTIONS__ + __cxa_dependent_exception* dep_exception_header = NULL; + if (isDependentException(&exception_header->unwindHeader)) + { + dep_exception_header = + reinterpret_cast<__cxa_dependent_exception*>(exception_header); + thrown_object = dep_exception_header->primaryException; + exception_header = cxa_exception_from_thrown_object(thrown_object); + } +#endif if (std::__libcpp_atomic_add(&exception_header->referenceCount, size_t(-1)) == 0) { if (NULL != exception_header->exceptionDestructor) exception_header->exceptionDestructor(thrown_object); __cxa_free_exception(thrown_object); +#ifdef __WASM_EXCEPTIONS__ + if (NULL != dep_exception_header) + __cxa_free_dependent_exception(dep_exception_header); +#endif } } }
diff --git a/test/core/test_exceptions_refcount_2.cpp b/test/core/test_exceptions_refcount_2.cpp index 80836f0..9aeab09 100644 --- a/test/core/test_exceptions_refcount_2.cpp +++ b/test/core/test_exceptions_refcount_2.cpp
@@ -15,12 +15,24 @@ } } +EMSCRIPTEN_KEEPALIVE extern "C" void rethrow_ptr() { + try { + throw MyException(); + } catch (...) { + std::rethrow_exception(std::current_exception()); + } +} + int main() { EM_ASM({ - try { - _rethrow(); - } catch (e) { - decrementExceptionRefcount(e); + for (let fn of [_rethrow, _rethrow_ptr]) { + try { + fn(); + } catch (e) { + incrementExceptionRefcount(e); + decrementExceptionRefcount(e); + decrementExceptionRefcount(e); + } } }); }
diff --git a/test/core/test_exceptions_refcount_2.out b/test/core/test_exceptions_refcount_2.out index 328c373..514bfd7 100644 --- a/test/core/test_exceptions_refcount_2.out +++ b/test/core/test_exceptions_refcount_2.out
@@ -1 +1,2 @@ ~MyException() +~MyException()