Skip to content
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

Stack overflow when calling std::terminate + -fwasm-exceptions #16407

Closed
blaugold opened this issue Mar 2, 2022 · 0 comments · Fixed by #16910
Closed

Stack overflow when calling std::terminate + -fwasm-exceptions #16407

blaugold opened this issue Mar 2, 2022 · 0 comments · Fixed by #16910
Assignees

Comments

@blaugold
Copy link

blaugold commented Mar 2, 2022

The following code when compiled with -fwasm-exceptions causes a stack overflow when executed:

// std_terminate.cc

#include <exception>

int main(int argc, char **argv)
{
    std::terminate();
    return 0;
}

Version of emscripten/emsdk:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.6 (c102a423a06d9833a8481d6859888bebf22842b0)
clang version 15.0.0 (https://github.com/llvm/llvm-project 6eec4835844439ab932515ff4ac857773c639171)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /Users/gabriel/Dev/emsdk/upstream/bin

Failing command line in full:

emcc std_terminate.cc -fwasm-exceptions -o std_terminate.js -g
node --experimental-wasm-eh std_terminate.js

This is the end of the program's output:

...
Aborted(native code called abort())
Aborted(native code called abort())
Aborted(native code called abort())
Aborted(native code called abort())
Aborted(native code called abort())
Aborted(native code called abort())
Aborted(native code called abort())
/Users/gabriel/Dev/emscripten-experiments/std_terminate.js:146
      throw ex;
      ^

RangeError: Maximum call stack size exceeded
    at console.value (node:internal/console/constructor:289:13)
    at console.warn (node:internal/console/constructor:364:26)
    at abort (/Users/gabriel/Dev/emscripten-experiments/std_terminate.js:1468:3)
    at _abort (/Users/gabriel/Dev/emscripten-experiments/std_terminate.js:1828:7)
    at abort_message (wasm://wasm/000abcea:wasm-function[11]:0x48b)
    at demangling_terminate_handler() (wasm://wasm/000abcea:wasm-function[12]:0x553)
    at std::__terminate(void (*)()) (wasm://wasm/000abcea:wasm-function[17]:0x601)
    at std::terminate() (wasm://wasm/000abcea:wasm-function[15]:0x5db)
    at std::__terminate(void (*)()) (wasm://wasm/000abcea:wasm-function[17]:0x626)
    at std::terminate() (wasm://wasm/000abcea:wasm-function[15]:0x5db)

Full link command and output with -v appended:

  "/Users/gabriel/Dev/emsdk/upstream/bin/clang++" -target wasm32-unknown-emscripten -DEMSCRIPTEN -D__EMSCRIPTEN_major__=3 -D__EMSCRIPTEN_minor__=1 -D__EMSCRIPTEN_tiny__=6 -fvisibility=default -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -D_LIBCPP_ABI_VERSION=2 -fno-threadsafe-statics -Werror=implicit-function-declaration -Xclang -iwithsysroot/include/SDL --sysroot=/Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot -Xclang -iwithsysroot/include/compat -fwasm-exceptions -g -v std_terminate.cc -c -o /var/folders/qf/gn4cjsnx0pj84fdxgzgt780c0000gn/T/emscripten_temp_z8ny51_l/std_terminate_0.o
clang version 15.0.0 (https://github.com/llvm/llvm-project 6eec4835844439ab932515ff4ac857773c639171)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /Users/gabriel/Dev/emsdk/upstream/bin
 (in-process)
 "/Users/gabriel/Dev/emsdk/upstream/bin/clang-15" -cc1 -triple wasm32-unknown-emscripten -emit-obj -mrelax-all --mrelax-relocations -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name std_terminate.cc -mrelocation-model static -mframe-pointer=none -ffp-contract=on -fno-rounding-math -mconstructor-aliases -target-feature +exception-handling -mllvm -wasm-enable-eh -target-cpu generic -mllvm -treat-scalable-fixed-error-as-warning -debug-info-kind=constructor -dwarf-version=4 -debugger-tuning=gdb -target-linker-version 609.8 -v -fcoverage-compilation-dir=/Users/gabriel/Dev/emscripten-experiments -resource-dir /Users/gabriel/Dev/emsdk/upstream/lib/clang/15.0.0 -D EMSCRIPTEN -D __EMSCRIPTEN_major__=3 -D __EMSCRIPTEN_minor__=1 -D __EMSCRIPTEN_tiny__=6 -D _LIBCPP_ABI_VERSION=2 -isysroot /Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot -internal-isystem /Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten/c++/v1 -internal-isystem /Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1 -internal-isystem /Users/gabriel/Dev/emsdk/upstream/lib/clang/15.0.0/include -internal-isystem /Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten -internal-isystem /Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot/include -Werror=implicit-function-declaration -fdeprecated-macro -fdebug-compilation-dir=/Users/gabriel/Dev/emscripten-experiments -ferror-limit 19 -fvisibility default -fgnuc-version=4.2.1 -fno-threadsafe-statics -fcxx-exceptions -fexceptions -exception-model=wasm -fcolor-diagnostics -iwithsysroot/include/SDL -iwithsysroot/include/compat -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -o /var/folders/qf/gn4cjsnx0pj84fdxgzgt780c0000gn/T/emscripten_temp_z8ny51_l/std_terminate_0.o -x c++ std_terminate.cc
clang -cc1 version 15.0.0 based upon LLVM 15.0.0git default target x86_64-apple-darwin21.3.0
ignoring nonexistent directory "/Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten/c++/v1"
ignoring nonexistent directory "/Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten"
#include "..." search starts here:
#include <...> search starts here:
 /Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot/include/SDL
 /Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot/include/compat
 /Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1
 /Users/gabriel/Dev/emsdk/upstream/lib/clang/15.0.0/include
 /Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot/include
End of search list.
 "/Users/gabriel/Dev/emsdk/upstream/bin/wasm-ld" -o std_terminate.wasm /var/folders/qf/gn4cjsnx0pj84fdxgzgt780c0000gn/T/emscripten_temp_z8ny51_l/std_terminate_0.o -L/Users/gabriel/Dev/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten -lGL -lal -lhtml5 -lstubs-debug -lnoexit -lc-debug -ldlmalloc -lcompiler_rt -lc++-except -lc++abi-except -lunwind-except -lsockets -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -mllvm -wasm-enable-eh -mllvm -exception-model=wasm --import-undefined --export-if-defined=main --export-if-defined=__start_em_asm --export-if-defined=__stop_em_asm --export-if-defined=__stdio_exit --export=emscripten_stack_get_end --export=emscripten_stack_get_free --export=emscripten_stack_get_base --export=emscripten_stack_init --export=stackSave --export=stackRestore --export=stackAlloc --export=__wasm_call_ctors --export=__errno_location --export-table -z stack-size=5242880 --initial-memory=16777216 --no-entry --max-memory=16777216 --global-base=1024
 "/Users/gabriel/Dev/emsdk/upstream/bin/wasm-emscripten-finalize" --minimize-wasm-changes -g --dyncalls-i64 --dwarf std_terminate.wasm -o std_terminate.wasm --detect-features
 "/Users/gabriel/Dev/emsdk/node/14.18.2_64bit/bin/node" /Users/gabriel/Dev/emsdk/upstream/emscripten/src/compiler.js /var/folders/qf/gn4cjsnx0pj84fdxgzgt780c0000gn/T/tmpd78xv3y0.json
 "/Users/gabriel/Dev/emsdk/upstream/bin/llvm-objcopy" std_terminate.wasm std_terminate.wasm --remove-section=producers
aheejin added a commit to aheejin/emscripten that referenced this issue May 7, 2022
We used to implement `abort()` with throwing a JS exception. emscripten-core#9730
changed it to `RuntimeError`, because it is the class a trap becomes
once it hits a JS frame.

But this turned out to be not working, because Wasm EH currently does
not assume all `RuntimeError`s are from traps; it decides whether a
`RuntimeError` is from a trap or not based on a hidden field within the
object. Relevant past discussion:
WebAssembly/exception-handling#89 (comment)

So at the moment we don't have a way of throwing a trap from JS, which
is inconvenient. I think we eventually want to make JS API for this, but
for the moment, this PR resorts to a wasm function that traps. This at
least makes calling `std::terminate` work. So far calling it exhausted
the call stack and aborted.

Fixes emscripten-core#16407.
aheejin added a commit to aheejin/emscripten that referenced this issue May 7, 2022
We used to implement `abort()` with throwing a JS exception. emscripten-core#9730
changed it to `RuntimeError`, because it is the class a trap becomes
once it hits a JS frame.

But this turned out to be not working, because Wasm EH currently does
not assume all `RuntimeError`s are from traps; it decides whether a
`RuntimeError` is from a trap or not based on a hidden field within the
object. Relevant past discussion:
WebAssembly/exception-handling#89 (comment)

So at the moment we don't have a way of throwing a trap from JS, which
is inconvenient. I think we eventually want to make JS API for this, but
for the moment, this PR resorts to a wasm function that traps. This at
least makes calling `std::terminate` work. So far calling it exhausted
the call stack and aborted.

Fixes emscripten-core#16407.
aheejin added a commit to aheejin/emscripten that referenced this issue May 10, 2022
`noexcept` function shouldn't throw, so `noexcept` function code
generation is to `invoke` every function call in those functions and in
case they throw, call `std::terminate`. This codegen comes from clang
and native platforms do this too. So in wasm, they become something like
```wasm
try
  function body
catch_all
  call std::terminate
end
```

`std::terminate` calls `std::__terminate`. Both of `std::terminate` and
`std::__terminate` are `noexcept` now. So that means their code is
structured like that, which sounds like self-calling, but normally no
function calls in those functions should ever throw, so that's fine. But
in our case, `abort` ends up throwing, which is a problem.

The function body of `__terminate` eventually calls JS `abort`, and ends
up here:
https://github.com/emscripten-core/emscripten/blob/970998b2670a9bcf39d31e2b01db571089955add/src/preamble.js#L605-L623

This ends up throwing a JS exception. This is basically just a foreign
exception from the wasm perspective, and is caught by `catch_all`, and
calls `std::terminate` again. And the whole process continues until the
call stack is exhausted.

What emscripten-core#9730 tried to do was throwing a trap, because Wasm
`catch`/`catch_all` don't catch traps. Traps become `RuntimeError`s
after they hit a JS frame. To be consistent, we decided
`catch`/`catch_all` shouldn't catch them after they become
`RuntimeError`s. That's the reason emscripten-core#9730 changed the code to throw not
just any random thing but `RuntimeError`. But somehow we decided that we
make that trap distinction not based on `RuntimeError` class but some
hidden field
(WebAssembly/exception-handling#89 (comment)).

This PR removes `noexcept` from `std::terminate` and
`std::__terminate`'s signatures so that the cleanup that contains
`catch_all` is not generated for those two functions. So now the JS
exception thrown by `abort()` will unwind the stack, which is different
from native, but that can be considered OK because I don't think users
expect `abort` to preserve the stack intact?

Fixes emscripten-core#16407.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants