aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuca Di Sera <luca.disera@qt.io>2025-07-09 14:55:05 +0200
committerLuca Di Sera <luca.disera@qt.io>2025-07-16 13:11:54 +0200
commitbd801b6fa046da698b5c98add6f234644d4f6b22 (patch)
treebfcf05e5ef4aac2ad508a66851804d361c1cba95
parent16caa338e39f77886a5ec184e0dbae4742884fd3 (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)
-rw-r--r--src/qml/jit/qv4assemblercommon_p.h2
-rw-r--r--tests/auto/qml/qqmlecmascript/data/jittedJavaScriptExpressionDoesNotCrashOnExceptionBeingThrown.qml40
-rw-r--r--tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp21
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"