Skip to content

Commit 6b36901

Browse files
authored
[EH] Make Wasm EH rethrow print stack traces (#20372)
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.
1 parent 3106f67 commit 6b36901

File tree

2 files changed

+91
-16
lines changed

2 files changed

+91
-16
lines changed

system/lib/libcxxabi/src/cxa_exception.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -287,14 +287,10 @@ __cxa_throw(void *thrown_object, std::type_info *tinfo, void (_LIBCXXABI_DTOR_FU
287287

288288
#ifdef __USING_SJLJ_EXCEPTIONS__
289289
_Unwind_SjLj_RaiseException(&exception_header->unwindHeader);
290-
#elif __USING_WASM_EXCEPTIONS__
291-
#ifdef NDEBUG
292-
_Unwind_RaiseException(&exception_header->unwindHeader);
293-
#else
290+
#elif defined(__USING_WASM_EXCEPTIONS__) && !defined(NDEBUG)
294291
// In debug mode, call a JS library function to use WebAssembly.Exception JS
295292
// API, which enables us to include stack traces
296293
__throw_exception_with_stack_trace(&exception_header->unwindHeader);
297-
#endif
298294
#else
299295
_Unwind_RaiseException(&exception_header->unwindHeader);
300296
#endif
@@ -644,6 +640,10 @@ void __cxa_rethrow() {
644640
}
645641
#ifdef __USING_SJLJ_EXCEPTIONS__
646642
_Unwind_SjLj_RaiseException(&exception_header->unwindHeader);
643+
#elif defined(__USING_WASM_EXCEPTIONS__) && !defined(NDEBUG)
644+
// In debug mode, call a JS library function to use WebAssembly.Exception JS
645+
// API, which enables us to include stack traces
646+
__throw_exception_with_stack_trace(&exception_header->unwindHeader);
647647
#else
648648
_Unwind_RaiseException(&exception_header->unwindHeader);
649649
#endif
@@ -769,8 +769,13 @@ __cxa_rethrow_primary_exception(void* thrown_object)
769769
dep_exception_header->unwindHeader.exception_cleanup = dependent_exception_cleanup;
770770
#ifdef __USING_SJLJ_EXCEPTIONS__
771771
_Unwind_SjLj_RaiseException(&dep_exception_header->unwindHeader);
772+
#elif defined(__USING_WASM_EXCEPTIONS__) && !defined(NDEBUG)
773+
// In debug mode, call a JS library function to use
774+
// WebAssembly.Exception JS API, which enables us to include stack
775+
// traces
776+
__throw_exception_with_stack_trace(&exception_header->unwindHeader);
772777
#else
773-
_Unwind_RaiseException(&dep_exception_header->unwindHeader);
778+
_Unwind_RaiseException(&exception_header->unwindHeader);
774779
#endif
775780
// Some sort of unwinding error. Note that terminate is a handler.
776781
__cxa_begin_catch(&dep_exception_header->unwindHeader);

test/test_other.py

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8529,7 +8529,7 @@ def test_exceptions_stack_trace_and_message(self, wasm_eh):
85298529
return 0;
85308530
}
85318531
'''
8532-
emcc_args = ['-g']
8532+
self.emcc_args = ['-g']
85338533

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

8560-
emcc_args += ['-fwasm-exceptions']
8560+
self.emcc_args += ['-fwasm-exceptions']
85618561
else:
8562-
emcc_args += ['-fexceptions']
8562+
self.emcc_args += ['-fexceptions']
85638563

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

85758574
# Prints stack traces
85768575
self.set_setting('ASSERTIONS', 0)
85778576
self.set_setting('EXCEPTION_STACK_TRACES', 1)
8578-
self.do_run(src, emcc_args=emcc_args, assert_all=True,
8579-
assert_returncode=NON_ZERO, expected_output=stack_trace_checks,
8580-
regex=True)
8577+
self.do_run(src, assert_all=True, assert_returncode=NON_ZERO,
8578+
expected_output=stack_trace_checks, regex=True)
85818579

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

8594+
@parameterized({
8595+
'': (False,),
8596+
'wasm': (True,),
8597+
})
8598+
def test_exceptions_rethrow_stack_trace_and_message(self, wasm_eh):
8599+
self.emcc_args = ['-g']
8600+
if wasm_eh:
8601+
# FIXME Node v18.13 (LTS as of Jan 2023) has not yet implemented the new
8602+
# optional 'traceStack' option in WebAssembly.Exception constructor
8603+
# (https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Exception/Exception)
8604+
# and embeds stack traces unconditionally. Change this back to
8605+
# self.require_wasm_eh() if this issue is fixed later.
8606+
self.require_v8()
8607+
self.emcc_args += ['-fwasm-exceptions']
8608+
else:
8609+
self.emcc_args += ['-fexceptions']
8610+
8611+
# Rethrowing exception currently loses the stack trace before the rethrowing
8612+
# due to how rethrowing is implemented. So in the examples below we don't
8613+
# print 'bar' at the moment.
8614+
# TODO Make rethrow preserve stack traces before rethrowing?
8615+
rethrow_src1 = r'''
8616+
#include <stdexcept>
8617+
8618+
void bar() {
8619+
throw std::runtime_error("my message");
8620+
}
8621+
void foo() {
8622+
try {
8623+
bar();
8624+
} catch (...) {
8625+
throw; // rethrowing by throw;
8626+
}
8627+
}
8628+
int main() {
8629+
foo();
8630+
return 0;
8631+
}
8632+
'''
8633+
rethrow_src2 = r'''
8634+
#include <stdexcept>
8635+
8636+
void bar() {
8637+
throw std::runtime_error("my message");
8638+
}
8639+
void foo() {
8640+
try {
8641+
bar();
8642+
} catch (...) {
8643+
auto e = std::current_exception();
8644+
std::rethrow_exception(e); // rethrowing by std::rethrow_exception
8645+
}
8646+
}
8647+
int main() {
8648+
foo();
8649+
return 0;
8650+
}
8651+
'''
8652+
rethrow_stack_trace_checks = [
8653+
'std::runtime_error[:,][ ]?my message', # 'std::runtime_error: my message' for Emscripten EH
8654+
'at ((src.wasm.)?_?__cxa_rethrow|___resumeException)', # '___resumeException' (JS symbol) for Emscripten EH
8655+
'at (src.wasm.)?foo',
8656+
'at (src.wasm.)?main']
8657+
8658+
self.set_setting('ASSERTIONS', 1)
8659+
err = self.do_run(rethrow_src1, assert_all=True, assert_returncode=NON_ZERO,
8660+
expected_output=rethrow_stack_trace_checks, regex=True)
8661+
self.assertNotContained('bar', err)
8662+
err = self.do_run(rethrow_src2, assert_all=True, assert_returncode=NON_ZERO,
8663+
expected_output=rethrow_stack_trace_checks, regex=True)
8664+
self.assertNotContained('bar', err)
8665+
85968666
@requires_node
85978667
def test_jsrun(self):
85988668
print(config.NODE_JS)

0 commit comments

Comments
 (0)