Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
96d7b56
[embind] Manage exceptions' states correctly
aheejin Feb 19, 2026
c83c7a0
Automatic rebaseline of codesize expectations. NFC
aheejin Mar 22, 2026
2b32153
Make several Emscripten EH JS function available at DISABLE_EXCEPTION…
aheejin Mar 22, 2026
0172b72
_emval_is_cpp_exception -> $emval_is_cpp_exception
aheejin Mar 23, 2026
ccd8094
Revert unnecessary changes
aheejin Mar 23, 2026
d122a1d
Merge branch 'main' into fix_embind_eh
aheejin Mar 24, 2026
1d0c028
Fix is_cpp_exception after #26523
aheejin Mar 24, 2026
d8b4741
Merge branch 'main' into fix_embind_eh
aheejin Mar 26, 2026
6400263
DISABLE_EXCEPTION_CATCHING/THROWING fixes
aheejin Mar 26, 2026
7cb05fb
Revive in/decrementUncaughtExceptionCount for Emscripten EH
aheejin Mar 27, 2026
8d75eb0
Merge branch 'main' into fix_embind_eh
aheejin Mar 27, 2026
10866ac
Expose in/decrementUncaughtExceptionCount when !DISABLE_EXCEPTION_THR…
aheejin Mar 27, 2026
cb69eab
Automatic rebaseline of codesize expectations. NFC
aheejin Mar 27, 2026
8ddb43b
__cxa_in/decrement_uncaught_exception -> __in/decrement_uncaught_exce…
aheejin Mar 27, 2026
c808c40
emval_destructors -> emval_exception_decrefs
aheejin Mar 27, 2026
0139122
isCppExceptionObject + Don't do Emval.toValue
aheejin Mar 27, 2026
1712f3b
Remove stray character
aheejin Mar 27, 2026
074cb6e
Fix dep
aheejin Mar 27, 2026
fa085a0
Simplify _emval_throw and isCppExceptionObject
aheejin Mar 27, 2026
b3a57f5
Fix deps
aheejin Mar 27, 2026
c4724df
Simplify more
aheejin Mar 27, 2026
f8c4137
Change in/decrementUncaughtExceptionCount to aliases
aheejin Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 64 additions & 8 deletions src/lib/libemval.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -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);
}
Copy link
Copy Markdown
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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It's really confusing to which one to wrap in #if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS and which one to wrap in #if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS in this file. Apparently we only enable refcounting when EXCEPTION_STACK_TRACES is true (which implies !DISABLE_EXCEPTION_CATCHING so we should be using it here too?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Wrapped in #if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS for now, because in Emscripten EH refcounting requires !DISABLE_EXCEPTION_CATCHING

#endif
emval_freelist.push(handle);
}
},
Expand Down Expand Up @@ -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);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think isCppExceptionObject itself and this whole block can just be wrapped in #if !DISABLE_EXCEPTION_CATCHING && !WASM_EXCEPTIONS

Then you can remove the return false from isCppExceptionObject since that can never happen.

Sorry to keep going back and forth on this but it seems like could simplify that code a bunch maybe?

Otherwise LGTM!

Copy link
Copy Markdown
Member Author

@aheejin aheejin Mar 27, 2026

Choose a reason for hiding this comment

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

Nice idea! Done: fa085a0 + c4724df

#endif
throw object;
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
},
};
Expand Down
22 changes: 21 additions & 1 deletion src/lib/libexceptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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),

Expand All @@ -394,6 +413,7 @@ var LibraryExceptions = {
$getExceptionMessage__deps: ['$getExceptionMessageCommon'],
$getExceptionMessage: (exn) => getExceptionMessageCommon(exn.excPtr),

#endif
#endif
};

Expand Down
11 changes: 11 additions & 0 deletions system/lib/libcxxabi/src/cxa_exception_js_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 47 additions & 0 deletions test/embind/test_embind_throw_val_uncaught_and_refcount.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include <emscripten/val.h>
#include <exception>
#include <iostream>

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;
}
10 changes: 10 additions & 0 deletions test/embind/test_embind_throw_val_uncaught_and_refcount.out
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Loading