Skip to content

Commit

Permalink
[EH] Make Wasm EH rethrow print stack traces (#20372)
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 #20301.
  • Loading branch information
aheejin committed Oct 3, 2023
1 parent 3106f67 commit 6b36901
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 16 deletions.
17 changes: 11 additions & 6 deletions system/lib/libcxxabi/src/cxa_exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,14 +287,10 @@ __cxa_throw(void *thrown_object, std::type_info *tinfo, void (_LIBCXXABI_DTOR_FU

#ifdef __USING_SJLJ_EXCEPTIONS__
_Unwind_SjLj_RaiseException(&exception_header->unwindHeader);
#elif __USING_WASM_EXCEPTIONS__
#ifdef NDEBUG
_Unwind_RaiseException(&exception_header->unwindHeader);
#else
#elif defined(__USING_WASM_EXCEPTIONS__) && !defined(NDEBUG)
// 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 @@ -644,6 +640,10 @@ void __cxa_rethrow() {
}
#ifdef __USING_SJLJ_EXCEPTIONS__
_Unwind_SjLj_RaiseException(&exception_header->unwindHeader);
#elif defined(__USING_WASM_EXCEPTIONS__) && !defined(NDEBUG)
// 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);
#else
_Unwind_RaiseException(&exception_header->unwindHeader);
#endif
Expand Down Expand Up @@ -769,8 +769,13 @@ __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 defined(__USING_WASM_EXCEPTIONS__) && !defined(NDEBUG)
// 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);
#else
_Unwind_RaiseException(&dep_exception_header->unwindHeader);
_Unwind_RaiseException(&exception_header->unwindHeader);
#endif
// Some sort of unwinding error. Note that terminate is a handler.
__cxa_begin_catch(&dep_exception_header->unwindHeader);
Expand Down
90 changes: 80 additions & 10 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -8529,7 +8529,7 @@ def test_exceptions_stack_trace_and_message(self, wasm_eh):
return 0;
}
'''
emcc_args = ['-g']
self.emcc_args = ['-g']

# Stack trace and message example for this example code:
# exiting due to exception: [object WebAssembly.Exception],Error: std::runtime_error,my message
Expand Down Expand Up @@ -8557,9 +8557,9 @@ def test_exceptions_stack_trace_and_message(self, wasm_eh):
# self.require_wasm_eh() if this issue is fixed later.
self.require_v8()

emcc_args += ['-fwasm-exceptions']
self.emcc_args += ['-fwasm-exceptions']
else:
emcc_args += ['-fexceptions']
self.emcc_args += ['-fexceptions']

# Stack traces are enabled when either of ASSERTIONS or
# EXCEPTION_STACK_TRACES is enabled. You can't disable
Expand All @@ -8568,16 +8568,14 @@ def test_exceptions_stack_trace_and_message(self, wasm_eh):
# Prints stack traces
self.set_setting('ASSERTIONS', 1)
self.set_setting('EXCEPTION_STACK_TRACES', 1)
self.do_run(src, emcc_args=emcc_args, assert_all=True,
assert_returncode=NON_ZERO, expected_output=stack_trace_checks,
regex=True)
self.do_run(src, assert_all=True, assert_returncode=NON_ZERO,
expected_output=stack_trace_checks, regex=True)

# Prints stack traces
self.set_setting('ASSERTIONS', 0)
self.set_setting('EXCEPTION_STACK_TRACES', 1)
self.do_run(src, emcc_args=emcc_args, assert_all=True,
assert_returncode=NON_ZERO, expected_output=stack_trace_checks,
regex=True)
self.do_run(src, assert_all=True, assert_returncode=NON_ZERO,
expected_output=stack_trace_checks, regex=True)

# Not allowed
self.set_setting('ASSERTIONS', 1)
Expand All @@ -8589,10 +8587,82 @@ def test_exceptions_stack_trace_and_message(self, wasm_eh):
# Doesn't print stack traces
self.set_setting('ASSERTIONS', 0)
self.set_setting('EXCEPTION_STACK_TRACES', 0)
err = self.do_run(src, emcc_args=emcc_args, assert_returncode=NON_ZERO)
err = self.do_run(src, assert_returncode=NON_ZERO)
for check in stack_trace_checks:
self.assertFalse(re.search(check, err), 'Expected regex "%s" to not match on:\n%s' % (check, err))

@parameterized({
'': (False,),
'wasm': (True,),
})
def test_exceptions_rethrow_stack_trace_and_message(self, wasm_eh):
self.emcc_args = ['-g']
if wasm_eh:
# FIXME Node v18.13 (LTS as of Jan 2023) has not yet implemented the new
# optional 'traceStack' option in WebAssembly.Exception constructor
# (https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Exception/Exception)
# and embeds stack traces unconditionally. Change this back to
# self.require_wasm_eh() if this issue is fixed later.
self.require_v8()
self.emcc_args += ['-fwasm-exceptions']
else:
self.emcc_args += ['-fexceptions']

# 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)
err = self.do_run(rethrow_src1, 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, 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 6b36901

Please sign in to comment.