Skip to content

Commit b8ce655

Browse files
authored
Support the EXCEPTION_STACK_TRACES option for Emscripten EH (emscripten-core#18535)
This would avoid having to re-throw C++ exceptions as JS ones or to call `getExceptionMessage()` on the thrown pointer in JS.
1 parent 4000c3b commit b8ce655

13 files changed

Lines changed: 130 additions & 55 deletions

ChangeLog.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,10 @@ See docs/process.md for more on how version tagging works.
2020

2121
3.1.32 (in development)
2222
-----------------------
23-
- In Wasm exception mode (`-fwasm-exceptions`), when
24-
`EXCEPTION_STACK_TRACES` is enabled, uncaught exceptions will display stack
25-
traces. This defaults to true when `ASSERTIONS` is enabled. This option is
26-
mainly for the users who want only exceptions' stack traces without turning
27-
`ASSERTIONS` on. This option currently works only for Wasm exceptions
28-
(-fwasm-exceptions). (#18642)
23+
- Added new linker option `-sEXCEPTION_STACK_TRACES` which will display a stack
24+
trace when an uncaught exception occurs. This defaults to true when
25+
`ASSERTIONS` is enabled. This option is mainly for the users who want only
26+
exceptions' stack traces without turning `ASSERTIONS` on. (#18642 and #18535)
2927
- `SUPPORT_LONGJMP`'s default value now depends on the exception mode. If Wasm
3028
EH (`-fwasm-exception`) is used, it defaults to `wasm`, and if Emscripten EH
3129
(`-sDISABLE_EXCEPTION_CATCHING=0`) is used or no exception support is used, it

emcc.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1932,11 +1932,18 @@ def phase_linker_setup(options, state, newargs):
19321932
if '_main' in settings.EXPORTED_FUNCTIONS:
19331933
settings.EXPORT_IF_DEFINED.append('__main_argc_argv')
19341934

1935-
# -sASSERTIONS implies basic stack overflow checks, and ASSERTIONS=2
1936-
# implies full stack overflow checks.
19371935
if settings.ASSERTIONS:
1938-
# However, we don't set this default in PURE_WASI, or when we are linking without standard
1939-
# libraries because STACK_OVERFLOW_CHECK depends on emscripten_stack_get_end which is defined
1936+
# Exceptions are thrown with a stack trace by default when ASSERTIONS is
1937+
# set and when building with either -fexceptions or -fwasm-exceptions.
1938+
if 'EXCEPTION_STACK_TRACES' in user_settings and not settings.EXCEPTION_STACK_TRACES:
1939+
exit_with_error('EXCEPTION_STACK_TRACES cannot be disabled when ASSERTIONS are enabled')
1940+
if settings.WASM_EXCEPTIONS or not settings.DISABLE_EXCEPTION_CATCHING:
1941+
settings.EXCEPTION_STACK_TRACES = 1
1942+
1943+
# -sASSERTIONS implies basic stack overflow checks, and ASSERTIONS=2
1944+
# implies full stack overflow checks. However, we don't set this default in
1945+
# PURE_WASI, or when we are linking without standard libraries because
1946+
# STACK_OVERFLOW_CHECK depends on emscripten_stack_get_end which is defined
19401947
# in libcompiler-rt.
19411948
if not settings.PURE_WASI and '-nostdlib' not in newargs and '-nodefaultlibs' not in newargs:
19421949
default_setting('STACK_OVERFLOW_CHECK', max(settings.ASSERTIONS, settings.STACK_OVERFLOW_CHECK))
@@ -2229,6 +2236,17 @@ def phase_linker_setup(options, state, newargs):
22292236
if options.use_closure_compiler is None and settings.TRANSPILE_TO_ES5:
22302237
diagnostics.warning('transpile', 'enabling transpilation via closure due to browser version settings. This warning can be suppressed by passing `--closure=1` or `--closure=0` to opt into our explicitly.')
22312238

2239+
# https://caniuse.com/class: EDGE:13 FF:45 CHROME:49 SAFARI:9
2240+
supports_es6_classes = (settings.MIN_EDGE_VERSION >= 13 and
2241+
settings.MIN_FIREFOX_VERSION >= 45 and
2242+
settings.MIN_CHROME_VERSION >= 49 and
2243+
settings.MIN_SAFARI_VERSION >= 90000 and
2244+
settings.MIN_IE_VERSION == 0x7FFFFFFF)
2245+
2246+
if not settings.DISABLE_EXCEPTION_CATCHING and settings.EXCEPTION_STACK_TRACES and not supports_es6_classes:
2247+
diagnostics.warning('transpile', '-sEXCEPTION_STACK_TRACES requires an engine that support ES6 classes.')
2248+
settings.EXCEPTION_STACK_TRACES = 0
2249+
22322250
# Silently drop any individual backwards compatibility emulation flags that are known never to occur on browsers that support WebAssembly.
22332251
if not settings.WASM2JS:
22342252
settings.POLYFILL_OLD_MATH_FUNCTIONS = 0
@@ -2828,28 +2846,18 @@ def get_full_import_name(name):
28282846
if settings.WASM_EXCEPTIONS:
28292847
settings.REQUIRED_EXPORTS += ['__trap']
28302848

2831-
# When ASSERTIONS or EXCEPTION_STACK_TRACES is set, we include stack traces in
2832-
# Wasm exception objects using the JS API, which needs this C++ tag exported.
2833-
if settings.ASSERTIONS:
2834-
if 'EXCEPTION_STACK_TRACES' in user_settings and not settings.EXCEPTION_STACK_TRACES:
2835-
exit_with_error('EXCEPTION_STACK_TRACES cannot be disabled when ASSERTIONS are enabled')
2836-
if not settings.DISABLE_EXCEPTION_CATCHING or settings.WASM_EXCEPTIONS:
2837-
settings.EXCEPTION_STACK_TRACES = 1
2838-
28392849
if settings.EXCEPTION_STACK_TRACES:
28402850
# If the user explicitly gave EXCEPTION_STACK_TRACES=1 without enabling EH,
28412851
# errors out.
28422852
if settings.DISABLE_EXCEPTION_CATCHING and not settings.WASM_EXCEPTIONS:
28432853
exit_with_error('EXCEPTION_STACK_TRACES requires either of -fexceptions or -fwasm-exceptions')
28442854
# EXCEPTION_STACK_TRACES implies EXPORT_EXCEPTION_HANDLING_HELPERS
28452855
settings.EXPORT_EXCEPTION_HANDLING_HELPERS = True
2846-
if settings.WASM_EXCEPTIONS:
2847-
settings.EXPORTED_FUNCTIONS += ['___cpp_exception']
28482856

28492857
# Make `getExceptionMessage` and other necessary functions available for use.
28502858
if settings.EXPORT_EXCEPTION_HANDLING_HELPERS:
28512859
# If the user explicitly gave EXPORT_EXCEPTION_HANDLING_HELPERS=1 without
2852-
# enagling EH, errors out.
2860+
# enabling EH, errors out.
28532861
if settings.DISABLE_EXCEPTION_CATCHING and not settings.WASM_EXCEPTIONS:
28542862
exit_with_error('EXPORT_EXCEPTION_HANDLING_HELPERS requires either of -fexceptions or -fwasm-exceptions')
28552863
# We also export refcount increasing and decreasing functions because if you

src/library.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1245,7 +1245,13 @@ mergeInto(LibraryManager.library, {
12451245

12461246
#if SUPPORT_LONGJMP == 'emscripten'
12471247
_emscripten_throw_longjmp__sig: 'v',
1248-
_emscripten_throw_longjmp: function() { throw Infinity; },
1248+
_emscripten_throw_longjmp: function() {
1249+
#if EXCEPTION_STACK_TRACES
1250+
throw new EmscriptenSjLj;
1251+
#else
1252+
throw Infinity;
1253+
#endif
1254+
},
12491255
#elif !SUPPORT_LONGJMP
12501256
#if !INCLUDE_FULL_LIBRARY
12511257
// These are in order to print helpful error messages when either longjmp of

src/library_dylink.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,17 @@ var LibraryDylink = {
309309
return dynCall(sig, arguments[0], Array.prototype.slice.call(arguments, 1));
310310
} catch(e) {
311311
stackRestore(sp);
312-
// Exceptions thrown from C++ exception will be integer numbers.
313-
// longjmp will throw the number Infinity. Re-throw other types of
314-
// exceptions using a compact and fast check.
312+
// Create a try-catch guard that rethrows the Emscripten EH exception.
313+
#if EXCEPTION_STACK_TRACES
314+
// Exceptions thrown from C++ and longjmps will be an instance of
315+
// EmscriptenEH.
316+
if (!(e instanceof EmscriptenEH)) throw e;
317+
#else
318+
// Exceptions thrown from C++ will be a pointer (number) and longjmp
319+
// will throw the number Infinity. Use the compact and fast "e !== e+0"
320+
// test to check if e was not a Number.
315321
if (e !== e+0) throw e;
322+
#endif
316323
_setThrew(1, 0);
317324
}
318325
}

src/parseTools.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -488,14 +488,18 @@ function makeReturn64(value) {
488488
return `(setTempRet0(${pair[1]}), ${pair[0]})`;
489489
}
490490

491-
function makeThrow(what) {
491+
function makeThrow(excPtr) {
492492
if (ASSERTIONS && DISABLE_EXCEPTION_CATCHING) {
493-
what += ' + " - Exception catching is disabled, this exception cannot be caught. Compile with -sNO_DISABLE_EXCEPTION_CATCHING or -sEXCEPTION_CATCHING_ALLOWED=[..] to catch."';
493+
excPtr += ' + " - Exception catching is disabled, this exception cannot be caught. Compile with -sNO_DISABLE_EXCEPTION_CATCHING or -sEXCEPTION_CATCHING_ALLOWED=[..] to catch."';
494494
if (MAIN_MODULE) {
495-
what += ' + " (note: in dynamic linking, if a side module wants exceptions, the main module must be built with that support)"';
495+
excPtr += ' + " (note: in dynamic linking, if a side module wants exceptions, the main module must be built with that support)"';
496496
}
497+
return `throw ${excPtr};`;
497498
}
498-
return `throw ${what};`;
499+
if (EXCEPTION_STACK_TRACES) {
500+
return `throw new CppException(${excPtr});`;
501+
}
502+
return `throw ${excPtr};`;
499503
}
500504

501505
function charCode(char) {

src/preamble.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,8 @@ function createExportWrapper(name, fixedasm) {
543543
}
544544
#endif
545545

546+
#include "runtime_exceptions.js"
547+
546548
#if ABORT_ON_WASM_EXCEPTIONS
547549
// `abortWrapperDepth` counts the recursion level of the wrapper function so
548550
// that we only handle exceptions at the top level letting the exception
@@ -566,8 +568,12 @@ function makeAbortWrapper(original) {
566568
if (
567569
ABORT // rethrow exception if abort() was called in the original function call above
568570
|| abortWrapperDepth > 1 // rethrow exceptions not caught at the top level if exception catching is enabled; rethrow from exceptions from within callMain
569-
#if SUPPORT_LONGJMP == 'emscripten'
570-
|| e === Infinity // rethrow longjmp if enabled (In Emscripten EH format longjmp will throw Infinity)
571+
#if SUPPORT_LONGJMP == 'emscripten' // Rethrow longjmp if enabled
572+
#if EXCEPTION_STACK_TRACES
573+
|| e instanceof EmscriptenSjLj // EXCEPTION_STACK_TRACES=1 will throw an instance of EmscriptenSjLj
574+
#else
575+
|| e === Infinity // EXCEPTION_STACK_TRACES=0 will throw Infinity
576+
#endif // EXCEPTION_STACK_TRACES
571577
#endif
572578
) {
573579
throw e;

src/preamble_minimal.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ var runtimeInitialized = false;
146146

147147
#include "runtime_math.js"
148148
#include "memoryprofiler.js"
149+
#include "runtime_exceptions.js"
149150
#include "runtime_debug.js"
150151

151152
// === Body ===

src/runtime_exceptions.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* @license
3+
* Copyright 2023 The Emscripten Authors
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
#if EXCEPTION_STACK_TRACES && !WASM_EXCEPTIONS
8+
// Base Emscripten EH error class
9+
class EmscriptenEH extends Error {}
10+
11+
#if SUPPORT_LONGJMP == 'emscripten'
12+
class EmscriptenSjLj extends EmscriptenEH {}
13+
#endif
14+
15+
class CppException extends EmscriptenEH {
16+
constructor(excPtr) {
17+
super(excPtr);
18+
#if !DISABLE_EXCEPTION_CATCHING
19+
const excInfo = getExceptionMessage(excPtr);
20+
this.name = excInfo[0];
21+
this.message = excInfo[1];
22+
#endif
23+
}
24+
}
25+
#endif // EXCEPTION_STACK_TRACES && !WASM_EXCEPTIONS

src/settings.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -711,8 +711,9 @@ var EXPORT_EXCEPTION_HANDLING_HELPERS = false;
711711
// When this is enabled, exceptions will contain stack traces and uncaught
712712
// exceptions will display stack traces upon exiting. This defaults to true when
713713
// ASSERTIONS is enabled. This option is for users who want exceptions' stack
714-
// traces but do not want other overheads ASSERTIONS can incur. This currently
715-
// works only for Wasm exceptions (-fwasm-exceptions).
714+
// traces but do not want other overheads ASSERTIONS can incur.
715+
// This option implies EXPORT_EXCEPTION_HANDLING_HELPERS.
716+
// [link]
716717
var EXCEPTION_STACK_TRACES = false;
717718

718719
// Internal: Tracks whether Emscripten should link in exception throwing (C++

src/settings_internal.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ var HAS_MAIN = false;
232232
var LINK_AS_CXX = false;
233233

234234
// Set when some minimum browser version triggers doesn't support the
235-
// minimum set of ES6 featurs. This triggers transpilation to ES5
235+
// minimum set of ES6 features. This triggers transpilation to ES5
236236
// using closure compiler.
237237
var TRANSPILE_TO_ES5 = false;
238238

0 commit comments

Comments
 (0)