Fix crash when invalidating a QSortFilterProxyModel
authorStephen Kelly <stephen.kelly@kdab.com>
Thu, 13 Sep 2012 13:23:15 +0000 (15:23 +0200)
committerQt by Nokia <qt-info@nokia.com>
Thu, 13 Sep 2012 14:20:11 +0000 (16:20 +0200)
Task-number: QTBUG-27122

Change-Id: Ibca46b88442f4f92422d9b3182e4bbf25716a07f
Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
src/corelib/itemmodels/qsortfilterproxymodel.cpp
tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp

index 53cea5e..e3cd2f5 100644 (file)
@@ -1052,21 +1052,31 @@ void QSortFilterProxyModelPrivate::filter_changed(const QModelIndex &source_pare
 
     // We need to iterate over a copy of m->mapped_children because otherwise it may be changed by other code, invalidating
     // the iterator it2.
-    // The m->mapped_children vector can be appended to when this function recurses for child indexes.
-    // The handle_filter_changed implementation can cause source_parent.parent() to be called, which will create
-    // a mapping (and do appending) while we are invalidating the filter.
-    QVector<QModelIndex> mappedChildren = m->mapped_children;
-    QVector<QModelIndex>::iterator it2 = mappedChildren.end();
-    while (it2 != mappedChildren.begin()) {
-        --it2;
-        const QModelIndex source_child_index = *it2;
+    // The m->mapped_children vector can be appended to with indexes which are no longer filtered
+    // out (in create_mapping) when this function recurses for child indexes.
+    const QVector<QModelIndex> mappedChildren = m->mapped_children;
+    QVector<int> indexesToRemove;
+    for (int i = 0; i < mappedChildren.size(); ++i) {
+        const QModelIndex source_child_index = mappedChildren.at(i);
         if (rows_removed.contains(source_child_index.row()) || columns_removed.contains(source_child_index.column())) {
-            it2 = mappedChildren.erase(it2);
+            indexesToRemove.push_back(i);
             remove_from_mapping(source_child_index);
         } else {
             filter_changed(source_child_index);
         }
     }
+    QVector<int>::const_iterator removeIt = indexesToRemove.constEnd();
+    const QVector<int>::const_iterator removeBegin = indexesToRemove.constBegin();
+
+    // We can't just remove these items from mappedChildren while iterating above and then
+    // do something like m->mapped_children = mappedChildren, because mapped_children might
+    // be appended to in create_mapping, and we would lose those new items.
+    // Because they are always appended in create_mapping, we can still remove them by
+    // position here.
+    while (removeIt != removeBegin) {
+        --removeIt;
+        m->mapped_children.remove(*removeIt);
+    }
 }
 
 /*!
index 7f141bd..41188bc 100644 (file)
@@ -146,6 +146,7 @@ private slots:
     void moveSourceRows();
 
     void hierarchyFilterInvalidation();
+    void simpleFilterInvalidation();
 
 protected:
     void buildHierarchy(const QStringList &data, QAbstractItemModel *model);
@@ -3582,6 +3583,64 @@ void tst_QSortFilterProxyModel::hierarchyFilterInvalidation()
     proxy.setMode(true);
 }
 
+class FilterProxy2 : public QSortFilterProxyModel
+{
+    Q_OBJECT
+public:
+    FilterProxy2(QObject *parent = 0)
+      : QSortFilterProxyModel(parent),
+        mode(false)
+    {
+
+    }
+
+public slots:
+    void setMode(bool on)
+    {
+        mode = on;
+        invalidateFilter();
+    }
+
+protected:
+    virtual bool filterAcceptsRow ( int source_row, const QModelIndex & source_parent ) const
+    {
+        if (source_parent.isValid()) {
+            return true;
+        } else {
+            if (0 == source_row) {
+                return true;
+            } else {
+                return !mode;
+            }
+        }
+    }
+
+private:
+    bool mode;
+};
+
+void tst_QSortFilterProxyModel::simpleFilterInvalidation()
+{
+    QStandardItemModel model;
+    for (int i = 0; i < 2; ++i) {
+        QStandardItem *child = new QStandardItem(QString("Row %1").arg(i));
+        child->appendRow(new QStandardItem("child"));
+        model.appendRow(child);
+    }
+
+    FilterProxy2 proxy;
+    proxy.setSourceModel(&model);
+
+    QTreeView view;
+    view.setModel(&proxy);
+
+    view.show();
+    QTest::qWaitForWindowExposed(&view);
+
+    proxy.setMode(true);
+    model.insertRow(0, new QStandardItem("extra"));
+}
+
 
 QTEST_MAIN(tst_QSortFilterProxyModel)
 #include "tst_qsortfilterproxymodel.moc"