QQuickItem: avoid emitting signals during destruction
authorDebian Qt/KDE Maintainers <debian-qt-kde@lists.debian.org>
Thu, 7 Nov 2024 08:47:17 +0000 (11:47 +0300)
committerDmitry Shachnev <mitya57@debian.org>
Thu, 7 Nov 2024 08:47:17 +0000 (11:47 +0300)
Origin: upstream, https://code.qt.io/cgit/qt/qtdeclarative.git/commit/?id=74873324bdf33997
Last-Update: 2023-02-26

If a QQuickItem is in the QQuickItem destructor, then it is both unsafe
and unnecessary to emit property change notifications. Connected code
can no longer rely on the state of the emitting object - if it was
originally a subclass of QQuickItem, then those subclass destructors
will already have run. And the QQuickItem destructor will also have
partially run, leaving the object in an undefined state.

Add a flag that we set to true at the top of ~QQuickItem, and don't emit
visibleChildrenChanged, parentChanged, visibleChanged, and
childrenChanged for items that are partially destroyed already.

[ChangeLog][Qt Quick][QQuickItem] QQuickItem no longer emits change
notifications for the parent, children, visible, and visibleChildren
properties while it is being destroyed.

Gbp-Pq: Name qquickitem_no_signals_on_destruction.patch

src/quick/items/qquickitem.cpp
src/quick/items/qquickitem_p.h
tests/auto/quick/qquickitem2/tst_qquickitem.cpp

index 540d12c84a119b57a95e6af111cd3577d6be35d1..0e6db2344a3c4b38fa5db87cf7b790abc03f9430 100644 (file)
@@ -2328,6 +2328,7 @@ QQuickItem::QQuickItem(QQuickItemPrivate &dd, QQuickItem *parent)
 QQuickItem::~QQuickItem()
 {
     Q_D(QQuickItem);
+    d->inDestructor = true;
 
     if (d->windowRefCount > 1)
         d->windowRefCount = 1; // Make sure window is set to null in next call to derefWindow().
@@ -2695,9 +2696,8 @@ void QQuickItem::setParentItem(QQuickItem *parentItem)
 
         const bool wasVisible = isVisible();
         op->removeChild(this);
-        if (wasVisible) {
+        if (wasVisible && !op->inDestructor)
             emit oldParentItem->visibleChildrenChanged();
-        }
     } else if (d->window) {
         QQuickWindowPrivate::get(d->window)->parentlessItems.remove(this);
     }
@@ -2774,8 +2774,9 @@ void QQuickItem::setParentItem(QQuickItem *parentItem)
 
     d->itemChange(ItemParentHasChanged, d->parentItem);
 
-    emit parentChanged(d->parentItem);
-    if (isVisible() && d->parentItem)
+    if (!d->inDestructor)
+        emit parentChanged(d->parentItem);
+    if (isVisible() && d->parentItem && !QQuickItemPrivate::get(d->parentItem)->inDestructor)
         emit d->parentItem->visibleChildrenChanged();
 }
 
@@ -2971,7 +2972,8 @@ void QQuickItemPrivate::removeChild(QQuickItem *child)
 
     itemChange(QQuickItem::ItemChildRemovedChange, child);
 
-    emit q->childrenChanged();
+    if (!inDestructor)
+        emit q->childrenChanged();
 }
 
 void QQuickItemPrivate::refWindow(QQuickWindow *c)
@@ -3200,6 +3202,7 @@ QQuickItemPrivate::QQuickItemPrivate()
     , touchEnabled(false)
 #endif
     , hasCursorHandler(false)
+    , inDestructor(false)
     , dirtyAttributes(0)
     , nextDirtyItem(nullptr)
     , prevDirtyItem(nullptr)
@@ -6112,9 +6115,11 @@ bool QQuickItemPrivate::setEffectiveVisibleRecur(bool newEffectiveVisible)
         QAccessible::updateAccessibility(&ev);
     }
 #endif
-    emit q->visibleChanged();
-    if (childVisibilityChanged)
-        emit q->visibleChildrenChanged();
+    if (!inDestructor) {
+        emit q->visibleChanged();
+        if (childVisibilityChanged)
+            emit q->visibleChildrenChanged();
+    }
 
     return true;    // effective visibility DID change
 }
index 841d91bb40f9308deeb94f5a2fde2c2d8d3d0b4c..ade8fb61f2107dc33515be07c16bf943cd5f492e 100644 (file)
@@ -472,6 +472,7 @@ public:
     bool replayingPressEvent:1;
     bool touchEnabled:1;
     bool hasCursorHandler:1;
+    quint32 inDestructor:1; // has entered ~QQuickItem
 
     enum DirtyType {
         TransformOrigin         = 0x00000001,
index c8ef36ee6893552ce43e0f3afcf66b7190b5cc20..0b9b4ffd7108c42df7894730e0cbf698c1097548 100644 (file)
@@ -132,6 +132,8 @@ private slots:
 
     void grab();
 
+    void signalsOnDestruction();
+
 private:
     QQmlEngine engine;
     bool qt_tab_all_widgets() {
@@ -3532,6 +3534,52 @@ void tst_QQuickItem::isAncestorOf()
     QVERIFY(!sub2.isAncestorOf(&sub2));
 }
 
+/*
+    Items that are getting destroyed should not emit property change notifications.
+*/
+void tst_QQuickItem::signalsOnDestruction()
+{
+    QQuickWindow window;
+    window.show();
+
+    // visual children, but not QObject children
+    std::unique_ptr<QQuickItem> parent(new QQuickItem(window.contentItem()));
+    std::unique_ptr<QQuickItem> child(new QQuickItem);
+    std::unique_ptr<QQuickItem> grandChild(new QQuickItem);
+
+    QSignalSpy childrenSpy(parent.get(), &QQuickItem::childrenChanged);
+    QSignalSpy visibleChildrenSpy(parent.get(), &QQuickItem::visibleChildrenChanged);
+    QSignalSpy childParentSpy(child.get(), &QQuickItem::parentChanged);
+    QSignalSpy childChildrenSpy(child.get(), &QQuickItem::childrenChanged);
+    QSignalSpy childVisibleChildrenSpy(child.get(), &QQuickItem::visibleChanged);
+    QSignalSpy grandChildParentSpy(grandChild.get(), &QQuickItem::parentChanged);
+
+    child->setParentItem(parent.get());
+    QCOMPARE(childrenSpy.count(), 1);
+    QCOMPARE(visibleChildrenSpy.count(), 1);
+    QCOMPARE(childParentSpy.count(), 1);
+    QCOMPARE(childChildrenSpy.count(), 0);
+    QCOMPARE(childVisibleChildrenSpy.count(), 0);
+
+    grandChild->setParentItem(child.get());
+    QCOMPARE(childrenSpy.count(), 1);
+    QCOMPARE(visibleChildrenSpy.count(), 1);
+    QCOMPARE(childParentSpy.count(), 1);
+    QCOMPARE(childChildrenSpy.count(), 1);
+    QCOMPARE(childVisibleChildrenSpy.count(), 0);
+    QCOMPARE(grandChildParentSpy.count(), 1);
+
+    parent.reset();
+
+    QVERIFY(!child->parentItem());
+    QVERIFY(grandChild->parentItem());
+    QCOMPARE(childrenSpy.count(), 1);
+    QCOMPARE(visibleChildrenSpy.count(), 1);
+    QCOMPARE(childParentSpy.count(), 2);
+    QCOMPARE(grandChildParentSpy.count(), 1);
+}
+
+
 QTEST_MAIN(tst_QQuickItem)
 
 #include "tst_qquickitem.moc"