From c343a0bb2a8f87758bf97241223224770e5c4687 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Tue, 22 Oct 2024 13:26:08 +0800 Subject: [PATCH] QQuickItemView: fix crash with zero-size SwipeView that uses Repeater When running the minimal example from the bug report, the order of events is: 1. deletables are iterated in QQuickRepeater::clear(), and deleteLater is called on the delegate item. 2. deletables are unparented. This causes Container (SwipeView) to be notified of the parent change and it removes the item. Part of this involves changing the currentIndex, since the removed item was current. 3. SwipeView's contentItem (ListView) has its currentIndex bound to the container's, so QQuickItemView::setCurrentIndex is called. 4. setCurrentIndex calls updateCurrent, which detects that the currentIndex (which is -1, because there was only one item) is out of range and so releases the item. This causes it to be added to unrequestedItems, even though it has been scheduled for deletion. This patch makes QQuickItemView detect that the item is going to be deleted and not add it to the list of deletables. Fixes: QTBUG-129622 Pick-to: 6.5 6.8 Change-Id: I999aedbdfafc61ff6d33eb6579331f470e9c1454 Reviewed-by: Richard Moe Gustavsen Reviewed-by: Fabian Kosmale --- src/quick/items/qquickitemview.cpp | 4 +- .../controls/data/tst_swipeview.qml | 59 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp index 19e5b7f..dabd563 100644 --- a/src/quick/items/qquickitemview.cpp +++ b/src/quick/items/qquickitemview.cpp @@ -2499,7 +2499,9 @@ bool QQuickItemViewPrivate::releaseItem(FxViewItem *item, QQmlInstanceModel::Reu // One case where this can happen is moving an item out of one ObjectModel and into another. QQuickItemPrivate::get(item->item)->setCulled(true); } - if (!isClearing) + // If deleteLater was called, the item isn't long for this world and so we shouldn't store references to it. + // This can happen when a Repeater is used to populate items in SwipeView's ListView contentItem. + if (!isClearing && !QObjectPrivate::get(item->item)->deleteLaterCalled) unrequestedItems.insert(item->item, model->indexOf(item->item, q)); } else if (flags & QQmlInstanceModel::Destroyed) { item->item->setParentItem(nullptr); diff --git a/tests/auto/quickcontrols/controls/data/tst_swipeview.qml b/tests/auto/quickcontrols/controls/data/tst_swipeview.qml index 3a7558c..a3dd16c 100644 --- a/tests/auto/quickcontrols/controls/data/tst_swipeview.qml +++ b/tests/auto/quickcontrols/controls/data/tst_swipeview.qml @@ -4,6 +4,7 @@ import QtQuick import QtTest import QtQuick.Controls +import QtQuick.Layouts TestCase { id: testCase @@ -760,4 +761,62 @@ TestCase { tryCompare(swipeListView, "contentX", swipeListView.width, 1000) compare(item2.x, swipeListView.width) } + + Component { + id: zeroSizeSwipeViewWithRepeatersComponent + + Item { + objectName: "rootItem" + anchors.fill: parent + + property alias swipeView: swipeView + property int d + + Timer { + interval: 2 + running: true + repeat: false + onTriggered: d = 2 + } + + SwipeView { + id: swipeView + contentItem.objectName: "swipeViewListView" + + Repeater { + objectName: "swipeViewContentItemRepeater" + model: [ + { + title: d + } + ] + + delegate: GridLayout { + objectName: "gridLayoutDelegate" + + Repeater { + id: repeater + objectName: "delegateRepeater" + model: d + delegate: Item { + objectName: "delegate" + index + + required property int index + } + } + } + } + } + } + } + + // QTBUG-129622 + function test_zeroSizeSwipeViewWithRepeaters() { + let root = createTemporaryObject(zeroSizeSwipeViewWithRepeatersComponent, testCase) + verify(root) + + let swipeView = root.swipeView + tryCompare(root, "d", 2) + // Shouldn't crash when the model is changed. + } } -- 2.47.0