Details
-
Bug
-
Resolution: Fixed
-
P1: Critical
-
5.15.7
-
None
Description
Crash on destruction of item based on QQuickItem.
Crash happens in case when item or children of the item have CPP bindings to parentChanged/visibleChanged signals (signals of child items or item) and virtual function of the destroying item called in connected cpp slot.
Current implementation of ~QQuckItem() emits signals
- parentChanged for item and children items
- visibleChanged for item and children items in case when item was invisible before destruction
When the slot (on someChild.VisibleChanged) is called developer does not expect that the signal was called during destruction of the item, so any function of item (stored as member/QPointer, for example) can be called and crash the app.
stack trace on the crash:
deleted item -> ~MyItem -> ~QQuckItem -> setParent(nullptr) -> QQuickItemPrivate::setEffectiveVisibleRecur for children -> parentChanged / visibleChanged in child -> Slot with call of item virtual function -> SEGFAULT
Pointer to that item is still valid in the slot.
Isolated example:
class ItemForTesting: public QQuickItem { Q_OBJECT public: explicit ItemForTesting(QQuickItem *parent = nullptr); ~ItemForTesting() override = default; virtual void setMember(int val); int m_member; }; ItemForTesting::ItemForTesting(QQuickItem *parent) : QQuickItem(parent) , m_member(0) {} void ItemForTesting::setMember(int val) { m_member = val; } //// Test case { .... QPointer<ItemForTesting> mainItem = new ItemForTesting(m_rootItem); mainItem->setVisible(false); // in this case visibleChanged will be emitted for item and for child items mainItem->connect(mainItem.data(), &QQuickItem::parentChanged, mainItem.data(), [=]() { qCritical() << "ParentChanged for mainItem. Check pointer" << QQmlData::wasDeleted(mainItem.data()) << mainItem.isNull(); // mainItem->setMember(2); // possible crash is here }); QQuickItem *childItem = new QQuickItem(mainItem); childItem->connect(childItem, &QQuickItem::visibleChanged, childItem, [=, &mainItem]() { qCritical() << "visibleChanged for childItem to" << childItem->isVisible() << ". Check pointer of mainItem" << QQmlData::wasDeleted(mainItem.data()) << mainItem.isNull(); mainItem->setMember(2); // Crash is here }); QSignalSpy spyDestroy(mainItem, &QObject::destroyed); mainItem->deleteLater(); spyDestroy.wait(); EXPECT_EQ(spyDestroy.count(), 1); }
the main problem here - calling of virtual function during destruction.
But in current implementation of ~QQuickItem, developer of ItemForTesting class could not grantee that virtual function of ItemForTesting will not be called during destruction of the ItemForTesting instance, even if developer does not call virtual function in the destructor directly. It can be called indirectly in children via signals-slots during ~QQuickItem.
Potentially, any component with virtual function can crash app in this case (because of unexpected signals).
Attached example qtbug107850_extended.ziphas 3 options:
- to change visible for mainItem created in QML
- to call deleting of mainItem created in QML -> in case if mainItem had visible = false (default case in this example) before the deletion it crashes.
- to call Cpp crash case (like mentioned in the ticket description)
pressing on 2 -> crash
pressing on 3 -> crash
pressing on 1, then 2 -> no crash
Attachments
Issue Links
- is cloned by
-
QTBUG-108213 Unexpected visibleChanged signals on QQuickItem destruction
- Closed
- relates to
-
QTBUG-95262 TypeErrors in tests
- Closed
-
QTBUG-108028 Property bindings can be re-evaluated after items are destroyed
- Closed
-
QTBUG-108213 Unexpected visibleChanged signals on QQuickItem destruction
- Closed
-
QTBUG-109136 Crash on QQuickItem destruction
- Closed
- mentioned in
-
Page Loading...
For Gerrit Dashboard: QTBUG-107850 | ||||||
---|---|---|---|---|---|---|
# | Subject | Branch | Project | Status | CR | V |
441283,1 | WIP block signals in the QQuickItem dtor | dev | qt/qtdeclarative | Status: NEW | -2 | 0 |