diff --git a/mkspecs/features/wasm/wasm.prf b/mkspecs/features/wasm/wasm.prf index 4e969aaf5ff..f1859aee41d 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 147c02e36df..5fd76e8536e 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 176d0ce4d7c..25e4707c565 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 ee032e9952d..d93aef64172 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 f1285d617ae..6f801f6dee2 100644 --- a/tests/manual/wasm/qtloader_integration/test_body.js +++ b/tests/manual/wasm/qtloader_integration/test_body.js @@ -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();