summarylogtreecommitdiffstats
path: root/plasma-crash-1.patch
blob: 90790bc05454ca612fae4add3681a1b54fccb5c0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
From 3bd0fd8f97e7a33a874929a383a42e6c710bfff3 Mon Sep 17 00:00:00 2001
From: Stephen Kelly <steveire@gmail.com>
Date: Sat, 17 Dec 2016 06:20:06 +0000
Subject: [PATCH] QSFPM: Fix handling of source model layout change

In sourceLayoutAboutToBeChanged the source model update is ignored if
the affected parents are filtered out anyway.  The same logic is
attempted in the sourceLayoutChanged slot, but there the early-return
logic is applied too late - the mapping is cleared before performing the
early-return.  Because pointers into the mapping are used in the
internalPointer of QModelIndexes in this class, persistent indexes used
later will segfault when attempting to dereference it.

Additionally, if a parent becomes invalid as a result of the
layoutChange, it would be filtered out by the condition in the loop,
resulting in a different result in the comparison of emptiness of the
parents container.

Fix that by persisting the parent's container, and performing the test
for early-return before clearing the mapping.

Task-number: QTBUG-47711
Task-number: QTBUG-32981
Change-Id: If45e8a1c97d39454160f52041bc9ae7e337dce97
Reviewed-by: David Faure <david.faure@kdab.com>
---
 src/corelib/itemmodels/qsortfilterproxymodel.cpp   |  31 ++---
 .../tst_qsortfilterproxymodel.cpp                  | 126 +++++++++++++++++++++
 2 files changed, 137 insertions(+), 20 deletions(-)

diff --git a/src/corelib/itemmodels/qsortfilterproxymodel.cpp b/src/corelib/itemmodels/qsortfilterproxymodel.cpp
index b0ddfa879d..333152138e 100644
--- a/src/corelib/itemmodels/qsortfilterproxymodel.cpp
+++ b/src/corelib/itemmodels/qsortfilterproxymodel.cpp
@@ -171,6 +171,7 @@ class QSortFilterProxyModelPrivate : public QAbstractProxyModelPrivate
     QRowsRemoval itemsBeingRemoved;
 
     QModelIndexPairList saved_persistent_indexes;
+    QList<QPersistentModelIndex> saved_layoutChange_parents;
 
     QHash<QModelIndex, Mapping *>::const_iterator create_mapping(
         const QModelIndex &source_parent) const;
@@ -1331,23 +1332,23 @@ void QSortFilterProxyModelPrivate::_q_sourceLayoutAboutToBeChanged(const QList<Q
     Q_UNUSED(hint); // We can't forward Hint because we might filter additional rows or columns
     saved_persistent_indexes.clear();
 
-    QList<QPersistentModelIndex> parents;
+    saved_layoutChange_parents.clear();
     for (const QPersistentModelIndex &parent : sourceParents) {
         if (!parent.isValid()) {
-            parents << QPersistentModelIndex();
+            saved_layoutChange_parents << QPersistentModelIndex();
             continue;
         }
         const QModelIndex mappedParent = q->mapFromSource(parent);
         // Might be filtered out.
         if (mappedParent.isValid())
-            parents << mappedParent;
+            saved_layoutChange_parents << mappedParent;
     }
 
     // All parents filtered out.
-    if (!sourceParents.isEmpty() && parents.isEmpty())
+    if (!sourceParents.isEmpty() && saved_layoutChange_parents.isEmpty())
         return;
 
-    emit q->layoutAboutToBeChanged(parents);
+    emit q->layoutAboutToBeChanged(saved_layoutChange_parents);
     if (persistent.indexes.isEmpty())
         return;
 
@@ -1359,6 +1360,9 @@ void QSortFilterProxyModelPrivate::_q_sourceLayoutChanged(const QList<QPersisten
     Q_Q(QSortFilterProxyModel);
     Q_UNUSED(hint); // We can't forward Hint because we might filter additional rows or columns
 
+    if (!sourceParents.isEmpty() && saved_layoutChange_parents.isEmpty())
+        return;
+
     // Optimize: We only actually have to clear the mapping related to the contents of
     // sourceParents, not everything.
     qDeleteAll(source_index_mapping);
@@ -1373,21 +1377,8 @@ void QSortFilterProxyModelPrivate::_q_sourceLayoutChanged(const QList<QPersisten
         source_index_mapping.clear();
     }
 
-    QList<QPersistentModelIndex> parents;
-    for (const QPersistentModelIndex &parent : sourceParents) {
-        if (!parent.isValid()) {
-            parents << QPersistentModelIndex();
-            continue;
-        }
-        const QModelIndex mappedParent = q->mapFromSource(parent);
-        if (mappedParent.isValid())
-            parents << mappedParent;
-    }
-
-    if (!sourceParents.isEmpty() && parents.isEmpty())
-        return;
-
-    emit q->layoutChanged(parents);
+    emit q->layoutChanged(saved_layoutChange_parents);
+    saved_layoutChange_parents.clear();
 }
 
 void QSortFilterProxyModelPrivate::_q_sourceRowsAboutToBeInserted(
diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp b/tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp
index 38e3c6890d..6b98d9f4a3 100644
--- a/tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp
+++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel/tst_qsortfilterproxymodel.cpp
@@ -145,6 +145,8 @@ private slots:
     void canDropMimeData();
     void filterHint();
 
+    void sourceLayoutChangeLeavesValidPersistentIndexes();
+
 protected:
     void buildHierarchy(const QStringList &data, QAbstractItemModel *model);
     void checkHierarchy(const QStringList &data, const QAbstractItemModel *model);
@@ -4181,5 +4183,129 @@ void tst_QSortFilterProxyModel::filterHint()
              QAbstractItemModel::NoLayoutChangeHint);
 }
 
