aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2023-03-29 16:36:03 +0200
committerUlf Hermann <ulf.hermann@qt.io>2023-04-25 19:50:21 +0200
commitb299416894bcaf7965fd53c195636d84f0674255 (patch)
tree7249cc73df7a6c5a5b7a2d70786b724cde0c84ad
parenta235fb4ae68dcb887b64fcb60a97dfbae5381e79 (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.cpp23
-rw-r--r--tests/auto/qml/qml.pro1
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/deleteRace.qml50
-rw-r--r--tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp23
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"