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

Use RuntimeError in abort() before module instantiation #18664

Merged
merged 5 commits into from
Feb 8, 2023

Conversation

aheejin
Copy link
Member

@aheejin aheejin commented Feb 4, 2023

We decided use a trap for abort function in case of Wasm EH in order to prevent infinite-looping (#16910).

emscripten/src/preamble.js

Lines 472 to 474 in 44b2c2a

#if WASM_EXCEPTIONS == 1
// See above, in the meantime, we resort to wasm code for trapping.
___trap();

The short reason is, in Wasm EH RuntimeError is treated as a foreign exception and caught by catch_all, so you try to abort the program and throw a RuntimeError, but it is caught by some catch_all used in the cleanup (=destructors, etc) code, causing the program to continue to run.

__trap is defined in compiler-rt and exported when Wasm EH is enabled.

This has worked so far, but in case we fail to instantiate a Wasm module and call abort because of it, we don't have access to the imported Module['asm']['__trap'], because Module['asm'] has not been set:

abort(reason);

And we haven't had the opportunity to run this code:

Module['asm'] = exports;

So the abort call will end like this:

TypeError: Cannot read properties of undefined (reading '__trap')
    at ___trap (/usr/local/google/home/aheejin/test/gl84/exported_api.js:5152:34)
    at abort (/usr/local/google/home/aheejin/test/gl84/exported_api.js:892:5)
    at /usr/local/google/home/aheejin/test/gl84/exported_api.js:1172:5

which may not be the worst thing in the world given that we are crashing anyway, but not the situation we intended.

This PR lets us throw RuntimeError in case we don't have the wasm module instantiated and have access to Module['asm']['__trap']. This is OK even with Wasm EH because we haven't been running the module, there's no risk of infinite-looping we tried to prevent in #16910.

I'm not sure how to add a test for this, because the test would require a module that fails to instantiate. This was found while I was debugging something else.

We decided use a trap for `abort` function in case of Wasm EH in order
to prevent infinite-looping (emscripten-core#16910).
https://github.com/emscripten-core/emscripten/blob/44b2c2a1ecad39c534e14179acb49419dfee528b/src/preamble.js#L472-L474

The short reason is, in Wasm EH `RuntimeError` is treated as a foreign
exception and caught by `catch_all`, so you try to abort the program and
throw a `RuntimeError`, but it is caught by some `catch_all` used in the
cleanup (=destructors, etc) code, causing the program to continue to
run.

`__trap` is defined in compiler-rt and exported when Wasm EH is enabled.

This has worked so far, but in case we fail to instantiate a Wasm
module and call `abort` because of it, we don't have access to the
imported `Module['asm']['__trap']`, because `Module['asm']` has not been
set:
https://github.com/emscripten-core/emscripten/blob/44b2c2a1ecad39c534e14179acb49419dfee528b/src/preamble.js#L848

So the `abort` call will end like this:
```console
TypeError: Cannot read properties of undefined (reading '__trap')
    at ___trap (/usr/local/google/home/aheejin/test/gl84/exported_api.js:5152:34)
    at abort (/usr/local/google/home/aheejin/test/gl84/exported_api.js:892:5)
    at /usr/local/google/home/aheejin/test/gl84/exported_api.js:1172:5
```
which may not be the worst thing in the world given that we are crashing
anyway, but not the situation we intended.

This PR lets us throw `RuntimeError` in case we don't have the wasm
module instantiated and have access to `Module['asm']['__trap']`. This
is OK even with Wasm EH because we haven't been running the module,
there's no risk of infinite-looping we tried to prevent in emscripten-core#16910.

I'm not sure how to add a test for this, because the test would require
a module that fails to instantiate. This was found while I was debugging
something else.
@aheejin aheejin requested review from kripken and sbc100 February 4, 2023 04:07
src/preamble.js Outdated
// Wasm EH code (because RuntimeError is considered as a foreign exception and
// caught by 'catch_all'), but in case throwing RuntimeError is fine because
// the module has not even been instantiated, even less running.
if (typeof Module['asm'] !== 'undefined')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this check if (runtimeInitialized) ? I think that should work but I'm not sure. It's generally the right condition to check before calling into the wasm, like ___trap() here, but maybe this place is special somehow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (runtimeInitialized) seems to work, thanks. Didn't know that variable exited.

@sbc100
Copy link
Collaborator

sbc100 commented Feb 7, 2023

Do you know where the offending call to abort is coming from exactly? I'm happy to land this fix but it also seems wrong to be a calling abort if the program isn't even running yet. I'd like to look into fixing that too.

Copy link
Member

@kripken kripken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm but I'm also curious where the problem happens. Is it in a global ctor or other startup code?

@kripken
Copy link
Member

kripken commented Feb 7, 2023

Oh, I think it can't be the wasm module at all, because then runtimeInitialized would be true..?

Then I think it has to be what you said before @aheejin , that it errors during a failure to instantiate the wasm in the JS code. @sbc100 I think such a call to abort() is reasonable? I'm not sure what we'd replace it with.

@aheejin
Copy link
Member Author

aheejin commented Feb 7, 2023

The case I was debugging was this: #18592 (comment)

And the callsite involved in this case was here, in instantiateArrayBuffer (click to see the code around the line):

abort(reason);

Actually, it is not the initialization itself that fails, even though this can happen when intitialization fails too. What fails in this example is this line:

wasmOffsetConverter = new WasmOffsetConverter(savedBinary, instance.module);

which transfers the control flow here:

emscripten/src/preamble.js

Lines 821 to 849 in 30ba0ae

}).then(receiver, function(reason) {
err('failed to asynchronously prepare wasm: ' + reason);
#if WASM == 2
#if ENVIRONMENT_MAY_BE_NODE || ENVIRONMENT_MAY_BE_SHELL
if (typeof location != 'undefined') {
#endif
// WebAssembly compilation failed, try running the JS fallback instead.
var search = location.search;
if (search.indexOf('_rwasm=0') < 0) {
location.href += (search ? search + '&' : '?') + '_rwasm=0';
// Return here to avoid calling abort() below. The application
// still has a chance to start sucessfully do we don't want to
// trigger onAbort or onExit handlers.
return;
}
#if ENVIRONMENT_MAY_BE_NODE || ENVIRONMENT_MAY_BE_SHELL
}
#endif
#endif // WASM == 2
#if ASSERTIONS
// Warn on some common problems.
if (isFileURI(wasmBinaryFile)) {
err('warning: Loading from a file URI (' + wasmBinaryFile + ') is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing');
}
#endif
abort(reason);
});

which ends with the abort call.

@kripken
Copy link
Member

kripken commented Feb 7, 2023

Makes sense, thanks for the details @aheejin

@aheejin aheejin merged commit 5f73782 into emscripten-core:main Feb 8, 2023
@aheejin aheejin deleted the abort_trap branch February 8, 2023 01:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants