diff --git a/src/lib/libemval.js b/src/lib/libemval.js index b77744023d311..ada9d19e87529 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -11,6 +11,9 @@ var LibraryEmVal = { // Stack of handles available for reuse. $emval_freelist: [], +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + $emval_exception_decrefs: [], +#endif // Array of alternating pairs (value, refcount). // reserve 0 and some special values. These never get de-allocated. $emval_handles: [ @@ -80,13 +83,27 @@ var LibraryEmVal = { } }, - _emval_decref__deps: ['$emval_freelist', '$emval_handles'], + _emval_decref__deps: ['$emval_freelist', '$emval_handles', +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + '$emval_exception_decrefs', +#endif + ], _emval_decref: (handle) => { if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}} && 0 === --emval_handles[handle + 1]) { #if ASSERTIONS assert(emval_handles[handle] !== undefined, `Decref for unallocated handle.`); #endif + var value = emval_handles[handle]; emval_handles[handle] = undefined; +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + // In case the value is a C++ exception, decrement the refcount, so the + // memory can be freed correctly + var destructor = emval_exception_decrefs[handle]; + if (destructor) { + emval_exception_decrefs[handle] = undefined; + destructor(value); + } +#endif emval_freelist.push(handle); } }, @@ -392,18 +409,40 @@ ${functionBody} return delete object[property]; }, +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + $isCppExceptionObject__deps: ['$Emval'], + $isCppExceptionObject: (object) => { +#if !DISABLE_EXCEPTION_CATCHING + return object instanceof CppException; +#else // WASM_EXCEPTIONS + return object instanceof WebAssembly.Exception; +#endif + }, +#endif + _emval_throw__deps: ['$Emval', -#if !WASM_EXCEPTIONS && !DISABLE_EXCEPTION_CATCHING +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS +#if !DISABLE_EXCEPTION_CATCHING '$exceptionLast', + '$ExceptionInfo', +#endif + '$incrementExceptionRefcount', + '$incrementUncaughtExceptionCount', + '$isCppExceptionObject', #endif ], _emval_throw: (object) => { object = Emval.toValue(object); -#if !WASM_EXCEPTIONS && !DISABLE_EXCEPTION_CATCHING - // If we are throwing Emcripten C++ exception, set exceptionLast, as we do - // in __cxa_throw. C++ exception will be an instance of CppEmscripten. - if (object instanceof CppException) { +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + if (isCppExceptionObject(object)) { +#if !DISABLE_EXCEPTION_CATCHING + var info = new ExceptionInfo(object.excPtr); + info.set_caught(false); + info.set_rethrown(false); exceptionLast = object; +#endif + incrementUncaughtExceptionCount(); + incrementExceptionRefcount(object); } #endif throw object; @@ -446,7 +485,15 @@ ${functionBody} })); }, - _emval_from_current_cxa_exception__deps: ['$Emval', '__cxa_rethrow'], + _emval_from_current_cxa_exception__deps: ['$Emval', '__cxa_rethrow', +#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS + '$decrementUncaughtExceptionCount', +#endif +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + '$decrementExceptionRefcount', + '$emval_exception_decrefs', +#endif + ], _emval_from_current_cxa_exception: () => { try { // Use __cxa_rethrow which already has mechanism for generating @@ -455,7 +502,16 @@ ${functionBody} // with metadata optimised out otherwise. ___cxa_rethrow(); } catch (e) { - return Emval.toHandle(e); +#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS + // ___cxa_rethrow incremented uncaughtExceptionCount. + // Since we caught it in JS, we need to manually decrement it to balance. + decrementUncaughtExceptionCount(); +#endif + var handle = Emval.toHandle(e); +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + emval_exception_decrefs[handle] = decrementExceptionRefcount; +#endif + return handle; } }, }; diff --git a/src/lib/libexceptions.js b/src/lib/libexceptions.js index 5f59c161229c1..b04ebf7d3ad4e 100644 --- a/src/lib/libexceptions.js +++ b/src/lib/libexceptions.js @@ -366,6 +366,12 @@ var LibraryExceptions = { return ___thrown_object_from_unwind_exception(unwind_header); }, + $incrementUncaughtExceptionCount__deps: ['__increment_uncaught_exception'], + $incrementUncaughtExceptionCount: '__increment_uncaught_exception', + + $decrementUncaughtExceptionCount__deps: ['__decrement_uncaught_exception'], + $decrementUncaughtExceptionCount: '__decrement_uncaught_exception', + $incrementExceptionRefcount__deps: ['__cxa_increment_exception_refcount', '$getCppExceptionThrownObjectFromWebAssemblyException'], $incrementExceptionRefcount: (ex) => { var ptr = getCppExceptionThrownObjectFromWebAssemblyException(ex); @@ -384,7 +390,20 @@ var LibraryExceptions = { return getExceptionMessageCommon(ptr); }, -#elif !DISABLE_EXCEPTION_CATCHING +#else +#if !DISABLE_EXCEPTION_THROWING + $incrementUncaughtExceptionCount__deps: ['$uncaughtExceptionCount'], + $incrementUncaughtExceptionCount: () => { + uncaughtExceptionCount++; + }, + + $decrementUncaughtExceptionCount__deps: ['$uncaughtExceptionCount'], + $decrementUncaughtExceptionCount: () => { + uncaughtExceptionCount--; + }, +#endif + +#if !DISABLE_EXCEPTION_CATCHING $incrementExceptionRefcount__deps: ['__cxa_increment_exception_refcount'], $incrementExceptionRefcount: (exn) => ___cxa_increment_exception_refcount(exn.excPtr), @@ -394,6 +413,7 @@ var LibraryExceptions = { $getExceptionMessage__deps: ['$getExceptionMessageCommon'], $getExceptionMessage: (exn) => getExceptionMessageCommon(exn.excPtr), +#endif #endif }; diff --git a/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp b/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp index 05d908d45577d..dcc63364acee9 100644 --- a/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp +++ b/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp @@ -123,6 +123,17 @@ char* __get_exception_terminate_message(void* thrown_object) { free(type); return result; } + +#ifdef __WASM_EXCEPTIONS__ +void __increment_uncaught_exception() throw() { + __cxa_get_globals()->uncaughtExceptions += 1; +} + +void __decrement_uncaught_exception() throw() { + __cxa_get_globals()->uncaughtExceptions -= 1; +} +#endif + } // extern "C" } // namespace __cxxabiv1 diff --git a/test/embind/test_embind_throw_val_uncaught_and_refcount.cpp b/test/embind/test_embind_throw_val_uncaught_and_refcount.cpp new file mode 100644 index 0000000000000..c4e3baa0c88ec --- /dev/null +++ b/test/embind/test_embind_throw_val_uncaught_and_refcount.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +using namespace emscripten; + +int main() { + val error = val::null(); + try { + throw std::runtime_error("test exception"); + } catch (const std::exception& e) { + // Capture the exception + error = val::take_ownership( + emscripten::internal::_emval_from_current_cxa_exception()); + } + + std::cout << "Captured exception." << std::endl; + + int uncaught_before = std::uncaught_exceptions(); + std::cout << "Uncaught before throw 1: " << uncaught_before << std::endl; + + // First throw + try { + std::cout << "Throwing 1..." << std::endl; + error.throw_(); + } catch (const std::exception& e) { + std::cout << "Caught 1: " << e.what() << std::endl; + int uncaught_during = std::uncaught_exceptions(); + std::cout << "Uncaught during catch 1: " << uncaught_during << std::endl; + } + + int uncaught_between = std::uncaught_exceptions(); + std::cout << "Uncaught between throws: " << uncaught_between << std::endl; + + // Second throw - if refcount was messed up, this might fail/crash + try { + std::cout << "Throwing 2..." << std::endl; + error.throw_(); + } catch (const std::exception& e) { + std::cout << "Caught 2: " << e.what() << std::endl; + int uncaught_during = std::uncaught_exceptions(); + std::cout << "Uncaught during catch 2: " << uncaught_during << std::endl; + } + + std::cout << "Done." << std::endl; + return 0; +} diff --git a/test/embind/test_embind_throw_val_uncaught_and_refcount.out b/test/embind/test_embind_throw_val_uncaught_and_refcount.out new file mode 100644 index 0000000000000..eb2c80ecbc4e3 --- /dev/null +++ b/test/embind/test_embind_throw_val_uncaught_and_refcount.out @@ -0,0 +1,10 @@ +Captured exception. +Uncaught before throw 1: 0 +Throwing 1... +Caught 1: test exception +Uncaught during catch 1: 0 +Uncaught between throws: 0 +Throwing 2... +Caught 2: test exception +Uncaught during catch 2: 0 +Done. diff --git a/test/test_core.py b/test/test_core.py index 5bb1d1bd349a3..28af4edd5f97c 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -7782,6 +7782,10 @@ def test_embind_wasm_workers(self): def test_embind_throw_cpp_exception(self): self.do_run_in_out_file_test('embind/test_embind_throw_cpp_exception.cpp', cflags=['-lembind', '-std=c++20']) + @with_all_eh_sjlj + def test_embind_throw_val_uncaught_and_refcount(self): + self.do_run_in_out_file_test('embind/test_embind_throw_val_uncaught_and_refcount.cpp', cflags=['-lembind', '-std=c++20']) + @parameterized({ '': ('DEFAULT', False), 'all': ('ALL', False),