QFormLayout: don't access out-of-bounds layout data

When rows are hidden (implicitly or explicitly), then their layout data
does not get fully updated. If rows get hidden after the layout data has
been calculated once, then we must make sure that their indices are
reset. Otherwise we might access array indices that are out of bounds
when the layout data structure gets resized to fit only visible rows.

For good measure, skip entirely over hidden rows when accessing the
layout data when arranging the widget.

Fixes: QTBUG-109237
Change-Id: I4d6943b6a110edb61f60ce78d31f0fc64b5cc03d
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
(cherry picked from commit c3a5fe2fd7a1b8a6b6133c938ffe6b3f30181bf0)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Volker Hilsheimer 2023-01-12 16:25:31 +01:00 committed by Qt Cherry-pick Bot
parent 934735822a
commit 9bbf761373
2 changed files with 60 additions and 2 deletions

View File

@ -480,6 +480,7 @@ void QFormLayoutPrivate::recalcHFW(int w)
void QFormLayoutPrivate::setupHfwLayoutData()
{
Q_Q(QFormLayout);
// setupVerticalLayoutData must be called before this
// setupHorizontalLayoutData must also be called before this
// copies non hfw data into hfw
@ -504,6 +505,10 @@ void QFormLayoutPrivate::setupHfwLayoutData()
QFormLayoutItem *label = m_matrix(i, 0);
QFormLayoutItem *field = m_matrix(i, 1);
// ignore rows with only hidden items
if (!q->isRowVisible(i))
continue;
if (label && label->vLayoutIndex > -1) {
if (label->isHfw) {
// We don't check sideBySide here, since a label is only
@ -681,9 +686,15 @@ void QFormLayoutPrivate::setupVerticalLayoutData(int width)
QFormLayoutItem *label = m_matrix(i, 0);
QFormLayoutItem *field = m_matrix(i, 1);
// Totally ignore empty rows or rows with only hidden items
if (!q->isRowVisible(i))
// Ignore empty rows or rows with only hidden items,
// and invalidate their position in the layout.
if (!q->isRowVisible(i)) {
if (label)
label->vLayoutIndex = -1;
if (field)
field->vLayoutIndex = -1;
continue;
}
QSize min1;
QSize min2;
@ -2190,6 +2201,9 @@ void QFormLayoutPrivate::arrangeWidgets(const QList<QLayoutStruct> &layouts, QRe
QFormLayoutItem *label = m_matrix(i, 0);
QFormLayoutItem *field = m_matrix(i, 1);
if (!q->isRowVisible(i))
continue;
if (label && label->vLayoutIndex > -1) {
int height = layouts.at(label->vLayoutIndex).size;
if ((label->expandingDirections() & Qt::Vertical) == 0) {

View File

@ -112,6 +112,7 @@ private slots:
void setLayout();
void hideShowRow();
void showWithHiddenRow();
void hiddenRowAndStretch();
/*
QLayoutItem *itemAt(int row, ItemRole role) const;
@ -1253,6 +1254,49 @@ void tst_QFormLayout::showWithHiddenRow()
topLevel.show();
}
/*
Test that hiding rows does not leave outdated layout data behind
in hidden items that results in out-of-bounds array access. See
QTBUG-109237.
*/
void tst_QFormLayout::hiddenRowAndStretch()
{
QWidget topLevel;
QFormLayout layout;
layout.setRowWrapPolicy(QFormLayout::WrapAllRows);
// We need our own stretcher item so that QFormLayout doesn't insert
// it's own, as that would grow the size of the layout data array again.
QSpacerItem *stretch = new QSpacerItem(100, 100, QSizePolicy::Expanding, QSizePolicy::Expanding);
layout.setItem(0, QFormLayout::FieldRole, stretch);
QLabel *lastLabel = nullptr;
QLineEdit *lastField = nullptr;
for (int row = 1; row < 4; ++row) {
QLabel *label = new QLabel(QString("Label %1").arg(row));
label->setWordWrap(true);
QLineEdit *field = new QLineEdit;
layout.setWidget(row, QFormLayout::LabelRole, label);
layout.setWidget(row, QFormLayout::FieldRole, field);
if (row == 3) {
lastLabel = label;
lastField = field;
}
}
Q_ASSERT(lastLabel);
Q_ASSERT(lastField);
topLevel.setLayout(&layout);
topLevel.sizeHint();
lastLabel->setVisible(false);
lastField->setVisible(false);
// should not assert here
topLevel.show();
}
void tst_QFormLayout::itemAt()
{
QWidget topLevel;