diff options
| author | Ulf Hermann <ulf.hermann@qt.io> | 2024-01-19 16:19:06 +0100 |
|---|---|---|
| committer | Ulf Hermann <ulf.hermann@qt.io> | 2024-02-06 11:12:49 +0100 |
| commit | bba76cd15f718c79f9c5e6f5e297ed211887f5f4 (patch) | |
| tree | e7f576e85f5156c82839accdeb1c47a8f61a90c8 | |
| parent | 684656d53a3064c61744921269d4dca25c08dc5b (diff) | |
QtQml: Clear context objects more thoroughly on destruction
The same object can be the context object of a hierarchy of contexts. So
far we would only clear one of them, leaving dangling pointers in the
others. Clear all the contexts.
Fixes: QTBUG-119326
Change-Id: I509f257672813866e3736b51f430f1243a8577f0
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
(cherry picked from commit 27ba69af2f64a8b194655c9fbb276ce981075f75)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 833f4f6913835a18c37b02bd4784d62cbb4d0701)
(cherry picked from commit 477c3c3c25d478caa5b833a9c64f3945b3d3a83e)
(cherry picked from commit 7318fadc42e462babeca473864d12cfd033ffa42)
(cherry picked from commit e1ded8e46f71c12855811d0ee1cb2c36dcf67517)
| -rw-r--r-- | src/qml/jsruntime/qv4qobjectwrapper.cpp | 2 | ||||
| -rw-r--r-- | src/qml/qml/qqmlcontext_p.h | 21 | ||||
| -rw-r--r-- | src/qml/qml/qqmlengine.cpp | 20 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlcontext/data/A.qml | 6 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlcontext/data/B.qml | 6 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlcontext/data/C.qml | 6 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlcontext/data/destroyContextObject.qml | 5 | ||||
| -rw-r--r-- | tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp | 25 |
8 files changed, 76 insertions, 15 deletions
diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index b9cb74c3ca..e261b4a6d3 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -1153,7 +1153,7 @@ void QObjectWrapper::destroyObject(bool lastCall) if (!h->object()->parent() && !ddata->indestructible) { if (ddata && ddata->ownContext) { Q_ASSERT(ddata->ownContext == ddata->context); - ddata->ownContext->emitDestruction(); + ddata->ownContext->deepClearContextObject(h->object()); ddata->ownContext = nullptr; ddata->context = nullptr; } diff --git a/src/qml/qml/qqmlcontext_p.h b/src/qml/qml/qqmlcontext_p.h index 4d09abd927..92fd9f3a9f 100644 --- a/src/qml/qml/qqmlcontext_p.h +++ b/src/qml/qml/qqmlcontext_p.h @@ -139,6 +139,27 @@ public: // If internal is false publicContext owns this. QQmlContext *asQQmlContext(); QQmlContextPrivate *asQQmlContextPrivate(); + + template<typename HandleSelf, typename HandleLinked> + void deepClearContextObject( + QObject *expectedContextObject, HandleSelf &&handleSelf, HandleLinked &&handleLinked) { + for (QQmlContextData *lc = linkedContext; lc; lc = lc->linkedContext) { + handleLinked(lc); + if (lc->contextObject == expectedContextObject) + lc->contextObject = nullptr; + } + handleSelf(this); + if (contextObject == expectedContextObject) + contextObject = nullptr; + } + void deepClearContextObject(QObject *contextObject) + { + deepClearContextObject( + contextObject, + [](QQmlContextData *self) { self->emitDestruction(); }, + [](QQmlContextData *){}); + } + quint32 refCount = 0; quint32 isInternal:1; quint32 isJSContext:1; diff --git a/src/qml/qml/qqmlengine.cpp b/src/qml/qml/qqmlengine.cpp index e564a3d0cf..a0ecdfa61a 100644 --- a/src/qml/qml/qqmlengine.cpp +++ b/src/qml/qml/qqmlengine.cpp @@ -695,22 +695,16 @@ QQmlEnginePrivate::~QQmlEnginePrivate() void QQmlPrivate::qdeclarativeelement_destructor(QObject *o) { if (QQmlData *d = QQmlData::get(o)) { + const auto invalidate = [](QQmlContextData *c) {c->invalidate();}; if (d->ownContext) { - for (QQmlContextData *lc = d->ownContext->linkedContext; lc; lc = lc->linkedContext) { - lc->invalidate(); - if (lc->contextObject == o) - lc->contextObject = nullptr; - } - d->ownContext->invalidate(); - if (d->ownContext->contextObject == o) - d->ownContext->contextObject = nullptr; + d->ownContext->deepClearContextObject(o, invalidate, invalidate); d->ownContext = nullptr; d->context = nullptr; + Q_ASSERT(!d->outerContext || d->outerContext->contextObject != o); + } else if (d->outerContext && d->outerContext->contextObject == o) { + d->outerContext->deepClearContextObject(o, invalidate, invalidate); } - if (d->outerContext && d->outerContext->contextObject == o) - d->outerContext->contextObject = nullptr; - // Mark this object as in the process of deletion to // prevent it resolving in bindings QQmlData::markAsDeleted(o); @@ -884,9 +878,7 @@ void QQmlData::setQueuedForDeletion(QObject *object) if (QQmlData *ddata = QQmlData::get(object)) { if (ddata->ownContext) { Q_ASSERT(ddata->ownContext == ddata->context); - ddata->context->emitDestruction(); - if (ddata->ownContext->contextObject == object) - ddata->ownContext->contextObject = nullptr; + ddata->ownContext->deepClearContextObject(object); ddata->ownContext = nullptr; ddata->context = nullptr; } diff --git a/tests/auto/qml/qqmlcontext/data/A.qml b/tests/auto/qml/qqmlcontext/data/A.qml new file mode 100644 index 0000000000..569a54422f --- /dev/null +++ b/tests/auto/qml/qqmlcontext/data/A.qml @@ -0,0 +1,6 @@ +import QtQml 2.15 + +B { + id: b + property int y: 2 +} diff --git a/tests/auto/qml/qqmlcontext/data/B.qml b/tests/auto/qml/qqmlcontext/data/B.qml new file mode 100644 index 0000000000..43e3db04cb --- /dev/null +++ b/tests/auto/qml/qqmlcontext/data/B.qml @@ -0,0 +1,6 @@ +import QtQml 2.15 + +C { + id: z + property int z: 3 +} diff --git a/tests/auto/qml/qqmlcontext/data/C.qml b/tests/auto/qml/qqmlcontext/data/C.qml new file mode 100644 index 0000000000..07ad9c4f5a --- /dev/null +++ b/tests/auto/qml/qqmlcontext/data/C.qml @@ -0,0 +1,6 @@ +import QtQml 2.15 + +QtObject { + id: outer + objectName: "the" + "C" +} diff --git a/tests/auto/qml/qqmlcontext/data/destroyContextObject.qml b/tests/auto/qml/qqmlcontext/data/destroyContextObject.qml new file mode 100644 index 0000000000..bfd090ab38 --- /dev/null +++ b/tests/auto/qml/qqmlcontext/data/destroyContextObject.qml @@ -0,0 +1,5 @@ +import QtQml 2.15 + +QtObject { + property A a: A {} +} diff --git a/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp b/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp index 2866988a1a..1106f7b529 100644 --- a/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp +++ b/tests/auto/qml/qqmlcontext/tst_qqmlcontext.cpp @@ -72,6 +72,7 @@ private slots: void outerContextObject(); void contextObjectHierarchy(); void destroyContextProperty(); + void destroyContextObject(); void numericContextProperty(); @@ -920,6 +921,30 @@ void tst_qqmlcontext::destroyContextProperty() // TODO: Or are we? } +void tst_qqmlcontext::destroyContextObject() +{ + QQmlEngine engine; + QList<QQmlContextDataRef> contexts; + QQmlComponent component(&engine, testFileUrl("destroyContextObject.qml")); + QScopedPointer<QObject> root(component.create()); + + QPointer<QObject> a = root->property("a").value<QObject *>(); + QVERIFY(a); + + for (QQmlContextDataRef context = QQmlData::get(a)->ownContext; + context; context = context->parent) { + contexts.append(context); + } + + QObject *deleted = a.data(); + root.reset(); + + QVERIFY(a.isNull()); + + for (const auto &context : contexts) + QVERIFY(context->contextObject != deleted); +} + void tst_qqmlcontext::numericContextProperty() { QQmlEngine engine; |
