Details
-
Bug
-
Resolution: Invalid
-
P1: Critical
-
None
-
5.10.1, 5.11.0 Beta 2
-
None
-
Visual Studio 2017
Description
We've recently migrated one of our older models to use the recently introduced recursiveFiltering functionality. Unfortunately we're seeing an assert
ASSERT failure in QVector<T>::remove: "index out of range"
On the line:
source_to_proxy.remove(start, delta_item_count);
where delta_item_count = -1, because source_to_proxy.size() == 0
if (end >= source_to_proxy.size()) end = source_to_proxy.size() - 1; int delta_item_count = end - start + 1;
We've actually seen this crash where no filtering takes place. i.e.
bool RecursiveFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { return true; }
However it requires a lot more model operations to trigger it. I've managed to create a small test case which exhibits the problem and attached it. It's not the cleanest / minimal example I was after, but it's fairly close. It has some interesting requirements, the mappings must be created before it will crash, in the example I've instatantiated the mappings by creating a tree view on the filter model. It then requires a a number of add / removes from the model before it'll crash (enough to corrupt the proxy mappings I believe). I've connected up a timer in main to cause the crash once the tree views have instantiated. In this particular test case both of the following are required, but in our main application we've only required the recursivefiltering enabled I believe.
setDynamicSortFilter(false); setRecursiveFilteringEnabled(true);
I believe there's some extra checking that recalculates the mappings if the sort filter is on that avoids the crash (or perhaps just delays it). The tree model has been pulled directly from Examples\Qt-5.11.0\widgets\itemviews\simpletreemodel, though it has two extra functions (void TreeItem::removeChild(int row) and TreeModel::CrashFilterModel()).
The key functions are however:
Crashing Code
void TreeModel::CrashFilterModel() { // Choose the first child of the root // In the dummy data this is "Getting Started" // It currently has two children. auto firstChildOfRoot = index(0, 0, QModelIndex()); // Make sure the model has some data in it if (firstChildOfRoot.isValid()) { auto* treeItemParent = static_cast<TreeItem*>(firstChildOfRoot.internalPointer()); QList<QVariant> dummyColumnData; dummyColumnData.append("First Column"); dummyColumnData.append("second column"); // Begin by inserting an element at position 2 (after the current children) // And then removing the element at position 0 beginInsertRows(firstChildOfRoot, 2, 2); treeItemParent->appendChild(new TreeItem(dummyColumnData, treeItemParent)); endInsertRows(); Q_ASSERT(treeItemParent->childCount() == 3); beginRemoveRows(firstChildOfRoot, 0, 0); treeItemParent->removeChild(0); endRemoveRows(); Q_ASSERT(treeItemParent->childCount() == 2); beginInsertRows(firstChildOfRoot, 2, 2); treeItemParent->appendChild(new TreeItem(dummyColumnData, treeItemParent)); endInsertRows(); Q_ASSERT(treeItemParent->childCount() == 3); beginRemoveRows(firstChildOfRoot, 0, 0); treeItemParent->removeChild(0); endRemoveRows(); Q_ASSERT(treeItemParent->childCount() == 2); beginInsertRows(firstChildOfRoot, 2, 2); treeItemParent->appendChild(new TreeItem(dummyColumnData, treeItemParent)); endInsertRows(); Q_ASSERT(treeItemParent->childCount() == 3); beginRemoveRows(firstChildOfRoot, 0, 0); treeItemParent->removeChild(0); endRemoveRows(); Q_ASSERT(treeItemParent->childCount() == 2); // Remove twice in a row, but remove the second item beginRemoveRows(firstChildOfRoot, 1, 1); treeItemParent->removeChild(1); // CRASH HAPPENS HERE endRemoveRows(); Q_ASSERT(treeItemParent->childCount() == 1); } }
Filtering
bool RecursiveFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { bool accepts = true; auto childIndex = sourceModel()->index(source_row, 0, source_parent); // Filter all items which have no children if ((childIndex.isValid()) && (sourceModel()->rowCount(childIndex) == 0)) { accepts = false; } // Filter all elements at row 0 if ((childIndex.isValid()) && ((childIndex.row() == 0))) { accepts = false; } return accepts; }
Initial Tree
Getting Started How to familiarize yourself with Qt Designer Launching Designer Running the Qt Designer application A Dialog Without Auto-Connect How to connect widgets without a naming scheme The User Interface How to interact with Qt Designer The User Interface How to interact with Qt Designer