diff options
| author | Mitch Curtis <mitch.curtis@qt.io> | 2026-09-19 12:19:56 +0800 |
|---|---|---|
| committer | Mitch Curtis <mitch.curtis@qt.io> | 2026-09-26 00:40:10 +0000 |
| commit | 9b1f087a9d2c79aa9bbcc94717394fc04b48bcfb (patch) | |
| tree | f3a81fb6a27f1b19acb2c86863786486e19331bc | |
| parent | 1171fd3c023c6b777ed567d32a159c14ba22b5e5 (diff) | |
QQmlSortFilterProxyModel: fix assert when queried without a sourceModel
rowCount() and columnCount() guarded against a missing source model
with !d->model. Since 74eeda555f569b6ddd2f5f0937fbff80591db143 ("Use
sourceModel property in the QML SFPM instead of model"), that is
QAbstractProxyModelPrivate::model, which holds
QAbstractItemModelPrivate::staticEmptyModel() rather than nullptr when
no source is set, so the guard never triggered. create_mapping() checks
sourceModel(), which does return nullptr for the empty model, and
asserts.
index(), headerData(), setHeaderData(), insertRows(), insertColumns(),
removeRows() and removeColumns() had no guard at all, so they hit the
same assert.
Check sourceModel() in all of them. Add tests for querying a proxy
that has no source, and for a proxy whose source is another proxy that
has no source of its own.
Fixes: QTBUG-150555
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Change-Id: Ia05a32337e718389f20d7be90d381883252342d5
Reviewed-by: SanthoshKumar Selvaraj <santhosh.kumar.selvaraj@qt.io>
3 files changed, 75 insertions, 7 deletions
diff --git a/src/qmlmodels/sfpm/qqmlsortfilterproxymodel.cpp b/src/qmlmodels/sfpm/qqmlsortfilterproxymodel.cpp index 9988766aef..aa7e662192 100644 --- a/src/qmlmodels/sfpm/qqmlsortfilterproxymodel.cpp +++ b/src/qmlmodels/sfpm/qqmlsortfilterproxymodel.cpp @@ -446,7 +446,7 @@ QItemSelection QQmlSortFilterProxyModel::mapSelectionFromSource(const QItemSelec QModelIndex QQmlSortFilterProxyModel::index(int row, int column, const QModelIndex &parent) const { Q_D(const QQmlSortFilterProxyModel); - if (row < 0 || column < 0) + if (row < 0 || column < 0 || !sourceModel()) return QModelIndex(); QModelIndex source_parent = mapToSource(parent); // parent is already mapped at this point @@ -510,7 +510,7 @@ bool QQmlSortFilterProxyModel::hasChildren(const QModelIndex &parent) const int QQmlSortFilterProxyModel::columnCount(const QModelIndex &parent) const { Q_D(const QQmlSortFilterProxyModel); - if (!d->model) + if (!sourceModel()) return 0; QModelIndex source_parent = mapToSource(parent); if (parent.isValid() && !source_parent.isValid()) @@ -522,7 +522,7 @@ int QQmlSortFilterProxyModel::columnCount(const QModelIndex &parent) const int QQmlSortFilterProxyModel::rowCount(const QModelIndex &parent) const { Q_D(const QQmlSortFilterProxyModel); - if (!d->model) + if (!sourceModel()) return 0; QModelIndex source_parent = mapToSource(parent); if (parent.isValid() && !source_parent.isValid()) @@ -555,6 +555,8 @@ bool QQmlSortFilterProxyModel::setData(const QModelIndex &index, const QVariant QVariant QQmlSortFilterProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_D(const QQmlSortFilterProxyModel); + if (!sourceModel()) + return QVariant(); QSortFilterProxyModelHelper::IndexMap::const_iterator it = d->create_mapping(QModelIndex()); if (it.value()->source_rows.size() * it.value()->source_columns.size() > 0) return QAbstractProxyModel::headerData(section, orientation, role); @@ -578,6 +580,8 @@ bool QQmlSortFilterProxyModel::setHeaderData(int section, Qt::Orientation orient const QVariant &value, int role) { Q_D(QQmlSortFilterProxyModel); + if (!sourceModel()) + return false; QSortFilterProxyModelHelper::IndexMap::const_iterator it = d->create_mapping(QModelIndex()); if (it.value()->source_rows.size() * it.value()->source_columns.size() > 0) return QAbstractProxyModel::setHeaderData(section, orientation, value, role); @@ -600,7 +604,7 @@ bool QQmlSortFilterProxyModel::setHeaderData(int section, Qt::Orientation orient bool QQmlSortFilterProxyModel::insertRows(int row, int count, const QModelIndex &parent) { Q_D(QQmlSortFilterProxyModel); - if (row < 0 || count <= 0) + if (row < 0 || count <= 0 || !sourceModel()) return false; QModelIndex source_parent = mapToSource(parent); if (parent.isValid() && !source_parent.isValid()) @@ -620,7 +624,7 @@ bool QQmlSortFilterProxyModel::insertRows(int row, int count, const QModelIndex bool QQmlSortFilterProxyModel::insertColumns(int column, int count, const QModelIndex &parent) { Q_D(QQmlSortFilterProxyModel); - if (column < 0|| count <= 0) + if (column < 0 || count <= 0 || !sourceModel()) return false; QModelIndex source_parent = mapToSource(parent); if (parent.isValid() && !source_parent.isValid()) @@ -640,7 +644,7 @@ bool QQmlSortFilterProxyModel::insertColumns(int column, int count, const QModel bool QQmlSortFilterProxyModel::removeRows(int row, int count, const QModelIndex &parent) { Q_D(QQmlSortFilterProxyModel); - if (row < 0 || count <= 0) + if (row < 0 || count <= 0 || !sourceModel()) return false; QModelIndex source_parent = mapToSource(parent); if (parent.isValid() && !source_parent.isValid()) @@ -682,7 +686,7 @@ bool QQmlSortFilterProxyModel::removeRows(int row, int count, const QModelIndex bool QQmlSortFilterProxyModel::removeColumns(int column, int count, const QModelIndex &parent) { Q_D(QQmlSortFilterProxyModel); - if (column < 0 || count <= 0) + if (column < 0 || count <= 0 || !sourceModel()) return false; QModelIndex source_parent = mapToSource(parent); if (parent.isValid() && !source_parent.isValid()) diff --git a/tests/auto/qml/qqmlsortfilterproxymodel/data/sfpmProxyChain.qml b/tests/auto/qml/qqmlsortfilterproxymodel/data/sfpmProxyChain.qml new file mode 100644 index 0000000000..445aef2e60 --- /dev/null +++ b/tests/auto/qml/qqmlsortfilterproxymodel/data/sfpmProxyChain.qml @@ -0,0 +1,11 @@ +import QtQml + +QtObject { + // The inner proxy deliberately has no source model. + property SortFilterProxyModel innerProxyModel: SortFilterProxyModel {} + + property SortFilterProxyModel outerProxyModel: SortFilterProxyModel { + sourceModel: innerProxyModel + sorters: RoleSorter { roleName: "display" } + } +} diff --git a/tests/auto/qml/qqmlsortfilterproxymodel/tst_qqmlsortfilterproxymodel.cpp b/tests/auto/qml/qqmlsortfilterproxymodel/tst_qqmlsortfilterproxymodel.cpp index 9f73c0881e..b22996d5b9 100644 --- a/tests/auto/qml/qqmlsortfilterproxymodel/tst_qqmlsortfilterproxymodel.cpp +++ b/tests/auto/qml/qqmlsortfilterproxymodel/tst_qqmlsortfilterproxymodel.cpp @@ -58,6 +58,8 @@ private slots: void primarySorter(); void primaryColumnWithoutSourceModel(); + void queriesWithoutSourceModel(); + void proxyOfProxyWithoutSourceModel(); void anyOfFilter(); void allOfFilter(); @@ -1175,6 +1177,57 @@ void tst_QQmlSortFilterProxyModel::primaryColumnWithoutSourceModel() sfpmModel->setPrimarySortColumn(1); } +void tst_QQmlSortFilterProxyModel::queriesWithoutSourceModel() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("sfpmCommon.qml")); + QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8()); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + auto *sfpmModel = object->property("sfpmProxyModel").value<QQmlSortFilterProxyModel *>(); + QVERIFY(sfpmModel); + QVERIFY(!sfpmModel->sourceModel()); + + // These used to assert in create_mapping() because no source model was set. + QCOMPARE(sfpmModel->rowCount(), 0); + QCOMPARE(sfpmModel->columnCount(), 0); + QVERIFY(!sfpmModel->index(0, 0).isValid()); + QVERIFY(!sfpmModel->headerData(0, Qt::Horizontal).isValid()); + QVERIFY(!sfpmModel->setHeaderData(0, Qt::Horizontal, QStringLiteral("header"))); + QVERIFY(!sfpmModel->insertRows(0, 1)); + QVERIFY(!sfpmModel->insertColumns(0, 1)); + QVERIFY(!sfpmModel->removeRows(0, 1)); + QVERIFY(!sfpmModel->removeColumns(0, 1)); +} + +void tst_QQmlSortFilterProxyModel::proxyOfProxyWithoutSourceModel() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("sfpmProxyChain.qml")); + QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8()); + // Setting the outer proxy's source used to assert in create_mapping(), + // because the inner proxy has no source model of its own. + QScopedPointer<QObject> object(component.create()); + QVERIFY2(!object.isNull(), qPrintable(component.errorString())); + + auto *innerProxyModel = object->property("innerProxyModel").value<QQmlSortFilterProxyModel *>(); + QVERIFY(innerProxyModel); + QVERIFY(!innerProxyModel->sourceModel()); + auto *outerProxyModel = object->property("outerProxyModel").value<QQmlSortFilterProxyModel *>(); + QVERIFY(outerProxyModel); + QCOMPARE(outerProxyModel->sourceModel(), innerProxyModel); + QCOMPARE(outerProxyModel->rowCount(), 0); + + // Once the inner proxy gets a source, its rows should show up in the outer one. + QStandardItemModel sourceModel; + for (int rowIndex = 0; rowIndex < 3; ++rowIndex) + sourceModel.appendRow(new QStandardItem(QStringLiteral("item"))); + innerProxyModel->setSourceModel(&sourceModel); + QCOMPARE(innerProxyModel->rowCount(), 3); + QCOMPARE(outerProxyModel->rowCount(), 3); +} + void tst_QQmlSortFilterProxyModel::anyOfFilter() { QQmlEngine engine; |
