-
Notifications
You must be signed in to change notification settings - Fork 3.5k
[embind] Manage exceptions' states correctly #26519
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
96d7b56
c83c7a0
2b32153
0172b72
ccd8094
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| var LibraryEmVal = { | ||
| // Stack of handles available for reuse. | ||
| $emval_freelist: [], | ||
| $emval_destructors: [], | ||
| // Array of alternating pairs (value, refcount). | ||
| // reserve 0 and some special values. These never get de-allocated. | ||
| $emval_handles: [ | ||
|
|
@@ -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); | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrap this new code in |
||
| emval_freelist.push(handle); | ||
| } | ||
| }, | ||
|
|
@@ -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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| #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; | ||
| }, | ||
|
|
||
|
|
@@ -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 | ||
|
|
@@ -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; | ||
| } | ||
| }, | ||
| }; | ||
|
|
||
| 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 | ||
| } |
| 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; | ||
| } |
| 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. |
There was a problem hiding this comment.
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_exceptionjust stores booleans and thendecrementExceptionRefcountis 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).