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;
}
CHECK_STREAM_WRITE_PRECOND(*this)
writeQSizeType(*this, len); // write length specifier
if (len > 0)
// Write length then, if any, content
if (writeQSizeType(*this, len) && len > 0)
writeRawData(s, len);
return *this;
}

View File

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

View File

@ -134,6 +134,7 @@ private slots:
void stream_atEnd();
void stream_writeError();
void stream_writeSizeLimitExceeded();
void stream_QByteArray2();
@ -2190,6 +2191,19 @@ void tst_QDataStream::stream_writeError()
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()
{
QByteArray ba;

View File

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