QtWidgets: restore Qt 5 compatibility for save/restore state

Several classes in QWidget use QDataStream internally in order
to save and restore state. These QDataStream usages were not
versioned, meaning that if Qt changes the serialization for some
datatype, then the data saved between different Qt versions becomes
incompatible. Note that the save/restore API in question just produce
opaque blobs as QByteArrays -- the user has no control over the
QDataStream objects and thus versions.

Fix by version the usages.

In QHeaderView this has caused a regression because QBitArray *did*
change version between Qt 5 and 6. In general, using QDataStream without
explicit versioning is a mistake, so deploy the same fix elsewhere as
well.

Fixes: QTBUG-99487
Pick-to: 5.15 6.2 6.3
Change-Id: I82bb5c266f4e5dedc0887cbef855dccab1015e29
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: <doctor.whom@gmail.com>
This commit is contained in:
Giuseppe D'Angelo 2022-05-12 16:59:50 +02:00
parent cf3843a268
commit 07d80deeab
6 changed files with 42 additions and 5 deletions

View File

@ -418,6 +418,7 @@ QByteArray QFileDialog::saveState() const
int version = 4;
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_0);
stream << qint32(QFileDialogMagic);
stream << qint32(version);
@ -452,6 +453,7 @@ bool QFileDialog::restoreState(const QByteArray &state)
Q_D(QFileDialog);
QByteArray sd = state;
QDataStream stream(&sd, QIODevice::ReadOnly);
stream.setVersion(QDataStream::Qt_5_0);
if (stream.atEnd())
return false;
QStringList history;

View File

@ -1742,6 +1742,7 @@ QByteArray QHeaderView::saveState() const
Q_D(const QHeaderView);
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_0);
stream << QHeaderViewPrivate::VersionMarker;
stream << 0; // current version is 0
d->write(stream);
@ -1763,6 +1764,7 @@ bool QHeaderView::restoreState(const QByteArray &state)
return false;
QByteArray data = state;
QDataStream stream(&data, QIODevice::ReadOnly);
stream.setVersion(QDataStream::Qt_5_0);
int marker;
int ver;
stream >> marker;

View File

@ -1217,6 +1217,7 @@ QByteArray QMainWindow::saveState(int version) const
{
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_0);
stream << QMainWindowLayout::VersionMarker;
stream << version;
d_func()->layout->saveState(stream);
@ -1245,6 +1246,7 @@ bool QMainWindow::restoreState(const QByteArray &state, int version)
return false;
QByteArray sd = state;
QDataStream stream(&sd, QIODevice::ReadOnly);
stream.setVersion(QDataStream::Qt_5_0);
int marker, v;
stream >> marker;
stream >> v;

View File

@ -1164,10 +1164,12 @@ bool QMainWindowLayoutState::restoreState(QDataStream &_stream,
}
QDataStream ds(copy);
ds.setVersion(_stream.version());
if (!checkFormat(ds))
return false;
QDataStream stream(copy);
stream.setVersion(_stream.version());
while (!stream.atEnd()) {
uchar marker;

View File

@ -1650,6 +1650,7 @@ QByteArray QSplitter::saveState() const
int version = 1;
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_0);
stream << qint32(SplitterMagic);
stream << qint32(version);
@ -1691,6 +1692,7 @@ bool QSplitter::restoreState(const QByteArray &state)
int version = 1;
QByteArray sd = state;
QDataStream stream(&sd, QIODevice::ReadOnly);
stream.setVersion(QDataStream::Qt_5_0);
QList<int> list;
bool b;
qint32 i;

View File

@ -146,6 +146,7 @@ private slots:
void moveSectionAndReset();
void moveSectionAndRemove();
void saveRestore();
void QTBUG99487_saveRestoreQt5Compat();
void restoreToMoreColumns();
void restoreToMoreColumnsNoMovedColumns();
void restoreBeforeSetModel();
@ -1716,16 +1717,24 @@ static QByteArray savedState()
return h1.saveState();
}
void tst_QHeaderView::saveRestore()
// As generated by savedState()
static const QByteArray qt5SavedSate = QByteArrayLiteral("\x00\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x02\x01\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04\b\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00""d\x00\x00\x00\xD2\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00""d\x00\x00\x00\x00\x00\x00\x00\x84\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00""d\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00""d\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\xE8\x00\x00\x00\x00\x00\x00\x00\x00\x00");
enum class SaveRestoreOption
{
CheckGeneratedState,
DoNotCheckGeneratedState,
};
static void saveRestoreImpl(const QByteArray &state, SaveRestoreOption option)
{
QStandardItemModel m(4, 4);
const QByteArray s1 = savedState();
QHeaderView h2(Qt::Vertical);
QSignalSpy spy(&h2, &QHeaderView::sortIndicatorChanged);
h2.setModel(&m);
QVERIFY(h2.restoreState(s1));
QVERIFY(h2.restoreState(state));
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.at(0).at(0).toInt(), 2);
@ -1740,12 +1749,30 @@ void tst_QHeaderView::saveRestore()
QVERIFY(h2.isSectionHidden(3));
QCOMPARE(h2.hiddenSectionCount(), 1);
switch (option) {
case SaveRestoreOption::CheckGeneratedState:
{
QByteArray s2 = h2.saveState();
QCOMPARE(s1, s2);
QCOMPARE(state, s2);
break;
}
case SaveRestoreOption::DoNotCheckGeneratedState:
break;
};
QVERIFY(!h2.restoreState(QByteArrayLiteral("Garbage")));
}
void tst_QHeaderView::saveRestore()
{
saveRestoreImpl(savedState(), SaveRestoreOption::CheckGeneratedState);
}
void tst_QHeaderView::QTBUG99487_saveRestoreQt5Compat()
{
saveRestoreImpl(qt5SavedSate, SaveRestoreOption::DoNotCheckGeneratedState);
}
void tst_QHeaderView::restoreToMoreColumns()
{
// Restore state onto a model with more columns