QDataStream: use SizeLimitExceeded status in write operations

Set this status when the stream tries to write more data than the
current serialization format supports.

Update the methods that write containers to return early if they fail
to write the container size, and do not try to serialize the elements.

Convert the manual tst_manualqdatastream test into a data-driven
test, allowing us to specify various stream versions. Adjust the test
code to check that the SizeLimitExceeded status is set when the
stream version is <= Qt_6_6.

Amends fd48ce0b73c74dafd5db27bc1f2752ef665df7ef

Found in 6.7 API review

Pick-to: 6.7
Change-Id: If4c62ea53ac9bccd423f00f0f03afd6ba6bdc4f5
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Ivan Solovev 2024-01-24 17:30:58 +01:00
parent 0ed34d1992
commit dd514160ce
4 changed files with 60 additions and 22 deletions

View File

@ -1397,8 +1397,8 @@ QDataStream &QDataStream::writeBytes(const char *s, qint64 len)
return *this; return *this;
} }
CHECK_STREAM_WRITE_PRECOND(*this) CHECK_STREAM_WRITE_PRECOND(*this)
writeQSizeType(*this, len); // write length specifier // Write length then, if any, content
if (len > 0) if (writeQSizeType(*this, len) && len > 0)
writeRawData(s, len); writeRawData(s, len);
return *this; return *this;
} }

View File

