diff options
| author | Denis Letov <den.letoff@gmail.com> | 2026-09-29 20:28:01 +0300 |
|---|---|---|
| committer | Denis Letov <den.letoff@gmail.com> | 2026-09-30 16:03:57 +0000 |
| commit | 549ecd1eee8d9c48dfccf6f5404ec1c4b0175f08 (patch) | |
| tree | 939ea8c496b13297bc42b30eb4ffb328dc47abd5 | |
| parent | 2fba5e4ba8b4c54c244500dcd880d98817be4f2c (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.cpp | 4 | ||||
| -rw-r--r-- | tests/auto/quick/qquicklistview/data/orphanedSectionContext.qml | 42 | ||||
| -rw-r--r-- | tests/auto/quick/qquicklistview/tst_qquicklistview.cpp | 44 |
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 §ion) { + // 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); |
