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.

Pick-to: 6.5 6.2 5.15
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 27ba69af2f)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 833f4f6913)
This commit is contained in:
Ulf Hermann 2024-01-19 16:19:06 +01:00 committed by Qt Cherry-pick Bot
parent 61fa31707a
commit 477c3c3c25
8 changed files with 77 additions and 18 deletions

View File

@ -1402,9 +1402,7 @@ void QObjectWrapper::destroyObject(bool lastCall)
if (!o->parent() && !ddata->indestructible) { if (!o->parent() && !ddata->indestructible) {
if (ddata && ddata->ownContext) { if (ddata && ddata->ownContext) {
Q_ASSERT(ddata->ownContext.data() == ddata->context); Q_ASSERT(ddata->ownContext.data() == ddata->context);
ddata->ownContext->emitDestruction(); ddata->ownContext->deepClearContextObject(o);
if (ddata->ownContext->contextObject() == o)
ddata->ownContext->setContextObject(nullptr);
ddata->ownContext.reset(); ddata->ownContext.reset();
ddata->context = nullptr; ddata->context = nullptr;
} }

View File

@ -128,6 +128,28 @@ public:
QObject *contextObject() const { return m_contextObject; } QObject *contextObject() const { return m_contextObject; }
void setContextObject(QObject *contextObject) { m_contextObject = contextObject; } void setContextObject(QObject *contextObject) { m_contextObject = contextObject; }
template<typename HandleSelf, typename HandleLinked>
void deepClearContextObject(
QObject *contextObject, HandleSelf &&handleSelf, HandleLinked &&handleLinked) {
for (QQmlContextData *lc = m_linkedContext.data(); lc; lc = lc->m_linkedContext.data()) {
handleLinked(lc);
if (lc->m_contextObject == contextObject)
lc->m_contextObject = nullptr;
}
handleSelf(this);
if (m_contextObject == contextObject)
m_contextObject = nullptr;
}
void deepClearContextObject(QObject *contextObject)
{
deepClearContextObject(
contextObject,
[](QQmlContextData *self) { self->emitDestruction(); },
[](QQmlContextData *){});
}
QQmlEngine *engine() const { return m_engine; } QQmlEngine *engine() const { return m_engine; }
void setEngine(QQmlEngine *engine) { m_engine = engine; } void setEngine(QQmlEngine *engine) { m_engine = engine; }

View File

@ -215,23 +215,16 @@ void QQmlPrivate::qdeclarativeelement_destructor(QObject *o)
{ {
QObjectPrivate *p = QObjectPrivate::get(o); QObjectPrivate *p = QObjectPrivate::get(o);
if (QQmlData *d = QQmlData::get(p)) { if (QQmlData *d = QQmlData::get(p)) {
const auto invalidate = [](QQmlContextData *c) {c->invalidate();};
if (d->ownContext) { if (d->ownContext) {
for (QQmlRefPointer<QQmlContextData> lc = d->ownContext->linkedContext(); lc; d->ownContext->deepClearContextObject(o, invalidate, invalidate);
lc = lc->linkedContext()) {
lc->invalidate();
if (lc->contextObject() == o)
lc->setContextObject(nullptr);
}
d->ownContext->invalidate();
if (d->ownContext->contextObject() == o)
d->ownContext->setContextObject(nullptr);
d->ownContext.reset(); d->ownContext.reset();
d->context = 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->setContextObject(nullptr);
if (d->hasVMEMetaObject || d->hasInterceptorMetaObject) { if (d->hasVMEMetaObject || d->hasInterceptorMetaObject) {
// This is somewhat dangerous because another thread might concurrently // This is somewhat dangerous because another thread might concurrently
// try to resolve the dynamic metaobject. In practice this will then // try to resolve the dynamic metaobject. In practice this will then
@ -407,9 +400,7 @@ void QQmlData::setQueuedForDeletion(QObject *object)
if (QQmlData *ddata = QQmlData::get(object)) { if (QQmlData *ddata = QQmlData::get(object)) {
if (ddata->ownContext) { if (ddata->ownContext) {
Q_ASSERT(ddata->ownContext.data() == ddata->context); Q_ASSERT(ddata->ownContext.data() == ddata->context);
ddata->context->emitDestruction(); ddata->ownContext->deepClearContextObject(object);
if (ddata->ownContext->contextObject() == object)
ddata->ownContext->setContextObject(nullptr);
ddata->ownContext.reset(); ddata->ownContext.reset();
ddata->context = nullptr; ddata->context = nullptr;
} }

View File

@ -0,0 +1,6 @@
import QtQml
B {
id: b
property int y: 2
}

View File

@ -0,0 +1,6 @@
import QtQml
C {
id: z
property int z: 3
}

View File

@ -0,0 +1,6 @@
import QtQml
QtObject {
id: outer
objectName: "the" + "C"
}

View File

@ -0,0 +1,5 @@
import QtQml
QtObject {
property A a: A {}
}

View File

@ -49,6 +49,7 @@ private slots:
void outerContextObject(); void outerContextObject();
void contextObjectHierarchy(); void contextObjectHierarchy();
void destroyContextProperty(); void destroyContextProperty();
void destroyContextObject();
void numericContextProperty(); void numericContextProperty();
void gcDeletesContextObject(); void gcDeletesContextObject();
@ -982,6 +983,30 @@ void tst_qqmlcontext::destroyContextProperty()
// TODO: Or are we? // TODO: Or are we?
} }
void tst_qqmlcontext::destroyContextObject()
{
QQmlEngine engine;
QList<QQmlRefPointer<QQmlContextData>> contexts;
QQmlComponent component(&engine, testFileUrl("destroyContextObject.qml"));
QScopedPointer<QObject> root(component.create());
QPointer<QObject> a = root->property("a").value<QObject *>();
QVERIFY(a);
for (QQmlRefPointer<QQmlContextData> 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() void tst_qqmlcontext::numericContextProperty()
{ {
QQmlEngine engine; QQmlEngine engine;