@ -212,7 +212,7 @@ private:
#endif #endif
qint64 readBlock(char *data, qint64 len); qint64 readBlock(char *data, qint64 len);
static inline qint64 readQSizeType(QDataStream &s); static inline qint64 readQSizeType(QDataStream &s);
static inline void writeQSizeType(QDataStream &s, qint64 value); static inline bool writeQSizeType(QDataStream &s, qint64 value);
enum class QDataStreamSizes : quint32 { NullCode = 0xffffffffu, ExtendedSize = 0xfffffffeu }; enum class QDataStreamSizes : quint32 { NullCode = 0xffffffffu, ExtendedSize = 0xfffffffeu };
friend class QtPrivate::StreamStateSaver; friend class QtPrivate::StreamStateSaver;
@ -339,7 +339,8 @@ QDataStream &readAssociativeContainer(QDataStream &s, Container &c)
template <typename Container> template <typename Container>
QDataStream &writeSequentialContainer(QDataStream &s, const Container &c) QDataStream &writeSequentialContainer(QDataStream &s, const Container &c)
{ {
QDataStream::writeQSizeType(s, c.size()); if (!QDataStream::writeQSizeType(s, c.size()))
return s;
for (const typename Container::value_type &t : c) for (const typename Container::value_type &t : c)
s << t; s << t;
@ -349,7 +350,8 @@ QDataStream &writeSequentialContainer(QDataStream &s, const Container &c)
template <typename Container> template <typename Container>
QDataStream &writeAssociativeContainer(QDataStream &s, const Container &c) QDataStream &writeAssociativeContainer(QDataStream &s, const Container &c)
{ {
QDataStream::writeQSizeType(s, c.size()); if (!QDataStream::writeQSizeType(s, c.size()))
return s;
auto it = c.constBegin(); auto it = c.constBegin();
auto end = c.constEnd(); auto end = c.constEnd();
while (it != end) { while (it != end) {
@ -363,7 +365,8 @@ QDataStream &writeAssociativeContainer(QDataStream &s, const Container &c)
template <typename Container> template <typename Container>
QDataStream &writeAssociativeMultiContainer(QDataStream &s, const Container &c) QDataStream &writeAssociativeMultiContainer(QDataStream &s, const Container &c)
{ {
QDataStream::writeQSizeType(s, c.size()); if (!QDataStream::writeQSizeType(s, c.size()))
return s;
auto it = c.constBegin(); auto it = c.constBegin();
auto end = c.constEnd(); auto end = c.constEnd();
while (it != end) { while (it != end) {
@ -425,16 +428,19 @@ qint64 QDataStream::readQSizeType(QDataStream &s)
return extendedLen; return extendedLen;
} }
void QDataStream::writeQSizeType(QDataStream &s, qint64 value) bool QDataStream::writeQSizeType(QDataStream &s, qint64 value)
{ {
if (value < qint64(QDataStreamSizes::ExtendedSize)) if (value < qint64(QDataStreamSizes::ExtendedSize)) {
s << quint32(value); s << quint32(value);
else if (s.version() >= QDataStream::Qt_6_7) } else if (s.version() >= QDataStream::Qt_6_7) {
s << quint32(QDataStreamSizes::ExtendedSize) << value; s << quint32(QDataStreamSizes::ExtendedSize) << value;
else if (value == qint64(QDataStreamSizes::ExtendedSize)) } else if (value == qint64(QDataStreamSizes::ExtendedSize)) {
s << quint32(QDataStreamSizes::ExtendedSize); s << quint32(QDataStreamSizes::ExtendedSize);
else } else {
s.setStatus(QDataStream::WriteFailed); // value is too big for old format s.setStatus(QDataStream::SizeLimitExceeded); // value is too big for old format
return false;
}
return true;
} }
inline QDataStream &QDataStream::operator>>(char &i) inline QDataStream &QDataStream::operator>>(char &i)

View File

@ -134,6 +134,7 @@ private slots:
void stream_atEnd(); void stream_atEnd();
void stream_writeError(); void stream_writeError();
void stream_writeSizeLimitExceeded();
void stream_QByteArray2(); void stream_QByteArray2();
@ -2190,6 +2191,19 @@ void tst_QDataStream::stream_writeError()
TEST_WRITE_ERROR(.writeRawData("test", 4)) TEST_WRITE_ERROR(.writeRawData("test", 4))
} }
void tst_QDataStream::stream_writeSizeLimitExceeded()
{
QByteArray ba;
QDataStream ds(&ba, QDataStream::ReadWrite);
// Set the version that supports only 32-bit data size
ds.setVersion(QDataStream::Qt_6_6);
QCOMPARE(ds.status(), QDataStream::Ok);
const qint64 size = qint64(std::numeric_limits<quint32>::max()) + 1;
ds.writeBytes("", size);
QCOMPARE(ds.status(), QDataStream::SizeLimitExceeded);
QVERIFY(ba.isEmpty());
}
void tst_QDataStream::stream_QByteArray2() void tst_QDataStream::stream_QByteArray2()
{ {
QByteArray ba; QByteArray ba;

View File

@ -28,6 +28,7 @@ public slots:
private slots: private slots:
void stream_bigQString(); void stream_bigQString();
void stream_bigQByteArray();
void stream_bigQList(); void stream_bigQList();
void stream_bigQSet(); void stream_bigQSet();
void stream_bigQMap(); void stream_bigQMap();
@ -37,6 +38,10 @@ private slots:
void tst_QDataStream::initTestCase() void tst_QDataStream::initTestCase()
{ {
qputenv("QTEST_FUNCTION_TIMEOUT", "9000000"); qputenv("QTEST_FUNCTION_TIMEOUT", "9000000");
QTest::addColumn<QDataStream::Version>("streamVersion");
QTest::addRow("current") << QDataStream::Qt_DefaultCompiledVersion;
QTest::addRow("Qt_6_6") << QDataStream::Qt_6_6;
} }
template <class T> template <class T>
@ -110,11 +115,13 @@ template <class T>
void tst_QDataStream::stream_big() void tst_QDataStream::stream_big()
{ {
#if QT_POINTER_SIZE > 4 #if QT_POINTER_SIZE > 4
QFETCH_GLOBAL(const QDataStream::Version, streamVersion);
QElapsedTimer timer; QElapsedTimer timer;
T input; T input;
fill(input); fill(input);
QByteArray ba; QByteArray ba;
QDataStream inputstream(&ba, QIODevice::WriteOnly); QDataStream inputstream(&ba, QIODevice::WriteOnly);
inputstream.setVersion(streamVersion);
timer.start(); timer.start();
try { try {
inputstream << input; inputstream << input;
@ -122,17 +129,23 @@ void tst_QDataStream::stream_big()
QSKIP("Not enough memory to copy into QDataStream."); QSKIP("Not enough memory to copy into QDataStream.");
} }
qDebug("Streamed into QDataStream in %lld ms", timer.elapsed()); qDebug("Streamed into QDataStream in %lld ms", timer.elapsed());
T output; if (streamVersion < QDataStream::Qt_6_7) {
QDataStream outputstream(ba); // old versions do not support data size more than 4 GiB
timer.start(); QCOMPARE(inputstream.status(), QDataStream::SizeLimitExceeded);
try { QVERIFY(ba.isEmpty());
outputstream >> output; } else {
} catch (const std::bad_alloc &) { T output;
QSKIP("Not enough memory to copy out of QDataStream."); QDataStream outputstream(ba);
timer.start();
try {
outputstream >> output;
} catch (const std::bad_alloc &) {
QSKIP("Not enough memory to copy out of QDataStream.");
}
qDebug("Streamed out of QDataStream in %lld ms", timer.elapsed());
QCOMPARE(input.size(), output.size());
QCOMPARE(input, output);
} }
qDebug("Streamed out of QDataStream in %lld ms", timer.elapsed());
QCOMPARE(input.size(), output.size());
QCOMPARE(input, output);
#else #else
QSKIP("This test is 64-bit only."); QSKIP("This test is 64-bit only.");
#endif #endif
@ -142,6 +155,11 @@ void tst_QDataStream::stream_bigQString()
{ {
stream_big<QString>(); stream_big<QString>();
} }
void tst_QDataStream::stream_bigQByteArray()
{
stream_big<QByteArray>();
}
void tst_QDataStream::stream_bigQList() void tst_QDataStream::stream_bigQList()
{ {
stream_big<QList<char>>(); stream_big<QList<char>>();