diff options
| author | Luca Di Sera <luca.disera@qt.io> | 2025-07-09 14:55:05 +0200 |
|---|---|---|
| committer | Luca Di Sera <luca.disera@qt.io> | 2025-07-16 13:11:54 +0200 |
| commit | bd801b6fa046da698b5c98add6f234644d4f6b22 (patch) | |
| tree | bfcf05e5ef4aac2ad508a66851804d361c1cba95 | |
| parent | 16caa338e39f77886a5ec184e0dbae4742884fd3 (diff) | |
JIT: Always zero out the accumulator when an exception is thrown
Currently when a JITtted function throws an exception, on exiting, the
accumulator is not zeroed-out if we don't have an exception handler and
is zeroed out when we do have an exception handler.
In the case where an exception handler is missing, this means that if
the caller is making use of the result of the function call, it might be
dealing with garbage memory.
Many times this isn't an issue as the result of the call would correctly
be ignored in the face of handling the presence of an exception.
Nonetheless, not directly using the result is not necessarily enough to
avoid issues with the possible garbage.
In particular, if the result of the call is put on the JS stack and
handling the exception allocates, the GC might be run as part of the
allocation and read the elements on the JS stack, one of which would be
the garbage memory that was returned.
One case where this can happen in the current code-base is during the evaluation
of a non-signal `QQmlJavaScriptExpression`, which does put the result of
a call on the JS stack while later handling an exception in a possibly
allocating way, with the garbage result still on the JS stack.
Furthermore, the interpreter currently isn't affected by the same issue,
as the interpreter always zeroes out when unwinding without running an
handler.
This adds the additional problem of the behavior differing between the
an interpreted function and its JITted version in the face of an
exception when the result of the call is voluntarily or not inspected.
To avoid the issue, the code generated by the baseline JIT was modified
to always zero out the accumulator before exiting a function call after
an exception, independently of the presence of an exception handler.
This further aligns the behavior of a JITted function to that of the
interpreter when no handler is present.
A test was added to `tst_qqmlecmascript` that exemplifies the issue by
passing by `QQmlJavaScriptExpression::evaluate` with a JITted throwing
function.
Fixes: QTBUG-138242
Change-Id: I969dc790f8a274364ae124afaeae8e2381fc82ae
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
(cherry picked from commit 27feb8ba884be638d3cae848c4f4cbff1d29b243)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 98f488af50c82e15a11dfb6960d75e8ba1ee3da3)
(cherry picked from commit 4f1de9f3f2b9bd4760918795b10ad12a007d5665)
(cherry picked from commit 35bfbf1aefb9278459c23c34b0ccbfc394aa0b3d)
3 files changed, 62 insertions, 1 deletions
diff --git a/src/qml/jit/qv4assemblercommon_p.h b/src/qml/jit/qv4assemblercommon_p.h index ef89556bde..1444a93ca7 100644 --- a/src/qml/jit/qv4assemblercommon_p.h +++ b/src/qml/jit/qv4assemblercommon_p.h @@ -583,12 +583,12 @@ public: for (Jump j : catchyJumps) j.link(this); + loadUndefined(); // We don't need to check for isInterrupted here because if that is set, // then the first checkException() in any exception handler will find another "exception" // and jump out of the exception handler. loadPtr(exceptionHandlerAddress(), ScratchRegister); Jump exitFunction = branchPtr(Equal, ScratchRegister, TrustedImmPtr(0)); - loadUndefined(); jump(ScratchRegister); exitFunction.link(this); diff --git a/tests/auto/qml/qqmlecmascript/data/jittedJavaScriptExpressionDoesNotCrashOnExceptionBeingThrown.qml b/tests/auto/qml/qqmlecmascript/data/jittedJavaScriptExpressionDoesNotCrashOnExceptionBeingThrown.qml new file mode 100644 index 0000000000..7249113fd0 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/jittedJavaScriptExpressionDoesNotCrashOnExceptionBeingThrown.qml @@ -0,0 +1,40 @@ +import QtQml +import QtQuick + +Window { + id: root + + Item { + id: child + + Timer { + id: timer + + property int fuel: 10 + + interval: 1 + repeat: true + running: true + onTriggered: { + if (--fuel == 0) + running = false; + parent.state = parent.state === "inactive" ? "active" : "inactive"; + } + } + + states: [ + State { + name: "active" + StateChangeScript { + script: root.active = true; + } + }, + State { + name: "inactive" + StateChangeScript { + script: root.active = false; + } + } + ] + } +} diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index b5f5d8d1d6..dcde9a62f1 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -26,6 +26,7 @@ #include <private/qv4objectiterator_p.h> #include <private/qqmlabstractbinding_p.h> #include <private/qqmlvaluetypeproxybinding_p.h> +#include <private/qqmltimer_p.h> #include <QtCore/private/qproperty_p.h> #include <QtQuick/qquickwindow.h> #include <QtQuick/private/qquickitem_p.h> @@ -426,6 +427,8 @@ private slots: void methodCallOnDerivedSingleton(); + void jittedJavaScriptExpressionDoesNotCrashOnExceptionBeingThrown(); + private: // static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter); static void verifyContextLifetime(const QQmlRefPointer<QQmlContextData> &ctxt); @@ -10518,6 +10521,24 @@ void tst_qqmlecmascript::methodCallOnDerivedSingleton() QVERIFY(singleton->m_okay); } +void tst_qqmlecmascript::jittedJavaScriptExpressionDoesNotCrashOnExceptionBeingThrown() +{ + QQmlEngine engine; + + engine.handle()->memoryManager->aggressiveGC = true; + + QQmlComponent c(&engine, testFileUrl("jittedJavaScriptExpressionDoesNotCrashOnExceptionBeingThrown.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY2(o, qPrintable(c.errorString())); + + QQmlContext *context = qmlContext(o.data()); + auto timer = qobject_cast<QQmlTimer*>(context->objectForName("timer")); + QVERIFY(timer); + + QTRY_VERIFY(!timer->isRunning()); +} + QTEST_MAIN(tst_qqmlecmascript) #include "tst_qqmlecmascript.moc" |
