diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2024-01-17 09:01:20 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2024-01-17 20:30:47 +0100 |
commit | 9d8e78a2f2661d8a1f2909bfe8a20e15f833af2e (patch) | |
tree | 571b61e916ce6baf1ab92f3dceff942f62f15f20 | |
parent | 1a5f10746ab42527f9a0c7aa170b3f593b31bffb (diff) |
QtQml: Do not call signal handlers on half-deleted objects
Pick-to: 6.7 6.6
Fixes: QTBUG-121022
Change-Id: Icdefd6bef4906700d88eca47c09d0abe54f1eec9
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
-rw-r--r-- | src/qml/jsruntime/qv4qobjectwrapper.cpp | 4 | ||||
-rw-r--r-- | tests/auto/qml/qqmlecmascript/data/scriptConnect.deletion.qml | 36 | ||||
-rw-r--r-- | tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 18 |
3 files changed, 57 insertions, 1 deletions
diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index c4ae949ea8..5463133801 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -1197,13 +1197,15 @@ struct QObjectSlotDispatcher : public QtPrivate::QSlotObjectBase static void impl(int which, QSlotObjectBase *this_, QObject *receiver, void **metaArgs, bool *ret) { - Q_UNUSED(receiver); switch (which) { case Destroy: { delete static_cast<QObjectSlotDispatcher*>(this_); } break; case Call: { + if (QQmlData::wasDeleted(receiver)) + break; + QObjectSlotDispatcher *This = static_cast<QObjectSlotDispatcher*>(this_); ExecutionEngine *v4 = This->function.engine(); // Might be that we're still connected to a signal that's emitted long diff --git a/tests/auto/qml/qqmlecmascript/data/scriptConnect.deletion.qml b/tests/auto/qml/qqmlecmascript/data/scriptConnect.deletion.qml new file mode 100644 index 0000000000..efbbc9fedc --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/scriptConnect.deletion.qml @@ -0,0 +1,36 @@ +import Qt.test +import QtQml + +QtObject { + id: root + + property int a: 0 + property int b: 0 + + signal someSignal + + function destroyObj() { + obj.destroy() + } + + function test() { + ++a + } + + component DestructionReceiver: QtObject { + // Has its own context and therefore can receive Component.onDestruction + } + + property QtObject obj: QtObject { + property QtObject inner: DestructionReceiver { + Component.onDestruction: { + // The outer obj is already queued for deletion. + // We don't want to see this signal delivered. + root.someSignal(); + ++root.b + } + } + } + + Component.onCompleted: someSignal.connect(obj, test) +} diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 35b7e6a535..6680dd1429 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -3910,7 +3910,25 @@ void tst_qqmlecmascript::scriptConnect() engine.clearSingletons(); QMetaObject::invokeMethod(obj.data(), "mySignal", Qt::DirectConnection); QCOMPARE(obj.data()->property("a").toInt(), 1); + } + + { + QQmlComponent component(&engine, testFileUrl("scriptConnect.deletion.qml")); + + QScopedPointer<QObject> obj(component.create()); + QVERIFY2(obj, qPrintable(component.errorString())); + QVERIFY(!obj.isNull()); + + QCOMPARE(obj->property("a"), 0); + + QMetaObject::invokeMethod(obj.data(), "someSignal"); + QCOMPARE(obj->property("a"), 1); + + QCOMPARE(obj->property("b"), 0); + QMetaObject::invokeMethod(obj.data(), "destroyObj", Qt::DirectConnection); + QTRY_COMPARE(obj->property("b"), 1); + QCOMPARE(obj->property("a"), 1); } } |