aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDenis Letov <den.letoff@gmail.com>2026-09-29 20:28:01 +0300
committerDenis Letov <den.letoff@gmail.com>2026-09-30 16:03:57 +0000
commit549ecd1eee8d9c48dfccf6f5404ec1c4b0175f08 (patch)
tree939ea8c496b13297bc42b30eb4ffb328dc47abd5
parent2fba5e4ba8b4c54c244500dcd880d98817be4f2c (diff)
Quick: Fix crash when a section item outlives its context
A ListView section item can outlive its creation context, for example when the model is reset while the view is being laid out, or when the view is being torn down while a contentY animation is still running. The next call to setSectionHelper() then dereferences a null QQmlContext: QQmlContextData::get(nullptr) reads QQmlContextPrivate at offset 8, which crashes with an access violation. Skip the update when the context is null The section may keep its outdated label until it is released and a new section item is created with a valid context Fixes: QTBUG-130819 Pick-to: 6.8 6.12 Change-Id: I8be5b3073fdceda878014cf5c1cf136778691270 Reviewed-by: SanthoshKumar Selvaraj <santhosh.kumar.selvaraj@qt.io>
-rw-r--r--src/quick/items/qquicklistview.cpp4
-rw-r--r--tests/auto/quick/qquicklistview/data/orphanedSectionContext.qml42
-rw-r--r--tests/auto/quick/qquicklistview/tst_qquicklistview.cpp44
3 files changed, 90 insertions, 0 deletions
diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp
index 29eec9cb8c..1f7943af7c 100644
--- a/src/quick/items/qquicklistview.cpp
+++ b/src/quick/items/qquicklistview.cpp
@@ -2113,6 +2113,10 @@ bool QQuickListViewPrivate::flick(AxisData &data, qreal minExtent, qreal maxExte
void QQuickListViewPrivate::setSectionHelper(QQmlContext *context, QQuickItem *sectionItem, const QString &section)
{
+ // The section item can outlive its context
+ // Skip the update rather than dereference the null context
+ if (!context)
+ return;
if (!QQmlContextData::get(context)->isInternal() && context->contextProperty(QLatin1String("section")).isValid())
context->setContextProperty(QLatin1String("section"), section);
else
diff --git a/tests/auto/quick/qquicklistview/data/orphanedSectionContext.qml b/tests/auto/quick/qquicklistview/data/orphanedSectionContext.qml
new file mode 100644
index 0000000000..e74cea70a3
--- /dev/null
+++ b/tests/auto/quick/qquicklistview/data/orphanedSectionContext.qml
@@ -0,0 +1,42 @@
+import QtQuick
+
+ListView {
+ id: listView
+
+ width: 320
+ height: 480
+
+ model: ListModel {
+ id: listModel
+ Component.onCompleted: {
+ for (let i = 0; i < 50; ++i)
+ listModel.append({ name: "item " + i, sectionStr: "section " + (i % 5) })
+ }
+ }
+
+ property int sectionCount: 0
+
+ section.property: "sectionStr"
+ section.criteria: ViewSection.FullString
+ section.labelPositioning: ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart
+
+ // The section delegate is bound, so section items reuse the ListView's own
+ // context (see tst_QQuickListView::orphanedSectionContext()).
+ section.delegate: Rectangle {
+ width: listView.width
+ height: 30
+ color: "#E0E1D8"
+ Text {
+ anchors.centerIn: parent
+ font.pixelSize: 12
+ text: section
+ }
+ Component.onCompleted: ++listView.sectionCount
+ }
+
+ delegate: Item {
+ width: listView.width
+ height: 40
+ Text { text: name }
+ }
+}
diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
index c30f5e5660..fbf482e3d6 100644
--- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
+++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
@@ -126,6 +126,7 @@ private slots:
void sectionsItemInsertion();
void sectionsFocusChain();
void removeSectionsOnNonvisibleItems();
+ void orphanedSectionContext();
void cacheBuffer();
void positionViewAtBeginningEnd();
void positionViewAtBeginningWithSections_data();
@@ -2887,6 +2888,49 @@ void tst_QQuickListView::removeSectionsOnNonvisibleItems()
verifySectionData(listView, ++verifySectionId);
}
+void tst_QQuickListView::orphanedSectionContext()
+{
+ // QTBUG-130819: when the section delegate is bound, section items reuse
+ // the ListView's own context. If that context's parent context is
+ // destroyed, the section items are left with a null parent context, and
+ // setSectionHelper() must not dereference it.
+ QQmlEngine engine;
+ QQmlContext *outerContext = new QQmlContext(engine.rootContext());
+ QQmlContext *innerContext = new QQmlContext(outerContext);
+
+ QQmlComponent component(&engine, testFileUrl("orphanedSectionContext.qml"));
+ QScopedPointer<QObject> root(component.create(innerContext));
+ QVERIFY2(root, qPrintable(component.errorString()));
+ auto *listView = qobject_cast<QQuickListView *>(root.data());
+ QVERIFY(listView != nullptr);
+
+ QQuickWindow window;
+ window.resize(listView->width(), listView->height());
+ listView->setParentItem(window.contentItem());
+ window.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&window));
+
+ QTRY_COMPARE_GT(listView->property("sectionCount").toInt(), 0);
+
+ delete outerContext;
+
+ // Any relayout after the context was destroyed calls setSectionHelper()
+ // with a null parent context.
+ QQuickItem *firstSection = nullptr;
+ const QList<QQuickItem *> children = listView->contentItem()->childItems();
+ for (QQuickItem *child : children) {
+ if (qFuzzyCompare(child->y(), qreal(70.0)) && qFuzzyCompare(child->height(), qreal(30.0))
+ && child->isVisible()) {
+ firstSection = child;
+ break;
+ }
+ }
+ QVERIFY(firstSection != nullptr);
+
+ listView->setContentY(800);
+ QTRY_COMPARE(firstSection->isVisible(), false);
+}
+
void tst_QQuickListView::currentIndex_delayedItemCreation()
{
QFETCH(bool, setCurrentToZero);