Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
86 changes: 60 additions & 26 deletions src/lib/libemval.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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).

// 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 +81,19 @@ var LibraryEmVal = {
}
},

_emval_decref__deps: ['$emval_freelist', '$emval_handles'],
_emval_decref__deps: ['$emval_freelist', '$emval_handles', '$emval_destructors'],
_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;
var destructor = emval_destructors[handle];
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?

emval_freelist.push(handle);
}
},
Expand Down Expand Up @@ -392,37 +399,50 @@ ${functionBody}
return delete object[property];
},

_emval_throw__deps: ['$Emval',
#if !WASM_EXCEPTIONS
$emval_is_cpp_exception__deps: ['$Emval'],
$emval_is_cpp_exception: (object) => {
object = Emval.toValue(object);
#if WASM_EXCEPTIONS
return object instanceof WebAssembly.Exception;
#else
#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?

#endif
#endif
},

_emval_throw__deps: ['$Emval', '$emval_is_cpp_exception',
#if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS
'$exceptionLast',
'$ExceptionInfo',
'$exnToPtr',
#endif
#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS
'$incrementUncaughtExceptionCount',
#endif
#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS
'$incrementExceptionRefcount',
#endif
],
_emval_throw: (object) => {
var orig_object = object;
object = Emval.toValue(object);
#if !WASM_EXCEPTIONS
// If we are throwing Emcripten C++ exception, set exceptionLast, as we do
// in __cxa_throw. When EXCEPTION_STACK_TRACES is set, a C++ exception will
// be an instance of EmscriptenEH, and when EXCEPTION_STACK_TRACES is not
// set, it will be a pointer (number).
//
// This is different from __cxa_throw() in libexception.js because
// __cxa_throw() is called from the user C++ code when the 'throw' keyword
// is used, and the value thrown is a C++ pointer. When
// EXCEPTION_STACK_TRACES is true, we wrap it with CppException. But this
// _emval_throw is called when we throw whatever is contained in 'object',
// which can be anything including a CppException object, or a number, or
// other JS object. So we don't use storeException() wrapper here and we
// throw it as is.
#if EXCEPTION_STACK_TRACES
if (object instanceof CppException) {
exceptionLast = object;
}
#else
if (object === object+0) { // Check if it is a number
if (emval_is_cpp_exception(orig_object)) {
#if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS
exceptionLast = object;
}
var info = new ExceptionInfo(exnToPtr(object));
info.set_caught(false);
info.set_rethrown(false);
#endif
#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS
incrementUncaughtExceptionCount();
#endif
#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS
incrementExceptionRefcount(object);
#endif
}
throw object;
},

Expand Down Expand Up @@ -463,7 +483,12 @@ ${functionBody}
}));
},

_emval_from_current_cxa_exception__deps: ['$Emval', '__cxa_rethrow'],
_emval_from_current_cxa_exception__deps: ['$Emval', '__cxa_rethrow', '$emval_destructors',
#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS
'$decrementUncaughtExceptionCount',
'$decrementExceptionRefcount',
#endif
],
_emval_from_current_cxa_exception: () => {
try {
// Use __cxa_rethrow which already has mechanism for generating
Expand All @@ -472,7 +497,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_THROWING || WASM_EXCEPTIONS
emval_destructors[handle] = decrementExceptionRefcount;
#endif
return handle;
}
},
};
Expand Down
24 changes: 22 additions & 2 deletions src/lib/libexceptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ var LibraryExceptions = {
},

#endif
#if WASM_EXCEPTIONS || !DISABLE_EXCEPTION_CATCHING
#if WASM_EXCEPTIONS || !DISABLE_EXCEPTION_THROWING
$getExceptionMessageCommon__deps: ['__get_exception_message', 'free', '$stackSave', '$stackRestore', '$stackAlloc'],
$getExceptionMessageCommon: (ptr) => {
var sp = stackSave();
Expand Down Expand Up @@ -341,6 +341,16 @@ var LibraryExceptions = {
return ___thrown_object_from_unwind_exception(unwind_header);
},

$incrementUncaughtExceptionCount__deps: ['__cxa_increment_uncaught_exception'],
$incrementUncaughtExceptionCount: () => {
___cxa_increment_uncaught_exception();
},

$decrementUncaughtExceptionCount__deps: ['__cxa_decrement_uncaught_exception'],
$decrementUncaughtExceptionCount: () => {
___cxa_decrement_uncaught_exception();
},

$incrementExceptionRefcount__deps: ['__cxa_increment_exception_refcount', '$getCppExceptionThrownObjectFromWebAssemblyException'],
$incrementExceptionRefcount: (ex) => {
var ptr = getCppExceptionThrownObjectFromWebAssemblyException(ex);
Expand All @@ -359,7 +369,7 @@ var LibraryExceptions = {
return getExceptionMessageCommon(ptr);
},

#elif !DISABLE_EXCEPTION_CATCHING
#elif !DISABLE_EXCEPTION_THROWING
// When EXCEPTION_STACK_TRACES is set, the exception is an instance of
// CppException, whereas EXCEPTION_STACK_TRACES is unset it is a raw pointer.
$exnToPtr: (exn) => {
Expand All @@ -371,6 +381,16 @@ var LibraryExceptions = {
return exn;
},

$incrementUncaughtExceptionCount__deps: ['$uncaughtExceptionCount'],
$incrementUncaughtExceptionCount: () => {
uncaughtExceptionCount++;
},

$decrementUncaughtExceptionCount__deps: ['$uncaughtExceptionCount'],
$decrementUncaughtExceptionCount: () => {
uncaughtExceptionCount--;
},

$incrementExceptionRefcount__deps: ['$exnToPtr', '__cxa_increment_exception_refcount'],
$incrementExceptionRefcount: (exn) => ___cxa_increment_exception_refcount(exnToPtr(exn)),

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 __cxa_increment_uncaught_exception() throw() {
__cxa_get_globals()->uncaughtExceptions += 1;
}

void __cxa_decrement_uncaught_exception() throw() {
__cxa_get_globals()->uncaughtExceptions -= 1;
}
#endif

} // extern "C"

} // namespace __cxxabiv1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"a.html": 548,
"a.html.gz": 371,
"a.js": 7262,
"a.js.gz": 3324,
"a.js": 7314,
"a.js.gz": 3347,
"a.wasm": 7099,
"a.wasm.gz": 3257,
"total": 14909,
"total_gz": 6952
"total": 14961,
"total_gz": 6975
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"a.html": 548,
"a.html.gz": 371,
"a.js": 5353,
"a.js.gz": 2524,
"a.js": 5406,
"a.js.gz": 2544,
"a.wasm": 5741,
"a.wasm.gz": 2690,
"total": 11642,
"total_gz": 5585
"total": 11695,
"total_gz": 5605
}
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 @@ -7778,6 +7778,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