summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/corelib/text/qbytearray.cpp65
-rw-r--r--tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp13
2 files changed, 45 insertions, 33 deletions
diff --git a/src/corelib/text/qbytearray.cpp b/src/corelib/text/qbytearray.cpp
index 28094ec34e..7aae6973b4 100644
--- a/src/corelib/text/qbytearray.cpp
+++ b/src/corelib/text/qbytearray.cpp
@@ -506,15 +506,6 @@ quint16 qChecksum(QByteArrayView data, Qt::ChecksumType standard)
The default value is -1, which specifies zlib's default
compression.
-//![compress-limit-note]
- \note The maximum size of data that this function can consume is limited by
- what the platform's \c{unsigned long} can represent (a Zlib limitation).
- That means that data > 4GiB can be compressed and decompressed on a 64-bit
- Unix system, but not on a 64-bit Windows system. Portable code should
- therefore avoid using qCompress()/qUncompress() to compress more than 4GiB
- of input.
-//![compress-limit-note]
-
\sa qUncompress(const QByteArray &data)
*/
@@ -526,8 +517,6 @@ quint16 qChecksum(QByteArrayView data, Qt::ChecksumType standard)
Compresses the first \a nbytes of \a data at compression level
\a compressionLevel and returns the compressed data in a new byte array.
-
- \include qbytearray.cpp compress-limit-note
*/
#ifndef QT_NO_COMPRESS
@@ -687,30 +676,40 @@ QByteArray qCompress(const uchar* data, qsizetype nbytes, int compressionLevel)
if (compressionLevel < -1 || compressionLevel > 9)
compressionLevel = -1;
- ulong len = nbytes + nbytes / 100 + 13;
- QByteArray bazip;
- int res;
- do {
- bazip.resize(len + HeaderSize);
- res = ::compress2(reinterpret_cast<uchar *>(bazip.data()) + HeaderSize, &len,
- data, nbytes,
- compressionLevel);
-
- switch (res) {
- case Z_OK:
- bazip.resize(len + HeaderSize);
- qToBigEndian(qt_saturate<CompressSizeHint_t>(nbytes), bazip.data());
- break;
- case Z_MEM_ERROR:
- return tooMuchData(ZLibOp::Compression);
-
- case Z_BUF_ERROR:
- len *= 2;
- break;
+ QArrayDataPointer out = [&] {
+ constexpr qsizetype SingleAllocLimit = 256 * 1024; // the maximum size for which we use
+ // zlib's compressBound() to guarantee
+ // the output buffer size is sufficient
+ // to hold result
+ qsizetype capacity = HeaderSize;
+ if (nbytes < SingleAllocLimit) {
+ // use maximum size
+ capacity += compressBound(uLong(nbytes)); // cannot overflow (both times)!
+ return QArrayDataPointer{QTypedArrayData<char>::allocate(capacity)};
}
- } while (res == Z_BUF_ERROR);
- return bazip;
+ // for larger buffers, assume it compresses optimally, and
+ // grow geometrically from there:
+ constexpr qsizetype MaxCompressionFactor = 1024; // max theoretical factor is 1032
+ // cf. http://www.zlib.org/zlib_tech.html,
+ // but use a nearby power-of-two (faster)
+ capacity += std::max(qsizetype(compressBound(uLong(SingleAllocLimit))),
+ nbytes / MaxCompressionFactor);
+ return QArrayDataPointer{QTypedArrayData<char>::allocate(capacity, QArrayData::Grow)};
+ }();
+
+ if (out.data() == nullptr) // allocation failed
+ return tooMuchData(ZLibOp::Compression);
+
+ qToBigEndian(qt_saturate<CompressSizeHint_t>(nbytes), out.data());
+ out.size = HeaderSize;
+
+ return xxflate(ZLibOp::Compression, std::move(out), {data, nbytes},
+ [=] (z_stream *zs) { return deflateInit(zs, compressionLevel); },
+ [] (z_stream *zs, size_t inputLeft) {
+ return deflate(zs, inputLeft ? Z_NO_FLUSH : Z_FINISH);
+ },
+ [] (z_stream *zs) { deflateEnd(zs); });
}
#endif
diff --git a/tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp b/tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp
index a3534f30a4..7f48c60b7c 100644
--- a/tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp
+++ b/tests/auto/corelib/text/qbytearray/tst_qbytearray.cpp
@@ -360,6 +360,19 @@ void tst_QByteArray::qUncompress4GiBPlus()
QCOMPARE(c.size(), 4 * GiB + 1);
QCOMPARE(std::string_view{c}.find_first_not_of('X'),
std::string_view::npos);
+
+ // re-compress once
+ // (produces 18MiB, we shouldn't use much more than that in allocated capacity)
+ c = ::qCompress(c);
+ QVERIFY(!c.isNull());
+
+ // and un-compress again, to make sure compression worked (we
+ // can't compare with compressed_3x, because zlib may change):
+ c = ::qUncompress(c);
+
+ QCOMPARE(c.size(), 4 * GiB + 1);
+ QCOMPARE(std::string_view{c}.find_first_not_of('X'),
+ std::string_view::npos);
}
}