aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMitch Curtis <mitch.curtis@qt.io>2026-09-19 12:19:56 +0800
committerMitch Curtis <mitch.curtis@qt.io>2026-09-26 00:40:10 +0000
commit9b1f087a9d2c79aa9bbcc94717394fc04b48bcfb (patch)
treef3a81fb6a27f1b19acb2c86863786486e19331bc
parent1171fd3c023c6b777ed567d32a159c14ba22b5e5 (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>
-rw-r--r--src/qmlmodels/sfpm/qqmlsortfilterproxymodel.cpp18
-rw-r--r--tests/auto/qml/qqmlsortfilterproxymodel/data/sfpmProxyChain.qml11
-rw-r--r--tests/auto/qml/qqmlsortfilterproxymodel/tst_qqmlsortfilterproxymodel.cpp53
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;