diff options
| author | Ulf Hermann <ulf.hermann@qt.io> | 2023-03-29 16:36:03 +0200 |
|---|---|---|
| committer | Ulf Hermann <ulf.hermann@qt.io> | 2023-04-25 19:50:21 +0200 |
| commit | b299416894bcaf7965fd53c195636d84f0674255 (patch) | |
| tree | 7249cc73df7a6c5a5b7a2d70786b724cde0c84ad | |
| parent | a235fb4ae68dcb887b64fcb60a97dfbae5381e79 (diff) | |
Models: Avoid crashes when deleting cache items
... and also fix and enable the test that tests it.
Fixes: QTBUG-91425
Change-Id: I58cf9ee29922f83fc6621f771b80ed557b31f106
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
(cherry picked from commit 0cfdecba54e4f40468c4c9a8a6668cc1bc0eff65)
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
| -rw-r--r-- | src/qmlmodels/qqmldelegatemodel.cpp | 23 | ||||
| -rw-r--r-- | tests/auto/qml/qml.pro | 1 | ||||
| -rw-r--r-- | tests/auto/qml/qqmldelegatemodel/data/deleteRace.qml | 50 | ||||
| -rw-r--r-- | tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp | 23 |
4 files changed, 79 insertions, 18 deletions
diff --git a/src/qmlmodels/qqmldelegatemodel.cpp b/src/qmlmodels/qqmldelegatemodel.cpp index ba636df45d..001c3bacd1 100644 --- a/src/qmlmodels/qqmldelegatemodel.cpp +++ b/src/qmlmodels/qqmldelegatemodel.cpp @@ -1871,10 +1871,15 @@ void QQmlDelegateModelPrivate::emitChanges() for (int i = 1; i < m_groupCount; ++i) QQmlDelegateModelGroupPrivate::get(m_groups[i])->emitModelUpdated(reset); - auto cacheCopy = m_cache; // deliberate; emitChanges may alter m_cache - for (QQmlDelegateModelItem *cacheItem : qAsConst(cacheCopy)) { - if (cacheItem->attached) - cacheItem->attached->emitChanges(); + // emitChanges may alter m_cache and delete items + QVarLengthArray<QPointer<QQmlDelegateModelAttached>> attachedObjects; + attachedObjects.reserve(m_cache.length()); + for (const QQmlDelegateModelItem *cacheItem : qAsConst(m_cache)) + attachedObjects.append(cacheItem->attached); + + for (const QPointer<QQmlDelegateModelAttached> &attached : qAsConst(attachedObjects)) { + if (attached && attached->m_cacheItem) + attached->emitChanges(); } } @@ -2663,20 +2668,24 @@ void QQmlDelegateModelAttached::emitChanges() m_previousGroups = m_cacheItem->groups; int indexChanges = 0; - for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i) { + const int groupCount = m_cacheItem->metaType->groupCount; + for (int i = 1; i < groupCount; ++i) { if (m_previousIndex[i] != m_currentIndex[i]) { m_previousIndex[i] = m_currentIndex[i]; indexChanges |= (1 << i); } } + // Don't access m_cacheItem anymore once we've started sending signals. + // We don't own it and someone might delete it. + int notifierId = 0; const QMetaObject *meta = metaObject(); - for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) { + for (int i = 1; i < groupCount; ++i, ++notifierId) { if (groupChanges & (1 << i)) QMetaObject::activate(this, meta, notifierId, nullptr); } - for (int i = 1; i < m_cacheItem->metaType->groupCount; ++i, ++notifierId) { + for (int i = 1; i < groupCount; ++i, ++notifierId) { if (indexChanges & (1 << i)) QMetaObject::activate(this, meta, notifierId, nullptr); } diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro index 621e8bb437..104fb216c2 100644 --- a/tests/auto/qml/qml.pro +++ b/tests/auto/qml/qml.pro @@ -43,6 +43,7 @@ PRIVATETESTS += \ animation \ qqmlecmascript \ qqmlcontext \ + qqmldelegatemodel \ qqmlexpression \ qqmlglobal \ qqmllanguage \ diff --git a/tests/auto/qml/qqmldelegatemodel/data/deleteRace.qml b/tests/auto/qml/qqmldelegatemodel/data/deleteRace.qml new file mode 100644 index 0000000000..23874970e7 --- /dev/null +++ b/tests/auto/qml/qqmldelegatemodel/data/deleteRace.qml @@ -0,0 +1,50 @@ +import QtQuick 2.15 +import QtQml.Models 2.15 + +Item { + DelegateModel { + id: delegateModel + model: ListModel { + id: sourceModel + + ListElement { title: "foo" } + ListElement { title: "bar" } + + function clear() { + if (count > 0) + remove(0, count); + } + } + + groups: [ + DelegateModelGroup { name: "selectedItems" } + ] + + delegate: Text { + height: DelegateModel.inSelectedItems ? implicitHeight * 2 : implicitHeight + Component.onCompleted: { + if (index === 0) + DelegateModel.inSelectedItems = true; + } + } + + Component.onCompleted: { + items.create(0) + items.create(1) + } + } + + ListView { + anchors.fill: parent + model: delegateModel + } + + Timer { + running: true + interval: 10 + onTriggered: sourceModel.clear() + } + + property int count: delegateModel.items.count +} + diff --git a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp index 35f1e2c94d..f0afdb16ca 100644 --- a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp +++ b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp @@ -47,6 +47,7 @@ private slots: void filterOnGroup_removeWhenCompleted(); void qtbug_86017(); void contextAccessedByHandler(); + void deleteRace(); }; class AbstractItemModel : public QAbstractItemModel @@ -139,17 +140,6 @@ void tst_QQmlDelegateModel::valueWithoutCallingObjectFirst() QCOMPARE(model->variantValue(index, role), expectedValue); } -void tst_QQmlDelegateModel::filterOnGroup_removeWhenCompleted() -{ - QQuickView view(testFileUrl("removeFromGroup.qml")); - QCOMPARE(view.status(), QQuickView::Ready); - view.show(); - QQuickItem *root = view.rootObject(); - QVERIFY(root); - QQmlDelegateModel *model = root->findChild<QQmlDelegateModel*>(); - QVERIFY(model); - QTest::qWaitFor([=]{ return model->count() == 2; } ); - void tst_QQmlDelegateModel::qtbug_86017() { QQmlEngine engine; @@ -186,6 +176,17 @@ void tst_QQmlDelegateModel::contextAccessedByHandler() QVERIFY(root->property("works").toBool()); } +void tst_QQmlDelegateModel::deleteRace() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("deleteRace.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QTRY_COMPARE(o->property("count").toInt(), 2); + QTRY_COMPARE(o->property("count").toInt(), 0); +} + QTEST_MAIN(tst_QQmlDelegateModel) #include "tst_qqmldelegatemodel.moc" |
