From 35453446a511366e1250858b249e36c80b6ad044 Mon Sep 17 00:00:00 2001 From: Marc Mutz Date: Sun, 4 Jul 2021 23:44:01 +0200 Subject: [PATCH] QCryptographicHash: don't present the same data over and over again Need to decrement 'remaining' (check), but also increment data (meep). Testing is a bit complicated, as most algorithms are just too slow to fit into the 5min QTestLib timeout. Picked the fast ones and Sha512 (which completes here in < 17s, with threads), at least. Amends e12577b56396cca0df05f88f8787706a3a12c82d. [ChangeLog][QtCore][QCryptographicHash] Fixed a bug where presenting more than 4GiB in a single addData() call would calculate the wrong result(). Pick-to: 6.1 6.2 Change-Id: Ic72916ebc33ba087d58225af6d8240e46e41f434 Reviewed-by: Lars Knoll --- src/corelib/tools/qcryptographichash.cpp | 4 +- .../tst_qcryptographichash.cpp | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/corelib/tools/qcryptographichash.cpp b/src/corelib/tools/qcryptographichash.cpp index 47b72038eb..f6db6b976c 100644 --- a/src/corelib/tools/qcryptographichash.cpp +++ b/src/corelib/tools/qcryptographichash.cpp @@ -363,10 +363,8 @@ void QCryptographicHash::addData(const char *data, qsizetype length) // feed the data UINT_MAX bytes at a time, as some of the methods below // take a uint (of course, feeding more than 4G of data into the hashing // functions will be pretty slow anyway) - qsizetype remaining = length; - while (remaining) { + for (auto remaining = length; remaining; remaining -= length, data += length) { length = qMin(qsizetype(std::numeric_limits::max()), remaining); - remaining -= length; #else { #endif diff --git a/tests/auto/corelib/tools/qcryptographichash/tst_qcryptographichash.cpp b/tests/auto/corelib/tools/qcryptographichash/tst_qcryptographichash.cpp index c819f02d70..92a2c3516c 100644 --- a/tests/auto/corelib/tools/qcryptographichash/tst_qcryptographichash.cpp +++ b/tests/auto/corelib/tools/qcryptographichash/tst_qcryptographichash.cpp @@ -29,9 +29,14 @@ #include #include +#include #include #include +#if QT_CONFIG(cxx11_future) +# include +#endif + Q_DECLARE_METATYPE(QCryptographicHash::Algorithm) class tst_QCryptographicHash : public QObject @@ -51,6 +56,11 @@ private slots: void files(); void hashLength_data(); void hashLength(); + // keep last + void moreThan4GiBOfData_data(); + void moreThan4GiBOfData(); +private: + std::vector large; }; void tst_QCryptographicHash::repeated_result_data() @@ -419,5 +429,79 @@ void tst_QCryptographicHash::hashLength() QCOMPARE(QCryptographicHash::hashLength(algorithm), output.length()); } +void tst_QCryptographicHash::moreThan4GiBOfData_data() +{ +#if QT_POINTER_SIZE > 4 + QElapsedTimer timer; + timer.start(); + const size_t GiB = 1024 * 1024 * 1024; + try { + large.resize(4 * GiB + 1, '\0'); + } catch (const std::bad_alloc &) { + QSKIP("Could not allocate 4GiB plus one byte of RAM."); + } + QCOMPARE(large.size(), 4 * GiB + 1); + large.back() = '\1'; + qDebug("created dataset in %lld ms", timer.elapsed()); + + QTest::addColumn("algorithm"); + auto me = QMetaEnum::fromType(); + auto row = [me] (QCryptographicHash::Algorithm algo) { + QTest::addRow("%s", me.valueToKey(int(algo))) << algo; + }; + // these are reasonably fast (O(secs)) + row(QCryptographicHash::Md4); + row(QCryptographicHash::Md5); + row(QCryptographicHash::Sha1); + // this is already significantly slower, but important (O(min) + row(QCryptographicHash::Sha512); + // the rest is just too slow +#else + QSKIP("This test is 64-bit only."); +#endif +} + +void tst_QCryptographicHash::moreThan4GiBOfData() +{ + QFETCH(const QCryptographicHash::Algorithm, algorithm); + +# if QT_CONFIG(cxx11_future) + using MaybeThread = std::thread; +# else + struct MaybeThread { + std::function func; + void join() { func(); } + }; +# endif + + QElapsedTimer timer; + timer.start(); + const auto sg = qScopeGuard([&] { + qDebug() << algorithm << "test finished in" << timer.restart() << "ms"; + }); + + const auto begin = large.data(); + const auto mid = begin + large.size() / 2; + const auto end = begin + large.size(); + + QByteArray single; + QByteArray chunked; + + auto t = MaybeThread{[&] { + QCryptographicHash h(algorithm); + h.addData(begin, end - begin); + single = h.result(); + }}; + { + QCryptographicHash h(algorithm); + h.addData(begin, mid - begin); + h.addData(mid, end - mid); + chunked = h.result(); + } + t.join(); + + QCOMPARE(single, chunked); +} + QTEST_MAIN(tst_QCryptographicHash) #include "tst_qcryptographichash.moc"