From af456842e13ab83cfeb44f3638b62652b201281c Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Wed, 11 Oct 2017 15:28:40 +0200 Subject: Change QRandomGenerator to have a deterministic mode Now only QRandomGenerator::system() will access the system-wide RNG, which we document to be cryptographically-safe and possibly backed by a true HWRNG. Everything else just wraps a Mersenne Twister. Change-Id: I0a103569c81b4711a649fffd14ec8cd3469425df Reviewed-by: Lars Knoll --- .../qrandomgenerator/tst_qrandomgenerator.cpp | 334 ++++++++++++++++----- 1 file changed, 253 insertions(+), 81 deletions(-) (limited to 'tests/auto/corelib') diff --git a/tests/auto/corelib/global/qrandomgenerator/tst_qrandomgenerator.cpp b/tests/auto/corelib/global/qrandomgenerator/tst_qrandomgenerator.cpp index e583766f21..f9b3ce5390 100644 --- a/tests/auto/corelib/global/qrandomgenerator/tst_qrandomgenerator.cpp +++ b/tests/auto/corelib/global/qrandomgenerator/tst_qrandomgenerator.cpp @@ -43,8 +43,8 @@ #define COMMA , #define QVERIFY_3TIMES(statement) \ do {\ - if (!QTest::qVerify(static_cast(statement), #statement, "1st try", __FILE__, __LINE__))\ - if (!QTest::qVerify(static_cast(statement), #statement, "2nd try", __FILE__, __LINE__))\ + if (!static_cast(statement))\ + if (!static_cast(statement))\ if (!QTest::qVerify(static_cast(statement), #statement, "3rd try", __FILE__, __LINE__))\ return;\ } while (0) @@ -71,6 +71,13 @@ public slots: void cleanup() { setRNGControl(0); } private slots: + void basics(); + void knownSequence(); + void copying(); + void copyingGlobal(); + void copyingSystem(); + void systemRng(); + void generate32_data(); void generate32(); void generate64_data() { generate32_data(); } @@ -110,18 +117,154 @@ private slots: void stdRandomDistributions(); }; +// The first 20 results of the sequence: +static const quint32 defaultRngResults[] = { + 853323747U, 2396352728U, 3025954838U, 2985633182U, 2815751046U, + 340588426U, 3587208406U, 298087538U, 2912478009U, 3642122814U, + 3202916223U, 799257577U, 1872145992U, 639469699U, 3201121432U, + 2388658094U, 1735523408U, 2215232359U, 668106566U, 2554687763U +}; + + using namespace std; QT_WARNING_DISABLE_GCC("-Wfloat-equal") QT_WARNING_DISABLE_CLANG("-Wfloat-equal") +struct RandomGenerator : public QRandomGenerator +{ + RandomGenerator(uint control) + : QRandomGenerator(control ? + QRandomGenerator(control & RandomDataMask) : + *QRandomGenerator::global()) + { + setRNGControl(control); + } +}; + +void tst_QRandomGenerator::basics() +{ + // default constructible + QRandomGenerator rng; + + // copyable && movable + rng = rng; + rng = std::move(rng); + + // 64-bit + QRandomGenerator64 rng64; + rng64 = rng64; + rng64 = std::move(rng64); + + // 32- and 64-bit should be interchangeable: + rng = rng64; + rng64 = rng; + rng = std::move(rng64); + rng64 = std::move(rng); + + // access global + QRandomGenerator *global = QRandomGenerator::global(); + QRandomGenerator globalCopy = *global; + globalCopy = *global; + QRandomGenerator64 *global64 = QRandomGenerator64::global(); + QRandomGenerator64 globalCopy64 = *global64; + globalCopy64 = *global64; + + // access system + QRandomGenerator *system = QRandomGenerator::system(); + QRandomGenerator systemRng = *system; + systemRng = *system; + + QRandomGenerator64 *system64 = QRandomGenerator64::system(); + QRandomGenerator64 systemRng64 = *system64; + systemRng64 = *system64; + + Q_STATIC_ASSERT(std::is_same::value); + Q_STATIC_ASSERT(std::is_samegenerate()) COMMA quint64>::value); +} + +void tst_QRandomGenerator::knownSequence() +{ + QRandomGenerator rng; + for (quint32 x : defaultRngResults) + QCOMPARE(rng(), x); +} + +void tst_QRandomGenerator::copying() +{ + QRandomGenerator rng1; + QRandomGenerator rng2 = rng1; + + quint32 samples[20]; + rng1.fillRange(samples); + + // should produce the same sequence, whichever it was + for (quint32 x : samples) + QCOMPARE(rng2(), x); +} + +void tst_QRandomGenerator::copyingGlobal() +{ + QRandomGenerator &global = *QRandomGenerator::global(); + QRandomGenerator copy = global; + + quint32 samples[20]; + global.fillRange(samples); + + // should produce the same sequence, whichever it was + for (quint32 x : samples) + QCOMPARE(copy(), x); +} + +void tst_QRandomGenerator::copyingSystem() +{ + QRandomGenerator &system = *QRandomGenerator::system(); + QRandomGenerator copy = system; + QRandomGenerator copy2 = copy; + copy2 = copy; + + quint32 samples[20]; + copy2.fillRange(samples); + + // should NOT produce the same sequence, whichever it was + int sameCount = 0; + for (quint32 x : samples) + sameCount += (copy() == x); + QVERIFY(sameCount < 20); +} + +void tst_QRandomGenerator::systemRng() +{ + QRandomGenerator *rng = QRandomGenerator::system(); + rng->generate(); + rng->generate64(); + rng->generateDouble(); + rng->bounded(100); + rng->bounded(100U); + +#ifdef QT_BUILD_INTERNAL + quint32 setpoint = std::numeric_limits::max(); + ++setpoint; + quint64 setpoint64 = quint64(setpoint) << 32 | setpoint; + setRNGControl(SetRandomData | setpoint); + + QCOMPARE(rng->generate(), setpoint); + QCOMPARE(rng->generate64(), setpoint64); + QCOMPARE(rng->generateDouble(), ldexp(setpoint64, -64)); + QCOMPARE(rng->bounded(100), 50); +#endif +} + void tst_QRandomGenerator::generate32_data() { QTest::addColumn("control"); - QTest::newRow("default") << 0U; + QTest::newRow("fixed") << (RandomValue32 & RandomDataMask); + QTest::newRow("global") << 0U; #ifdef QT_BUILD_INTERNAL - QTest::newRow("system") << uint(SkipHWRNG); + if (qt_has_hwrng()) + QTest::newRow("hwrng") << uint(UseSystemRNG); + QTest::newRow("system") << uint(UseSystemRNG | SkipHWRNG); # ifdef HAVE_FALLBACK_ENGINE - QTest::newRow("fallback") << uint(SkipHWRNG | SkipSystemRNG); + QTest::newRow("system-fallback") << uint(UseSystemRNG | SkipHWRNG | SkipSystemRNG); # endif #endif } @@ -129,39 +272,40 @@ void tst_QRandomGenerator::generate32_data() void tst_QRandomGenerator::generate32() { QFETCH(uint, control); - setRNGControl(control); + RandomGenerator rng(control); for (int i = 0; i < 4; ++i) { - QVERIFY_3TIMES([] { - quint32 value = QRandomGenerator::generate(); + QVERIFY_3TIMES([&] { + quint32 value = rng.generate(); return value != 0 && value != RandomValue32; }()); } // and should hopefully be different from repeated calls for (int i = 0; i < 4; ++i) - QVERIFY_3TIMES(QRandomGenerator::generate() != QRandomGenerator::generate()); + QVERIFY_3TIMES(rng.generate() != rng.generate()); } void tst_QRandomGenerator::generate64() { QFETCH(uint, control); - setRNGControl(control); + RandomGenerator rng(control); + QVERIFY_3TIMES(rng.generate64() > std::numeric_limits::max()); for (int i = 0; i < 4; ++i) { - QVERIFY_3TIMES([] { - quint64 value = QRandomGenerator::generate(); + QVERIFY_3TIMES([&] { + quint64 value = rng.generate64(); return value != 0 && value != RandomValue32 && value != RandomValue64; }()); } // and should hopefully be different from repeated calls for (int i = 0; i < 4; ++i) - QVERIFY_3TIMES(QRandomGenerator::generate64() != QRandomGenerator::generate64()); + QVERIFY_3TIMES(rng.generate64() != rng.generate64()); for (int i = 0; i < 4; ++i) - QVERIFY_3TIMES(QRandomGenerator::generate() != quint32(QRandomGenerator::generate64())); + QVERIFY_3TIMES(rng.generate() != quint32(rng.generate64())); for (int i = 0; i < 4; ++i) - QVERIFY_3TIMES(QRandomGenerator::generate() != (QRandomGenerator::generate64() >> 32)); + QVERIFY_3TIMES(rng.generate() != (rng.generate64() >> 32)); } void tst_QRandomGenerator::quality() @@ -190,7 +334,9 @@ void tst_QRandomGenerator::quality() Q_STATIC_ASSERT(FailureThreshold > AcceptableThreshold); QFETCH(uint, control); - setRNGControl(control); + if (control & RandomDataMask) + return; + RandomGenerator rng(control); int histogram[UCHAR_MAX + 1]; memset(histogram, 0, sizeof(histogram)); @@ -199,7 +345,7 @@ void tst_QRandomGenerator::quality() // test the quality of the generator quint32 buffer[BufferCount]; memset(buffer, 0xcc, sizeof(buffer)); - generate_n(buffer, +BufferCount, [] { return QRandomGenerator::generate(); }); + generate_n(buffer, +BufferCount, [&] { return rng.generate(); }); quint8 *ptr = reinterpret_cast(buffer); quint8 *end = ptr + sizeof(buffer); @@ -224,20 +370,20 @@ void tst_QRandomGenerator::quality() template void fillRange_template() { QFETCH(uint, control); - setRNGControl(control); + RandomGenerator rng(control); for (int i = 0; i < 4; ++i) { - QVERIFY_3TIMES([] { + QVERIFY_3TIMES([&] { T value[1] = { RandomValue32 }; - QRandomGenerator::fillRange(value); + rng.fillRange(value); return value[0] != 0 && value[0] != RandomValue32; }()); } for (int i = 0; i < 4; ++i) { - QVERIFY_3TIMES([] { + QVERIFY_3TIMES([&] { T array[2] = {}; - QRandomGenerator::fillRange(array); + rng.fillRange(array); return array[0] != array[1]; }()); } @@ -245,18 +391,18 @@ template void fillRange_template() if (sizeof(T) > sizeof(quint32)) { // just to shut up a warning about shifting uint more than the width enum { Shift = sizeof(T) / 2 * CHAR_BIT }; - QVERIFY_3TIMES([] { + QVERIFY_3TIMES([&] { T value[1] = { }; - QRandomGenerator::fillRange(value); + rng.fillRange(value); return quint32(value[0] >> Shift) != quint32(value[0]); }()); } // fill in a longer range - auto longerArrayCheck = [] { + auto longerArrayCheck = [&] { T array[32]; memset(array, 0, sizeof(array)); - QRandomGenerator::fillRange(array); + rng.fillRange(array); if (sizeof(T) == sizeof(RandomValue64) && find(begin(array), end(array), RandomValue64) != end(array)) return false; @@ -273,11 +419,11 @@ void tst_QRandomGenerator::fillRangeULLong() { fillRange_template(); template void generate_template() { QFETCH(uint, control); - setRNGControl(control); + RandomGenerator rng(control); // almost the same as fillRange, but limited to 32 bits for (int i = 0; i < 4; ++i) { - QVERIFY_3TIMES([] { + QVERIFY_3TIMES([&] { T value[1] = { RandomValue32 }; QRandomGenerator().generate(begin(value), end(value)); return value[0] != 0 && value[0] != RandomValue32 @@ -286,10 +432,10 @@ template void generate_template() } // fill in a longer range - auto longerArrayCheck = [] { + auto longerArrayCheck = [&] { T array[72] = {}; // at least 256 bytes QRandomGenerator().generate(begin(array), end(array)); - return find_if(begin(array), end(array), [](T cur) { + return find_if(begin(array), end(array), [&](T cur) { return cur == 0 || cur == RandomValue32 || cur == RandomValue64 || cur > numeric_limits::max(); }) == end(array); @@ -303,12 +449,12 @@ void tst_QRandomGenerator::generateULLong() { generate_template(); } void tst_QRandomGenerator::generateNonContiguous() { QFETCH(uint, control); - setRNGControl(control); + RandomGenerator rng(control); QLinkedList list = { 0, 0, 0, 0, 0, 0, 0, 0 }; auto longerArrayCheck = [&] { QRandomGenerator().generate(list.begin(), list.end()); - return find_if(list.begin(), list.end(), [](quint64 cur) { + return find_if(list.begin(), list.end(), [&](quint64 cur) { return cur == 0 || cur == RandomValue32 || cur == RandomValue64 || cur > numeric_limits::max(); }) == list.end(); @@ -326,7 +472,7 @@ void tst_QRandomGenerator::bounded_data() QTest::addColumn("sup"); QTest::addColumn("expected"); - auto newRow = [](quint32 val, quint32 sup) { + auto newRow = [&](quint32 val, quint32 sup) { // calculate the scaled value quint64 scaled = val; scaled <<= 32; @@ -352,31 +498,31 @@ void tst_QRandomGenerator::bounded() QFETCH(uint, control); QFETCH(quint32, sup); QFETCH(quint32, expected); - setRNGControl(control); + RandomGenerator rng(control); - quint32 value = QRandomGenerator::bounded(sup); + quint32 value = rng.bounded(sup); QVERIFY(value < sup); QCOMPARE(value, expected); - int ivalue = QRandomGenerator::bounded(sup); + int ivalue = rng.bounded(sup); QVERIFY(ivalue < int(sup)); QCOMPARE(ivalue, int(expected)); // confirm only the bound now - setRNGControl(control & (SkipHWRNG|SkipSystemRNG)); - value = QRandomGenerator::bounded(sup); + setRNGControl(control & (SkipHWRNG|SkipSystemRNG|UseSystemRNG)); + value = rng.bounded(sup); QVERIFY(value < sup); - value = QRandomGenerator::bounded(sup / 2, 3 * sup / 2); + value = rng.bounded(sup / 2, 3 * sup / 2); QVERIFY(value >= sup / 2); QVERIFY(value < 3 * sup / 2); - ivalue = QRandomGenerator::bounded(-int(sup), int(sup)); + ivalue = rng.bounded(-int(sup), int(sup)); QVERIFY(ivalue >= -int(sup)); QVERIFY(ivalue < int(sup)); // wholly negative range - ivalue = QRandomGenerator::bounded(-int(sup), 0); + ivalue = rng.bounded(-int(sup), 0); QVERIFY(ivalue >= -int(sup)); QVERIFY(ivalue < 0); } @@ -407,7 +553,9 @@ void tst_QRandomGenerator::boundedQuality() Q_STATIC_ASSERT(FailureThreshold > AcceptableThreshold); QFETCH(uint, control); - setRNGControl(control); + if (control & RandomDataMask) + return; + RandomGenerator rng(control); int histogram[Bound]; memset(histogram, 0, sizeof(histogram)); @@ -415,7 +563,7 @@ void tst_QRandomGenerator::boundedQuality() { // test the quality of the generator QVector buffer(BufferCount, 0xcdcdcdcd); - generate(buffer.begin(), buffer.end(), [] { return QRandomGenerator::bounded(Bound); }); + generate(buffer.begin(), buffer.end(), [&] { return rng.bounded(Bound); }); for (quint32 value : qAsConst(buffer)) { QVERIFY(value < Bound); @@ -441,24 +589,26 @@ void tst_QRandomGenerator::boundedQuality() void tst_QRandomGenerator::generateReal() { QFETCH(uint, control); - setRNGControl(control); + RandomGenerator rng(control); for (int i = 0; i < 4; ++i) { - QVERIFY_3TIMES([] { - qreal value = QRandomGenerator::generateDouble(); + QVERIFY_3TIMES([&] { + qreal value = rng.generateDouble(); return value >= 0 && value < 1 && value != RandomValueFP; }()); } // and should hopefully be different from repeated calls for (int i = 0; i < 4; ++i) - QVERIFY_3TIMES(QRandomGenerator::generateDouble() != QRandomGenerator::generateDouble()); + QVERIFY_3TIMES(rng.generateDouble() != rng.generateDouble()); } void tst_QRandomGenerator::qualityReal() { QFETCH(uint, control); - setRNGControl(control); + if (control & RandomDataMask) + return; + RandomGenerator rng(control); enum { SampleSize = 160, @@ -474,7 +624,7 @@ void tst_QRandomGenerator::qualityReal() }; double data[SampleSize]; - std::generate(std::begin(data), std::end(data), &QRandomGenerator::generateDouble); + std::generate(std::begin(data), std::end(data), [&rng] { return rng.generateDouble(); }); int aboveHalf = 0; int belowOneEighth = 0; @@ -503,12 +653,22 @@ void tst_QRandomGenerator::qualityReal() template void seedStdRandomEngine() { - QRandomGenerator rd; - Engine e(rd); - QVERIFY_3TIMES(e() != 0); + { + QRandomGenerator &rd = *QRandomGenerator::system(); + Engine e(rd); + QVERIFY_3TIMES(e() != 0); + + e.seed(rd); + QVERIFY_3TIMES(e() != 0); + } + { + QRandomGenerator64 &rd = *QRandomGenerator64::system(); + Engine e(rd); + QVERIFY_3TIMES(e() != 0); - e.seed(rd); - QVERIFY_3TIMES(e() != 0); + e.seed(rd); + QVERIFY_3TIMES(e() != 0); + } } void tst_QRandomGenerator::seedStdRandomEngines() @@ -533,12 +693,16 @@ void tst_QRandomGenerator::stdUniformIntDistribution_data() QTest::addColumn("control"); QTest::addColumn("max"); - auto newRow = [](quint32 max) { - QTest::addRow("default:%u", max) << 0U << max; - QTest::addRow("system:%u", max) << uint(SkipHWRNG) << max; - #ifdef HAVE_FALLBACK_ENGINE - QTest::addRow("fallback:%u", max) << uint(SkipHWRNG | SkipSystemRNG) << max; - #endif + auto newRow = [&](quint32 max) { +#ifdef QT_BUILD_INTERNAL + if (qt_has_hwrng()) + QTest::addRow("hwrng:%u", max) << uint(UseSystemRNG) << max; + QTest::addRow("system:%u", max) << uint(UseSystemRNG | SkipHWRNG) << max; +# ifdef HAVE_FALLBACK_ENGINE + QTest::addRow("system-fallback:%u", max) << uint(UseSystemRNG | SkipHWRNG | SkipSystemRNG) << max; +# endif +#endif + QTest::addRow("global:%u", max) << 0U << max; }; // useless: we can only generate zeroes: @@ -553,7 +717,7 @@ void tst_QRandomGenerator::stdUniformIntDistribution() { QFETCH(uint, control); QFETCH(quint32, max); - setRNGControl(control & (SkipHWRNG|SkipSystemRNG)); + RandomGenerator rng(control); { QRandomGenerator rd; @@ -621,21 +785,19 @@ void tst_QRandomGenerator::stdGenerateCanonical() QSKIP("MSVC 2013's std::generate_canonical is broken"); #else QFETCH(uint, control); - setRNGControl(control); + RandomGenerator rng(control); for (int i = 0; i < 4; ++i) { - QVERIFY_3TIMES([] { - QRandomGenerator rd; - qreal value = std::generate_canonical(rd); + QVERIFY_3TIMES([&] { + qreal value = std::generate_canonical(rng); return value > 0 && value < 1 && value != RandomValueFP; }()); } // and should hopefully be different from repeated calls - QRandomGenerator rd; for (int i = 0; i < 4; ++i) - QVERIFY_3TIMES(std::generate_canonical(rd) != - std::generate_canonical(rd)); + QVERIFY_3TIMES(std::generate_canonical(rng) != + std::generate_canonical(rng)); #endif } @@ -649,12 +811,16 @@ void tst_QRandomGenerator::stdUniformRealDistribution_data() QTest::addColumn("min"); QTest::addColumn("sup"); - auto newRow = [](double min, double sup) { - QTest::addRow("default:%g-%g", min, sup) << 0U << min << sup; - QTest::addRow("system:%g-%g", min, sup) << uint(SkipHWRNG) << min << sup; - #ifdef HAVE_FALLBACK_ENGINE - QTest::addRow("fallback:%g-%g", min, sup) << uint(SkipHWRNG | SkipSystemRNG) << min << sup; - #endif + auto newRow = [&](double min, double sup) { +#ifdef QT_BUILD_INTERNAL + if (qt_has_hwrng()) + QTest::addRow("hwrng:%g-%g", min, sup) << uint(UseSystemRNG) << min << sup; + QTest::addRow("system:%g-%g", min, sup) << uint(UseSystemRNG | SkipHWRNG) << min << sup; +# ifdef HAVE_FALLBACK_ENGINE + QTest::addRow("system-fallback:%g-%g", min, sup) << uint(UseSystemRNG | SkipHWRNG | SkipSystemRNG) << min << sup; +# endif +#endif + QTest::addRow("global:%g-%g", min, sup) << 0U << min << sup; }; newRow(0, 0); // useless: we can only generate zeroes @@ -670,7 +836,7 @@ void tst_QRandomGenerator::stdUniformRealDistribution() QFETCH(uint, control); QFETCH(double, min); QFETCH(double, sup); - setRNGControl(control & (SkipHWRNG|SkipSystemRNG)); + RandomGenerator rng(control & (SkipHWRNG|SkipSystemRNG|UseSystemRNG)); { QRandomGenerator rd; @@ -695,13 +861,9 @@ void tst_QRandomGenerator::stdUniformRealDistribution() } } -void tst_QRandomGenerator::stdRandomDistributions() +template void stdRandomDistributions_template() { - // just a compile check for some of the distributions, besides - // std::uniform_int_distribution and std::uniform_real_distribution (tested - // above) - - QRandomGenerator rd; + Generator rd; std::bernoulli_distribution()(rd); @@ -723,6 +885,16 @@ void tst_QRandomGenerator::stdRandomDistributions() } } +void tst_QRandomGenerator::stdRandomDistributions() +{ + // just a compile check for some of the distributions, besides + // std::uniform_int_distribution and std::uniform_real_distribution (tested + // above) + + stdRandomDistributions_template(); + stdRandomDistributions_template(); +} + QTEST_APPLESS_MAIN(tst_QRandomGenerator) #include "tst_qrandomgenerator.moc" -- cgit v1.2.3