wasm: Don't suppress exceptions during main()
If there's e.g. an infinite loop during main() that would previously result in a blank page, but not error message. The expected case is that we would get a RangeError exception, but that exception never reaches the catch handlers in qtloader.js. Work around this by setting noInitialRun, followed by calling main manually. We then need to handle the case where the app.exec() workaround throws, which should not trigger an error. Pick-to: 6.7 Change-Id: Ia8431279308770981316cd168e4316341bfb2531 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
parent
ac4619a36a
commit
db93cd4f61
@ -9,9 +9,9 @@ exists($$QMAKE_QT_CONFIG) {
|
||||
|
||||
## qmake puts a space if done otherwise
|
||||
!isEmpty(QT_WASM_EXTRA_EXPORTED_METHODS): {
|
||||
EXPORTED_METHODS = UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS,$$QT_WASM_EXTRA_EXPORTED_METHODS
|
||||
EXPORTED_METHODS = UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS,callMain,$$QT_WASM_EXTRA_EXPORTED_METHODS
|
||||
} else {
|
||||
EXPORTED_METHODS = UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS
|
||||
EXPORTED_METHODS = UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS,callMain
|
||||
}
|
||||
EMCC_LFLAGS += -s EXPORTED_RUNTIME_METHODS=$$EXPORTED_METHODS
|
||||
|
||||
|
@ -97,7 +97,7 @@ endfunction()
|
||||
function(_qt_internal_add_wasm_extra_exported_methods target)
|
||||
get_target_property(wasm_extra_exported_methods "${target}" QT_WASM_EXTRA_EXPORTED_METHODS)
|
||||
|
||||
set(wasm_default_exported_methods "UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS")
|
||||
set(wasm_default_exported_methods "UTF16ToString,stringToUTF16,JSEvents,specialHTMLTargets,FS,callMain")
|
||||
|
||||
if(NOT wasm_extra_exported_methods)
|
||||
set(wasm_extra_exported_methods ${QT_WASM_EXTRA_EXPORTED_METHODS})
|
||||
|
@ -105,6 +105,12 @@ async function qtLoad(config)
|
||||
config.qtFontDpi = config.qt.fontDpi;
|
||||
delete config.qt.fontDpi;
|
||||
|
||||
// Make Emscripten not call main(); this gives us more control over
|
||||
// the startup sequence.
|
||||
const originalNoInitialRun = config.noInitialRun;
|
||||
const originalArguments = config.arguments;
|
||||
config.noInitialRun = true;
|
||||
|
||||
// Used for rejecting a failed load's promise where emscripten itself does not allow it,
|
||||
// like in instantiateWasm below. This allows us to throw in case of a load error instead of
|
||||
// hanging on a promise to entry function, which emscripten unfortunately does.
|
||||
@ -219,7 +225,18 @@ async function qtLoad(config)
|
||||
try {
|
||||
instance = await Promise.race(
|
||||
[circuitBreaker, config.qt.entryFunction(config)]);
|
||||
|
||||
// Call main after creating the instance. We've opted into manually
|
||||
// calling main() by setting noInitialRun in the config. Thie Works around
|
||||
// issue where Emscripten suppresses all exceptions thrown during main.
|
||||
if (!originalNoInitialRun)
|
||||
instance.callMain(originalArguments);
|
||||
} catch (e) {
|
||||
// If this is the exception thrown by app.exec() then that is a normal
|
||||
// case and we suppress it.
|
||||
if (e == "unwind") // not much to go on
|
||||
return;
|
||||
|
||||
if (!onExitCalled) {
|
||||
onExitCalled = true;
|
||||
config.qt.onExit?.({
|
||||
|
@ -69,6 +69,11 @@ void crash()
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void stackOverflow()
|
||||
{
|
||||
stackOverflow(); // should eventually termniate with exception
|
||||
}
|
||||
|
||||
void exitApp()
|
||||
{
|
||||
emscripten_force_exit(ExitValueFromExitApp);
|
||||
@ -143,8 +148,15 @@ int main(int argc, char **argv)
|
||||
if (crashImmediately)
|
||||
crash();
|
||||
|
||||
const bool stackOverflowImmediately =
|
||||
std::find(arguments.begin(), arguments.end(), QStringLiteral("--stack-owerflow-immediately"))
|
||||
!= arguments.end();
|
||||
if (stackOverflowImmediately)
|
||||
stackOverflow();
|
||||
|
||||
const bool noGui = std::find(arguments.begin(), arguments.end(), QStringLiteral("--no-gui"))
|
||||
!= arguments.end();
|
||||
|
||||
if (!noGui) {
|
||||
AppWindow window;
|
||||
window.show();
|
||||
|
@ -345,7 +345,7 @@ export class QtLoaderIntegrationTests
|
||||
caughtException = e;
|
||||
}
|
||||
|
||||
assert.isUndefined(caughtException);
|
||||
assert.isTrue(caughtException !== undefined);
|
||||
assert.equal(1, onExitMock.calls.length);
|
||||
const exitStatus = onExitMock.calls[0][0];
|
||||
assert.isTrue(exitStatus.crashed);
|
||||
@ -353,6 +353,32 @@ export class QtLoaderIntegrationTests
|
||||
assert.isNotUndefined(exitStatus.text);
|
||||
}
|
||||
|
||||
async stackOwerflowImmediately()
|
||||
{
|
||||
const onExitMock = new Mock();
|
||||
let caughtException;
|
||||
try {
|
||||
await qtLoad({
|
||||
arguments: ['--no-gui', '--stack-owerflow-immediately'],
|
||||
qt: {
|
||||
onExit: onExitMock,
|
||||
entryFunction: tst_qtloader_integration_entry,
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
caughtException = e;
|
||||
}
|
||||
|
||||
assert.isTrue(caughtException !== undefined);
|
||||
assert.equal(1, onExitMock.calls.length);
|
||||
const exitStatus = onExitMock.calls[0][0];
|
||||
assert.isTrue(exitStatus.crashed);
|
||||
assert.isUndefined(exitStatus.code);
|
||||
// text should be "RangeError: Maximum call stack
|
||||
// size exceeded", or similar.
|
||||
assert.isNotUndefined(exitStatus.text);
|
||||
}
|
||||
|
||||
async userAbortCallbackCalled()
|
||||
{
|
||||
const onAbortMock = new Mock();
|
||||
|
Loading…
x
Reference in New Issue
Block a user