Skip to content

[embind] Manage exceptions' states correctly#26519

Open
aheejin wants to merge 5 commits intoemscripten-core:mainfrom
aheejin:fix_embind_eh
Open

[embind] Manage exceptions' states correctly#26519
aheejin wants to merge 5 commits intoemscripten-core:mainfrom
aheejin:fix_embind_eh

Conversation

@aheejin
Copy link
Member

@aheejin aheejin commented Mar 22, 2026

libemval.js has two exception-related functions: _emval_throw and _emval_from_current_cxa_exception, but they haven't been correclty updating various exception-related states. #26276 tried to fix this but it only updated exceptionLast.

This updates other internal states correctly. Some of them only apply to Emscripten EH, while others apply to both.

  1. To make the uncaught exception count updatable for Wasm EH, this adds ___cxa_in/decrement_uncaught_exception to cxa_exception_js_utils.cpp, and also adds a common interface in/decrementUncaughtExceptionCount in libexceptions.js so that you can call them regardless of which EH is used. For Wasm EH, the management of the uncaught exception count is done within libc++abi functions (__cxa_throw, __cxa_rethrow, ...), but here in embind we bypass those functions, so we have to manage them directly.

  2. To make refcount work correctly, this adds calls to in/decrementExceptionRefcount to exception-related functions. Also, this fixes memory leaks by adding the destructor mechanism that decrements refcount when emvals are destroyed. _emval_from_current_cxa_exception is similar to std::current_exception, which returns std::exception_ptr, whose destructor decrements the refcount so that the exception can be destroyed:

    exception_ptr::~exception_ptr() noexcept { __cxa_decrement_exception_refcount(__ptr_); }
    embind didn't have such a mechanism, so exceptions captured by _emval_from_current_cxa_exception leaked memory.

This also fixes #26290.

`libemval.js` has two exception-related functions: `_emval_throw` and
`_emval_from_current_cxa_exception`, but they haven't been correclty
updating various exception-related states. emscripten-core#26276 tried to fix this but
it only updated `exceptionLast`.

This updates other internal states correctly. Some of them only apply to
Emscripten EH, while others apply to both.

1. To make the uncaught exception count updatable for Wasm EH, this adds
   `___cxa_in/decrement_uncaught_exception` to
   `cxa_exception_js_utils.cpp`, and also adds a common interface
   `in/decrementUncaughtExceptionCount` in `libexceptions.js` so that
   you can call them regardless of which EH is used. For Wasm EH, the
   management of the uncaught exception count is done within libc++abi
   functions (`__cxa_throw`, `__cxa_rethrow`, ...), but here in embind
   we bypass those functions, so we have to manage them directly.

2. To make refcount work correctly, this adds calls to
   `in/decrementExceptionRefcount` to exception-related functions. Also,
   this fixes memory leaks by adding the destructor mechanism that
   decrements refcount when emvals are destroyed.
   `_emval_from_current_cxa_exception` is similar to
   `std::current_exception`, which returns `std::exception_ptr`, whose
   destructor decrements the refcount so that the exception can be
   destroyed:
   https://github.com/emscripten-core/emscripten/blob/62e22652509fbe7a00609ce48a653d0d66f27ba5/system/lib/libcxx/src/support/runtime/exception_pointer_cxxabi.ipp#L16
   embind didn't have such a mechanism, so exceptions captured by
   `_emval_from_current_cxa_exception` leaked memory.

This also fixes emscripten-core#26290.
@aheejin aheejin requested review from brendandahl and sbc100 March 22, 2026 19:19
aheejin added 4 commits March 22, 2026 19:24
This is an automatic change generated by tools/maint/rebaseline_tests.py.

The following (2) test expectation files were updated by
running the tests with `--rebaseline`:

```
codesize/test_minimal_runtime_code_size_hello_embind.json: 14909 => 14961 [+52 bytes / +0.35%]
codesize/test_minimal_runtime_code_size_hello_embind_val.json: 11642 => 11695 [+53 bytes / +0.46%]

Average change: +0.40% (+0.35% - +0.46%)
```
#if EXCEPTION_STACK_TRACES
return object instanceof CppException;
#else
return object === object+0; // Check if it is a number
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Give that this seems to also return try for numbers maybe it should be emval_could_be_cpp_exception?

if (destructor) {
emval_destructors[handle] = undefined;
destructor(value);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap this new code in #if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS?

var LibraryEmVal = {
// Stack of handles available for reuse.
$emval_freelist: [],
$emval_destructors: [],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Online define if #if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS.

Also, it might be misleading to simply call this emval_destructors. How about making it less generic and more sepcfic. e.g. emval_is_exception just stores booleans and then decrementExceptionRefcount is called explicitly during _emval_decref.

I fear that otherwise this looks too much like how embind handles cleanup / destructors for normal C++ objects (which it is not).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

val::throw_ linker error with -fno-exceptions

2 participants