Skip to content

Commit

Permalink
[EH] Make Wasm EH rethrow print stack traces
Browse files Browse the repository at this point in the history
In Wasm EH, even if we set `-sASSERTION` or `-sEXCEPTION_STACK_TRACES`,
if we rethrow an exception in the code, we lose the effect of that
option because previously we called `__throw_exception_with_stack_trace`
only in `__cxa_throw`:
https://github.com/emscripten-core/emscripten/blob/9ce7020632aa6f7578c6e40e197b800a4dd7073f/system/lib/libcxxabi/src/cxa_exception.cpp#L294-L296

This adds the same mechanism to `__cxa_rethrow` (which is used for
C++ `throw;`) and `__cxa_rethrow_primary_exception` (which is used for
`std::rethrow_exception`).

This does not solve the problem of losing stack traces _before_ the
rethrowing. libc++abi's `__cxa_rethrow` and
`__cxa_rethrow_primary_exception` are implemented as throwing a pointer
in the same way we first throw it and they are not aware of any
metadata. This happens even in the native platform with GDB; GDB's
backtrace only shows stack traces after rethrowing. We may try to fix
this later by passing `exnref`
(WebAssembly/exception-handling#281) to the
library, but this is likely to take some time.

Partially fixes emscripten-core#20301.
  • Loading branch information
aheejin committed Oct 2, 2023
1 parent 00e3406 commit a208c42
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 0 deletions.
16 changes: 16 additions & 0 deletions system/lib/libcxxabi/src/cxa_exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,14 @@ void __cxa_rethrow() {
}
#ifdef __USING_SJLJ_EXCEPTIONS__
_Unwind_SjLj_RaiseException(&exception_header->unwindHeader);
#elif __USING_WASM_EXCEPTIONS__
#ifdef NDEBUG
_Unwind_RaiseException(&exception_header->unwindHeader);
#else
// In debug mode, call a JS library function to use WebAssembly.Exception JS
// API, which enables us to include stack traces
__throw_exception_with_stack_trace(&exception_header->unwindHeader);
#endif
#else
_Unwind_RaiseException(&exception_header->unwindHeader);
#endif
Expand Down Expand Up @@ -769,6 +777,14 @@ __cxa_rethrow_primary_exception(void* thrown_object)
dep_exception_header->unwindHeader.exception_cleanup = dependent_exception_cleanup;
#ifdef __USING_SJLJ_EXCEPTIONS__
_Unwind_SjLj_RaiseException(&dep_exception_header->unwindHeader);
#elif __USING_WASM_EXCEPTIONS__
#ifdef NDEBUG
_Unwind_RaiseException(&exception_header->unwindHeader);
#else
// In debug mode, call a JS library function to use WebAssembly.Exception JS
// API, which enables us to include stack traces
__throw_exception_with_stack_trace(&exception_header->unwindHeader);
#endif
#else
_Unwind_RaiseException(&dep_exception_header->unwindHeader);
#endif
Expand Down
58 changes: 58 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -8593,6 +8593,64 @@ def test_exceptions_stack_trace_and_message(self, wasm_eh):
for check in stack_trace_checks:
self.assertFalse(re.search(check, err), 'Expected regex "%s" to not match on:\n%s' % (check, err))

# Rethrowing exception currently loses the stack trace before the
# rethrowing due to how rethrowing is implemented. So in the examples below
# we don't print 'bar' at the moment.
# TODO Make rethrow preserve stack traces before rethrowing?
rethrow_src1 = r'''
#include <stdexcept>

void bar() {
throw std::runtime_error("my message");
}
void foo() {
try {
bar();
} catch (...) {
throw; // rethrowing by throw;
}
}
int main() {
foo();
return 0;
}
'''
rethrow_src2 = r'''
#include <stdexcept>

void bar() {
throw std::runtime_error("my message");
}
void foo() {
try {
bar();
} catch (...) {
auto e = std::current_exception();
std::rethrow_exception(e); // rethrowing by std::rethrow_exception
}
}
int main() {
foo();
return 0;
}
'''
rethrow_stack_trace_checks = [
'std::runtime_error[:,][ ]?my message', # 'std::runtime_error: my message' for Emscripten EH
'at ((src.wasm.)?_?__cxa_rethrow|___resumeException)', # '___resumeException' (JS symbol) for Emscripten EH
'at (src.wasm.)?foo',
'at (src.wasm.)?main']

self.set_setting('ASSERTIONS', 1)
self.clear_setting('EXCEPTION_STACK_TRACES')
err = self.do_run(rethrow_src1, emcc_args=emcc_args, assert_all=True,
assert_returncode=NON_ZERO,
expected_output=rethrow_stack_trace_checks, regex=True)
self.assertNotContained('bar', err)
err = self.do_run(rethrow_src2, emcc_args=emcc_args, assert_all=True,
assert_returncode=NON_ZERO,
expected_output=rethrow_stack_trace_checks, regex=True)
self.assertNotContained('bar', err)

@requires_node
def test_jsrun(self):
print(config.NODE_JS)
Expand Down

0 comments on commit a208c42

Please sign in to comment.