diff --git a/src/corelib/compat/removed_api.cpp b/src/corelib/compat/removed_api.cpp index bb3dca62a13..591ef0fc68c 100644 --- a/src/corelib/compat/removed_api.cpp +++ b/src/corelib/compat/removed_api.cpp @@ -631,6 +631,56 @@ void QDateTime::setTimeZone(const QTimeZone &toZone) setTimeZone(toZone, TransitionResolution::LegacyBehavior); } +#include "qdatastream.h" + +QDataStream &QDataStream::readBytes(char *&s, uint &l) +{ + qsizetype length = 0; + (void)readBytes(s, length); + if (length != qsizetype(uint(length))) { + setStatus(ReadCorruptData); // Cannot store length in l + delete[] s; + l = 0; + return *this; + } + l = uint(length); + return *this; +} + +QDataStream &QDataStream::writeBytes(const char *s, uint len) +{ + qsizetype length = qsizetype(len); + if (length < 0) { + setStatus(WriteFailed); + return *this; + } + return writeBytes(s, length); +} + +int QDataStream::skipRawData(int len) +{ + return int(skipRawData(qint64(len))); +} + +#if QT_POINTER_SIZE != 4 + +int QDataStream::readBlock(char *data, int len) +{ + return int(readBlock(data, qsizetype(len))); +} + +int QDataStream::readRawData(char *s, int len) +{ + return int(readRawData(s, qsizetype(len))); +} + +int QDataStream::writeRawData(const char *s, int len) +{ + return writeRawData(s, qsizetype(len)); +} + +#endif // QT_POINTER_SIZE != 4 + #if defined(Q_OS_ANDROID) #include "qjniobject.h" diff --git a/src/corelib/serialization/qdatastream.cpp b/src/corelib/serialization/qdatastream.cpp index 7bb16645c9d..11236c14f15 100644 --- a/src/corelib/serialization/qdatastream.cpp +++ b/src/corelib/serialization/qdatastream.cpp @@ -525,8 +525,8 @@ void QDataStream::setByteOrder(ByteOrder bo) \value Qt_6_3 Same as Qt_6_0 \value Qt_6_4 Same as Qt_6_0 \value Qt_6_5 Same as Qt_6_0 - \value Qt_6_6 Same as Qt_6_0 - \value Qt_6_7 Same as Qt_6_6 + \value Qt_6_6 Version 21 (Qt 6.6) + \value Qt_6_7 Version 22 (Qt 6.7) \omitvalue Qt_DefaultCompiledVersion \sa setVersion(), version() @@ -731,13 +731,13 @@ bool QDataStream::isDeviceTransactionStarted() const \internal */ -int QDataStream::readBlock(char *data, int len) +qsizetype QDataStream::readBlock(char *data, qsizetype len) { // Disable reads on failure in transacted stream if (q_status != Ok && dev->isTransactionStarted()) return -1; - const int readResult = dev->read(data, len); + const qsizetype readResult = dev->read(data, len); if (readResult != len) setStatus(ReadPastEnd); return readResult; @@ -976,7 +976,7 @@ QDataStream &QDataStream::operator>>(double &f) QDataStream &QDataStream::operator>>(char *&s) { - uint len = 0; + qsizetype len = 0; return readBytes(s, len); } @@ -1020,30 +1020,40 @@ QDataStream &QDataStream::operator>>(char32_t &c) The \a l parameter is set to the length of the buffer. If the string read is empty, \a l is set to 0 and \a s is set to \nullptr. - The serialization format is a quint32 length specifier first, - then \a l bytes of data. + The serialization format is a length specifier first, then \a l + bytes of data. The length specifier is one quint32 if the version + is less than 6.7 or if the number of elements is less than 0xfffffffe + (2^32 -2), otherwise there is an extend value 0xfffffffe followed by + one quint64 with the actual value. In addition for containers that + support isNull(), it is encoded as a single quint32 with all bits + set and no data. \sa readRawData(), writeBytes() */ -QDataStream &QDataStream::readBytes(char *&s, uint &l) +QDataStream &QDataStream::readBytes(char *&s, qsizetype &l) { s = nullptr; l = 0; CHECK_STREAM_PRECOND(*this) - quint32 len; - *this >> len; - if (len == 0) + qint64 length = readQSizeType(*this); + if (length == 0) return *this; - const quint32 Step = 1024 * 1024; - quint32 allocated = 0; + qsizetype len = qsizetype(length); + if (length != len || length < 0) { + setStatus(ReadCorruptData); // Cannot store len in l + return *this; + } + + constexpr qsizetype Step = 1024 * 1024; + qsizetype allocated = 0; char *prevBuf = nullptr; char *curBuf = nullptr; do { - int blockSize = qMin(Step, len - allocated); + qsizetype blockSize = qMin(Step, len - allocated); prevBuf = curBuf; curBuf = new char[allocated + blockSize + 1]; if (prevBuf) { @@ -1059,7 +1069,7 @@ QDataStream &QDataStream::readBytes(char *&s, uint &l) s = curBuf; s[len] = '\0'; - l = (uint)len; + l = len; return *this; } @@ -1072,7 +1082,7 @@ QDataStream &QDataStream::readBytes(char *&s, uint &l) \sa readBytes(), QIODevice::read(), writeRawData() */ -int QDataStream::readRawData(char *s, int len) +qsizetype QDataStream::readRawData(char *s, qsizetype len) { CHECK_STREAM_PRECOND(-1) return readBlock(s, len); @@ -1346,22 +1356,26 @@ QDataStream &QDataStream::operator<<(char32_t c) Writes the length specifier \a len and the buffer \a s to the stream and returns a reference to the stream. - The \a len is serialized as a quint32, followed by \a len bytes - from \a s. Note that the data is \e not encoded. + The \a len is serialized as a quint32 and an optional quint64, + followed by \a len bytes from \a s. Note that the data is + \e not encoded. \sa writeRawData(), readBytes() */ -QDataStream &QDataStream::writeBytes(const char *s, uint len) +QDataStream &QDataStream::writeBytes(const char *s, qsizetype len) { + if (len < 0) { + q_status = WriteFailed; + return *this; + } CHECK_STREAM_WRITE_PRECOND(*this) - *this << (quint32)len; // write length specifier - if (len) + writeQSizeType(*this, len); // write length specifier + if (len > 0) writeRawData(s, len); return *this; } - /*! Writes \a len bytes from \a s to the stream. Returns the number of bytes actually written, or -1 on error. @@ -1370,10 +1384,10 @@ QDataStream &QDataStream::writeBytes(const char *s, uint len) \sa writeBytes(), QIODevice::write(), readRawData() */ -int QDataStream::writeRawData(const char *s, int len) +qsizetype QDataStream::writeRawData(const char *s, qsizetype len) { CHECK_STREAM_WRITE_PRECOND(-1) - int ret = dev->write(s, len); + qsizetype ret = dev->write(s, len); if (ret != len) q_status = WriteFailed; return ret; @@ -1390,13 +1404,13 @@ int QDataStream::writeRawData(const char *s, int len) \sa QIODevice::seek() */ -int QDataStream::skipRawData(int len) +qint64 QDataStream::skipRawData(qint64 len) { CHECK_STREAM_PRECOND(-1) if (q_status != Ok && dev->isTransactionStarted()) return -1; - const int skipResult = dev->skip(len); + const qint64 skipResult = dev->skip(len); if (skipResult != len) setStatus(ReadPastEnd); return skipResult; diff --git a/src/corelib/serialization/qdatastream.h b/src/corelib/serialization/qdatastream.h index e5ae55402a2..861c2e7a658 100644 --- a/src/corelib/serialization/qdatastream.h +++ b/src/corelib/serialization/qdatastream.h @@ -19,12 +19,26 @@ QT_BEGIN_NAMESPACE class qfloat16; #endif class QByteArray; +class QDataStream; class QIODevice; +class QString; #if !defined(QT_NO_DATASTREAM) || defined(QT_BOOTSTRAPPED) class QDataStreamPrivate; namespace QtPrivate { class StreamStateSaver; +template +QDataStream &readArrayBasedContainer(QDataStream &s, Container &c); +template +QDataStream &readListBasedContainer(QDataStream &s, Container &c); +template +QDataStream &readAssociativeContainer(QDataStream &s, Container &c); +template +QDataStream &writeSequentialContainer(QDataStream &s, const Container &c); +template +QDataStream &writeAssociativeContainer(QDataStream &s, const Container &c); +template +QDataStream &writeAssociativeMultiContainer(QDataStream &s, const Container &c); } class Q_CORE_EXPORT QDataStream : public QIODeviceBase { @@ -69,7 +83,7 @@ public: Qt_6_4 = Qt_6_0, Qt_6_5 = Qt_6_0, Qt_6_6 = 21, - Qt_6_7 = Qt_6_6, + Qt_6_7 = 22, Qt_DefaultCompiledVersion = Qt_6_7 #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) #error Add the datastream version for this Qt version and update Qt_DefaultCompiledVersion @@ -159,14 +173,20 @@ public: QDataStream &operator<<(char32_t c); QDataStream &operator<<(const volatile void *) = delete; - +#if QT_CORE_REMOVED_SINCE(6, 7) QDataStream &readBytes(char *&, uint &len); - int readRawData(char *, int len); - QDataStream &writeBytes(const char *, uint len); - int writeRawData(const char *, int len); - int skipRawData(int len); +#endif +#if QT_CORE_REMOVED_SINCE(6, 7) && QT_POINTER_SIZE != 4 + int readRawData(char *, int len); + int writeRawData(const char *, int len); +#endif + QDataStream &readBytes(char *&, qsizetype &len); + qsizetype readRawData(char *, qsizetype len); + QDataStream &writeBytes(const char *, qsizetype len); + qsizetype writeRawData(const char *, qsizetype len); + qint64 skipRawData(qint64 len); void startTransaction(); bool commitTransaction(); @@ -185,9 +205,32 @@ private: ByteOrder byteorder; int ver; Status q_status; - +#if QT_CORE_REMOVED_SINCE(6, 7) && QT_POINTER_SIZE != 4 int readBlock(char *data, int len); +#endif + qsizetype readBlock(char *data, qsizetype len); + static inline qint64 readQSizeType(QDataStream &s); + static inline void writeQSizeType(QDataStream &s, qint64 value); + enum class QDataStreamSizes : quint32 { NullCode = 0xffffffffu, ExtendedSize = 0xfffffffeu }; + friend class QtPrivate::StreamStateSaver; + Q_CORE_EXPORT friend QDataStream &operator<<(QDataStream &out, const QString &str); + Q_CORE_EXPORT friend QDataStream &operator>>(QDataStream &in, QString &str); + Q_CORE_EXPORT friend QDataStream &operator<<(QDataStream &out, const QByteArray &ba); + Q_CORE_EXPORT friend QDataStream &operator>>(QDataStream &in, QByteArray &ba); + template + friend QDataStream &QtPrivate::readArrayBasedContainer(QDataStream &s, Container &c); + template + friend QDataStream &QtPrivate::readListBasedContainer(QDataStream &s, Container &c); + template + friend QDataStream &QtPrivate::readAssociativeContainer(QDataStream &s, Container &c); + template + friend QDataStream &QtPrivate::writeSequentialContainer(QDataStream &s, const Container &c); + template + friend QDataStream &QtPrivate::writeAssociativeContainer(QDataStream &s, const Container &c); + template + friend QDataStream &QtPrivate::writeAssociativeMultiContainer(QDataStream &s, + const Container &c); }; namespace QtPrivate { @@ -219,10 +262,14 @@ QDataStream &readArrayBasedContainer(QDataStream &s, Container &c) StreamStateSaver stateSaver(&s); c.clear(); - quint32 n; - s >> n; + qint64 size = QDataStream::readQSizeType(s); + qsizetype n = size; + if (size != n || size < 0) { + s.setStatus(QDataStream::ReadCorruptData); + return s; + } c.reserve(n); - for (quint32 i = 0; i < n; ++i) { + for (qsizetype i = 0; i < n; ++i) { typename Container::value_type t; s >> t; if (s.status() != QDataStream::Ok) { @@ -241,9 +288,13 @@ QDataStream &readListBasedContainer(QDataStream &s, Container &c) StreamStateSaver stateSaver(&s); c.clear(); - quint32 n; - s >> n; - for (quint32 i = 0; i < n; ++i) { + qint64 size = QDataStream::readQSizeType(s); + qsizetype n = size; + if (size != n || size < 0) { + s.setStatus(QDataStream::ReadCorruptData); + return s; + } + for (qsizetype i = 0; i < n; ++i) { typename Container::value_type t; s >> t; if (s.status() != QDataStream::Ok) { @@ -262,9 +313,13 @@ QDataStream &readAssociativeContainer(QDataStream &s, Container &c) StreamStateSaver stateSaver(&s); c.clear(); - quint32 n; - s >> n; - for (quint32 i = 0; i < n; ++i) { + qint64 size = QDataStream::readQSizeType(s); + qsizetype n = size; + if (size != n || size < 0) { + s.setStatus(QDataStream::ReadCorruptData); + return s; + } + for (qsizetype i = 0; i < n; ++i) { typename Container::key_type k; typename Container::mapped_type t; s >> k >> t; @@ -281,7 +336,7 @@ QDataStream &readAssociativeContainer(QDataStream &s, Container &c) template QDataStream &writeSequentialContainer(QDataStream &s, const Container &c) { - s << quint32(c.size()); + QDataStream::writeQSizeType(s, c.size()); for (const typename Container::value_type &t : c) s << t; @@ -291,7 +346,7 @@ QDataStream &writeSequentialContainer(QDataStream &s, const Container &c) template QDataStream &writeAssociativeContainer(QDataStream &s, const Container &c) { - s << quint32(c.size()); + QDataStream::writeQSizeType(s, c.size()); auto it = c.constBegin(); auto end = c.constEnd(); while (it != end) { @@ -305,7 +360,7 @@ QDataStream &writeAssociativeContainer(QDataStream &s, const Container &c) template QDataStream &writeAssociativeMultiContainer(QDataStream &s, const Container &c) { - s << quint32(c.size()); + QDataStream::writeQSizeType(s, c.size()); auto it = c.constBegin(); auto end = c.constEnd(); while (it != end) { @@ -354,6 +409,31 @@ inline int QDataStream::version() const inline void QDataStream::setVersion(int v) { ver = v; } +qint64 QDataStream::readQSizeType(QDataStream &s) +{ + quint32 first; + s >> first; + if (first == quint32(QDataStreamSizes::NullCode)) + return -1; + if (first < quint32(QDataStreamSizes::ExtendedSize) || s.version() < QDataStream::Qt_6_7) + return qint64(first); + qint64 extendedLen; + s >> extendedLen; + return extendedLen; +} + +void QDataStream::writeQSizeType(QDataStream &s, qint64 value) +{ + if (value < qint64(QDataStreamSizes::ExtendedSize)) + s << quint32(value); + else if (s.version() >= QDataStream::Qt_6_7) + s << quint32(QDataStreamSizes::ExtendedSize) << value; + else if (value == qint64(QDataStreamSizes::ExtendedSize)) + s << quint32(QDataStreamSizes::ExtendedSize); + else + s.setStatus(QDataStream::WriteFailed); // value is too big for old format +} + inline QDataStream &QDataStream::operator>>(char &i) { return *this >> reinterpret_cast(i); } diff --git a/src/corelib/text/qbytearray.cpp b/src/corelib/text/qbytearray.cpp index b0e3590e28f..193d093c0d4 100644 --- a/src/corelib/text/qbytearray.cpp +++ b/src/corelib/text/qbytearray.cpp @@ -3266,7 +3266,7 @@ void QByteArray::clear() QDataStream &operator<<(QDataStream &out, const QByteArray &ba) { if (ba.isNull() && out.version() >= 6) { - out << (quint32)0xffffffff; + QDataStream::writeQSizeType(out, -1); return out; } return out.writeBytes(ba.constData(), ba.size()); @@ -3283,15 +3283,21 @@ QDataStream &operator<<(QDataStream &out, const QByteArray &ba) QDataStream &operator>>(QDataStream &in, QByteArray &ba) { ba.clear(); - quint32 len; - in >> len; - if (len == 0xffffffff) { // null byte-array + + qint64 size = QDataStream::readQSizeType(in); + qsizetype len = size; + if (size != len || size < -1) { + ba.clear(); + in.setStatus(QDataStream::ReadCorruptData); + return in; + } + if (len == -1) { // null byte-array ba = QByteArray(); return in; } - const quint32 Step = 1024 * 1024; - quint32 allocated = 0; + constexpr qsizetype Step = 1024 * 1024; + qsizetype allocated = 0; do { qsizetype blockSize = qMin(Step, len - allocated); diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp index 49641be2dce..d823e8bfbcb 100644 --- a/src/corelib/text/qstring.cpp +++ b/src/corelib/text/qstring.cpp @@ -9327,16 +9327,15 @@ QDataStream &operator<<(QDataStream &out, const QString &str) if (!str.isNull() || out.version() < 3) { if ((out.byteOrder() == QDataStream::BigEndian) == (QSysInfo::ByteOrder == QSysInfo::BigEndian)) { out.writeBytes(reinterpret_cast(str.unicode()), - static_cast(sizeof(QChar) * str.size())); + static_cast(sizeof(QChar) * str.size())); } else { QVarLengthArray buffer(str.size()); qbswap(str.constData(), str.size(), buffer.data()); out.writeBytes(reinterpret_cast(buffer.data()), - static_cast(sizeof(char16_t) * buffer.size())); + static_cast(sizeof(char16_t) * buffer.size())); } } else { - // write null marker - out << (quint32)0xffffffff; + QDataStream::writeQSizeType(out, -1); // write null marker } } return out; @@ -9358,20 +9357,25 @@ QDataStream &operator>>(QDataStream &in, QString &str) in >> l; str = QString::fromLatin1(l); } else { - quint32 bytes = 0; - in >> bytes; // read size of string - if (bytes == 0xffffffff) { // null string + qint64 size = QDataStream::readQSizeType(in); + qsizetype bytes = size; + if (size != bytes || size < -1) { + str.clear(); + in.setStatus(QDataStream::ReadCorruptData); + return in; + } + if (bytes == -1) { // null string str = QString(); - } else if (bytes > 0) { // not empty + } else if (bytes > 0) { if (bytes & 0x1) { str.clear(); in.setStatus(QDataStream::ReadCorruptData); return in; } - const quint32 Step = 1024 * 1024; - quint32 len = bytes / 2; - quint32 allocated = 0; + const qsizetype Step = 1024 * 1024; + qsizetype len = bytes / 2; + qsizetype allocated = 0; while (allocated < len) { int blockSize = qMin(Step, len - allocated); diff --git a/tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp b/tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp index 80c18187d5d..22bcba5ef13 100644 --- a/tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp +++ b/tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp @@ -296,6 +296,7 @@ static int NColorRoles[] = { QPalette::PlaceholderText + 1, // Qt_5_13, Qt_5_14, Qt_5_15 QPalette::PlaceholderText + 1, // Qt_6_0 QPalette::Accent + 1, // Qt_6_6 + QPalette::Accent + 1, // Qt_6_7 0 // add the correct value for Qt_5_14 here later }; @@ -2795,7 +2796,6 @@ void tst_QDataStream::status_charptr_QByteArray_data() QTest::newRow("badsize 1MB+1") << QByteArray("\x00\x10\x00\x01", 4) + oneMbMinus1 + QByteArray("j") << (int) QDataStream::ReadPastEnd << QByteArray(); QTest::newRow("badsize 3MB") << QByteArray("\x00\x30\x00\x00", 4) + threeMbMinus1 << (int) QDataStream::ReadPastEnd << QByteArray(); QTest::newRow("badsize 3MB+1") << QByteArray("\x00\x30\x00\x01", 4) + threeMbMinus1 + QByteArray("j") << (int) QDataStream::ReadPastEnd << QByteArray(); - QTest::newRow("size -1") << QByteArray("\xff\xff\xff\xff", 4) << (int) QDataStream::ReadPastEnd << QByteArray(); QTest::newRow("size -2") << QByteArray("\xff\xff\xff\xfe", 4) << (int) QDataStream::ReadPastEnd << QByteArray(); } @@ -2818,10 +2818,10 @@ void tst_QDataStream::status_charptr_QByteArray() { QDataStream stream(&data, QIODevice::ReadOnly); char *buf; - uint len; + qsizetype len; stream.readBytes(buf, len); - QCOMPARE((int)len, expectedString.size()); + QCOMPARE(len, expectedString.size()); QCOMPARE(QByteArray(buf, len), expectedString); QCOMPARE(int(stream.status()), expectedStatus); delete [] buf; @@ -2897,12 +2897,20 @@ void tst_QDataStream::status_QString_data() QTest::newRow("badsize 1MB+1") << QByteArray("\x00\x20\x00\x02", 4) + oneMbMinus1Data + QByteArray("j") << (int) QDataStream::ReadPastEnd << QString(); QTest::newRow("badsize 3MB") << QByteArray("\x00\x60\x00\x00", 4) + threeMbMinus1Data << (int) QDataStream::ReadPastEnd << QString(); QTest::newRow("badsize 3MB+1") << QByteArray("\x00\x60\x00\x02", 4) + threeMbMinus1Data + QByteArray("j") << (int) QDataStream::ReadPastEnd << QString(); - QTest::newRow("size -2") << QByteArray("\xff\xff\xff\xfe", 4) << (int) QDataStream::ReadPastEnd << QString(); - QTest::newRow("size MAX") << QByteArray("\x7f\xff\xff\xfe", 4) << (int) QDataStream::ReadPastEnd << QString(); + QTest::newRow("32 bit size should be 64 bit") << QByteArray("\xff\xff\xff\xfe", 4) << (int) QDataStream::ReadPastEnd << QString(); - // corrupt data +#if QT_POINTER_SIZE != 4 + // past end on 64 bit platforms + QTest::newRow("32 bit size MAX string no content") << QByteArray("\xff\xff\xff\xfc", 4) << (int) QDataStream::ReadPastEnd << QString(); +#else + // too big for 32 bit platforms + QTest::newRow("32 bit size MAX string no content") << QByteArray("\xff\xff\xff\xfc", 4) << (int) QDataStream::ReadCorruptData << QString(); +#endif + // too big on both 32 and 64 bit platforms because qsizetype is signed + QTest::newRow("64 bit size MAX string no content") << QByteArray("\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\xff\xfe", 12) << (int) QDataStream::ReadCorruptData << QString(); + + // corrupt data because QChar is 16 bit => even size required QTest::newRow("corrupt1") << QByteArray("yyyy") << (int) QDataStream::ReadCorruptData << QString(); - QTest::newRow("size -3") << QByteArray("\xff\xff\xff\xfd", 4) << (int) QDataStream::ReadCorruptData << QString(); } void tst_QDataStream::status_QString() diff --git a/tests/manual/corelib/CMakeLists.txt b/tests/manual/corelib/CMakeLists.txt index 0115fc76f9e..8ed7441e77a 100644 --- a/tests/manual/corelib/CMakeLists.txt +++ b/tests/manual/corelib/CMakeLists.txt @@ -1,5 +1,6 @@ # Copyright (C) 2023 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause +add_subdirectory(qdatastream) add_subdirectory(time) add_subdirectory(tools) diff --git a/tests/manual/corelib/qdatastream/CMakeLists.txt b/tests/manual/corelib/qdatastream/CMakeLists.txt new file mode 100644 index 00000000000..5d26a862e56 --- /dev/null +++ b/tests/manual/corelib/qdatastream/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_manual_qdatastream Test: +##################################################################### + +qt_internal_add_manual_test(tst_manual_qdatastream + SOURCES + tst_manualqdatastream.cpp + LIBRARIES + Qt::Core + ENABLE_AUTOGEN_TOOLS + uic +) diff --git a/tests/manual/corelib/qdatastream/qdatastream.pro b/tests/manual/corelib/qdatastream/qdatastream.pro new file mode 100644 index 00000000000..9143f628512 --- /dev/null +++ b/tests/manual/corelib/qdatastream/qdatastream.pro @@ -0,0 +1,6 @@ +CONFIG += testcase + +SOURCES += tst_manualqdatastream.cpp +QT = core testlib + +TARGET = tst_manual_qdatastream diff --git a/tests/manual/corelib/qdatastream/tst_manualqdatastream.cpp b/tests/manual/corelib/qdatastream/tst_manualqdatastream.cpp new file mode 100644 index 00000000000..59bff74e132 --- /dev/null +++ b/tests/manual/corelib/qdatastream/tst_manualqdatastream.cpp @@ -0,0 +1,171 @@ +// Copyright (C) 2023 BlackBerry Limited. All rights reserved. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include +#include +#include +#include +#include + +// These tests are way too slow to be part of automatic unit tests +class tst_QDataStream : public QObject +{ + Q_OBJECT + + template + void fill(T &input); + template <> + void fill(QSet &input); + template <> + void fill(QMap &input); + template <> + void fill(QHash &input); + + template + void stream_big(); + +public slots: + void initTestCase(); + +private slots: + void stream_bigQString(); + void stream_bigQList(); + void stream_bigQSet(); + void stream_bigQMap(); + void stream_bigQHash(); +}; + +void tst_QDataStream::initTestCase() +{ + qputenv("QTEST_FUNCTION_TIMEOUT", "9000000"); +} + +template +void tst_QDataStream::fill(T &input) +{ + constexpr qsizetype GiB = 1024 * 1024 * 1024; + constexpr qsizetype BaseSize = 4 * GiB + 1; + qDebug("Filling container with %lld entries", qint64(BaseSize)); + QElapsedTimer timer; + timer.start(); + try { + input.reserve(BaseSize); + input.resize(BaseSize, 'a'); + } catch (const std::bad_alloc &) { + QSKIP("Could not allocate 4 GiB of RAM."); + } + qDebug("Created dataset in %lld ms", timer.elapsed()); +} + +template <> +void tst_QDataStream::fill(QSet &input) +{ + constexpr qsizetype GiB = 1024 * 1024 * 1024; + constexpr qsizetype BaseSize = 4 * GiB + 1; + qDebug("Filling container with %lld entries", qint64(BaseSize)); + QElapsedTimer timer; + timer.start(); + try { + input.reserve(BaseSize); + for (qsizetype i = 0; i < BaseSize; ++i) + input.insert(i); + } catch (const std::bad_alloc &) { + QSKIP("Could not allocate 4 Gi entries."); + } + qDebug("Created dataset in %lld ms", timer.elapsed()); +} + +template <> +void tst_QDataStream::fill(QMap &input) +{ + constexpr qsizetype GiB = 1024 * 1024 * 1024; + constexpr qsizetype BaseSize = 4 * GiB + 1; + qDebug("Filling container with %lld entries", qint64(BaseSize)); + QElapsedTimer timer; + timer.start(); + try { + for (qsizetype i = 0; i < BaseSize; ++i) + input.insert(i, i); + } catch (const std::bad_alloc &) { + QSKIP("Could not allocate 4 Gi entries."); + } + qDebug("Created dataset in %lld ms", timer.elapsed()); +} + +template <> +void tst_QDataStream::fill(QHash &input) +{ + constexpr qsizetype GiB = 1024 * 1024 * 1024; + constexpr qsizetype BaseSize = 4 * GiB + 1; + qDebug("Filling container with %lld entries", qint64(BaseSize)); + QElapsedTimer timer; + timer.start(); + try { + input.reserve(BaseSize); + for (qsizetype i = 0; i < BaseSize; ++i) + input.emplace(i, i); + } catch (const std::bad_alloc &) { + QSKIP("Could not allocate 4 Gi entries."); + } + qDebug("Created dataset in %lld ms", timer.elapsed()); +} + +template +void tst_QDataStream::stream_big() +{ +#if QT_POINTER_SIZE > 4 + QElapsedTimer timer; + T input; + fill(input); + QByteArray ba; + QDataStream inputstream(&ba, QIODevice::WriteOnly); + timer.start(); + try { + inputstream << input; + } catch (const std::bad_alloc &) { + QSKIP("Not enough memory to copy into QDataStream."); + } + qDebug("Streamed into QDataStream in %lld ms", timer.elapsed()); + T output; + 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); +#else + QSKIP("This test is 64-bit only."); +#endif +} + +void tst_QDataStream::stream_bigQString() +{ + stream_big(); +} +void tst_QDataStream::stream_bigQList() +{ + stream_big>(); +} + +void tst_QDataStream::stream_bigQSet() +{ + stream_big>(); +} + +void tst_QDataStream::stream_bigQMap() +{ + stream_big>(); +} +void tst_QDataStream::stream_bigQHash() +{ + stream_big>(); +} + +QTEST_MAIN(tst_QDataStream) + +#include "tst_manualqdatastream.moc"