summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Sørvig <morten.sorvig@qt.io>2023-10-25 12:42:02 +0200
committerMorten Johan Sørvig <morten.sorvig@qt.io>2023-12-20 00:44:28 +0000
commitdb93cd4f61ec9ad75b2389e5ac1724becfce3a1b (patch)
tree3002bf37bea55f497664b51cc7c32d024c221c46
parentac4619a36a54a2168ea5d7a2c7d059781564098c (diff)
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>
-rw-r--r--mkspecs/features/wasm/wasm.prf4
-rw-r--r--src/corelib/Qt6WasmMacros.cmake2
-rw-r--r--src/plugins/platforms/wasm/qtloader.js17
-rw-r--r--tests/manual/wasm/qtloader_integration/main.cpp12
-rw-r--r--tests/manual/wasm/qtloader_integration/test_body.js28
5 files changed, 59 insertions, 4 deletions
diff --git a/mkspecs/features/wasm/wasm.prf b/mkspecs/features/wasm/wasm.prf
index 4e969aaf5f..f1859aee41 100644
--- a/mkspecs/features/wasm/wasm.prf
+++ b/mkspecs/features/wasm/wasm.prf
@@ -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
diff --git a/src/corelib/Qt6WasmMacros.cmake b/src/corelib/Qt6WasmMacros.cmake
index 147c02e36d..5fd76e8536 100644
--- a/src/corelib/Qt6WasmMacros.cmake
+++ b/src/corelib/Qt6WasmMacros.cmake
@@ -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})
diff --git a/src/plugins/platforms/wasm/qtloader.js b/src/plugins/platforms/wasm/qtloader.js
index 176d0ce4d7..25e4707c56 100644
--- a/src/plugins/platforms/wasm/qtloader.js
+++ b/src/plugins/platforms/wasm/qtloader.js
@@ -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?.({
diff --git a/tests/manual/wasm/qtloader_integration/main.cpp b/tests/manual/wasm/qtloader_integration/main.cpp
index ee032e9952..d93aef6417 100644
--- a/tests/manual/wasm/qtloader_integration/main.cpp
+++ b/tests/manual/wasm/qtloader_integration/main.cpp
@@ -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();
diff --git a/tests/manual/wasm/qtloader_integration/test_body.js b/tests/manual/wasm/qtloader_integration/test_body.js
index f1285d617a..6f801f6dee 100644
--- a/tests/manual/wasm/qtloader_integration/test_body.js
+++ b/tests/manual/wasm/qtloader_integration/test_body.js
@@ -345,11 +345,37 @@ 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);
+ assert.isUndefined(exitStatus.code);
+ 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);
}