+/**
+
+  Creates a model where each item has one child, to a set depth,
+  and the last item has no children.  For a model created with
+  setDepth(4):
+
+    - 1
+    - - 2
+    - - - 3
+    - - - - 4
+*/
+class StepTreeModel : public QAbstractItemModel
+{
+    Q_OBJECT
+public:
+    StepTreeModel(QObject * parent = 0)
+        : QAbstractItemModel(parent), m_depth(0) {}
+
+    int columnCount(const QModelIndex& = QModelIndex()) const override { return 1; }
+
+    int rowCount(const QModelIndex& parent = QModelIndex()) const override
+    {
+        quintptr parentId = (parent.isValid()) ? parent.internalId() : 0;
+        return (parentId < m_depth) ? 1 : 0;
+    }
+
+    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override
+    {
+        if (role != Qt::DisplayRole)
+            return QVariant();
+
+        return QString::number(index.internalId());
+    }
+
+    QModelIndex index(int, int, const QModelIndex& parent = QModelIndex()) const override
+    {
+        quintptr parentId = (parent.isValid()) ? parent.internalId() : 0;
+        if (parentId >= m_depth)
+            return QModelIndex();
+
+        return createIndex(0, 0, parentId + 1);
+    }
+
+    QModelIndex parent(const QModelIndex& index) const override
+    {
+        if (index.internalId() == 0)
+            return QModelIndex();
+
+        return createIndex(0, 0, index.internalId() - 1);
+    }
+
+    void setDepth(quintptr depth)
+    {
+        int parentIdWithLayoutChange = (m_depth < depth) ? m_depth : depth;
+
+        QList<QPersistentModelIndex> parentsOfLayoutChange;
+        parentsOfLayoutChange.push_back(createIndex(0, 0, parentIdWithLayoutChange));
+
+        layoutAboutToBeChanged(parentsOfLayoutChange);
+
+        auto existing = persistentIndexList();
+
+        QList<QModelIndex> updated;
+
+        for (auto idx : existing) {
+            if (indexDepth(idx) <= depth)
+                updated.push_back(idx);
+            else
+                updated.push_back({});
+        }
+
+        m_depth = depth;
+
+        changePersistentIndexList(existing, updated);
+
+        layoutChanged(parentsOfLayoutChange);
+    }
+
+private:
+    static quintptr indexDepth(QModelIndex const& index)
+    {
+        return (index.isValid()) ? 1 + indexDepth(index.parent()) : 0;
+    }
+
+private:
+    quintptr m_depth;
+};
+
+void tst_QSortFilterProxyModel::sourceLayoutChangeLeavesValidPersistentIndexes()
+{
+    StepTreeModel model;
+    Q_SET_OBJECT_NAME(model);
+    model.setDepth(4);
+
+    QSortFilterProxyModel proxy1;
+    proxy1.setSourceModel(&model);
+    Q_SET_OBJECT_NAME(proxy1);
+
+    proxy1.setFilterRegExp("1|2");
+
+    // The current state of things:
+    //  model         proxy
+    //   - 1           - 1
+    //   - - 2         - - 2
+    //   - - - 3
+    //   - - - - 4
+
+    // The setDepth call below removes '4' with a layoutChanged call.
+    // Because the proxy filters that out anyway, the proxy doesn't need
+    // to emit any signals or update persistent indexes.
+
+    QPersistentModelIndex persistentIndex = proxy1.index(0, 0, proxy1.index(0, 0));
+
+    model.setDepth(3);
+
+    // Calling parent() causes the internalPointer to be used.
+    // Before fixing QTBUG-47711, that could be a dangling pointer.
+    // The use of qDebug here makes sufficient use of the heap to
+    // cause corruption at runtime with normal use on linux (before
+    // the fix). valgrind confirms the fix.
+    qDebug() << persistentIndex.parent();
+    QVERIFY(persistentIndex.parent().isValid());
+}
+
 QTEST_MAIN(tst_QSortFilterProxyModel)
 #include "tst_qsortfilterproxymodel.moc"