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 <lars.knoll@qt.io>
bb10
Thiago Macieira 2017-10-11 15:28:40 +02:00
parent 4502999ff0
commit af456842e1
4 changed files with 844 additions and 394 deletions

View File

@ -43,10 +43,8 @@
#include "qrandom.h"
#include "qrandom_p.h"
#include <qobjectdefs.h>
#include <qmutex.h>
#include <qthreadstorage.h>
#include <private/qsimd_p.h>
#include <random>
#include <errno.h>
@ -86,6 +84,7 @@ DECLSPEC_IMPORT BOOLEAN WINAPI SystemFunction036(PVOID RandomBuffer, ULONG Rando
#undef Q_ASSERT_X
#undef Q_ASSERT
#define Q_ASSERT(cond) assert(cond)
#define Q_ASSERT_X(cond, x, msg) assert(cond && msg)
#if defined(QT_NO_DEBUG) && !defined(QT_FORCE_ASSERTS)
# define NDEBUG 1
#endif
@ -122,13 +121,41 @@ static QT_FUNCTION_TARGET(RDRND) qssize_t qt_random_cpu(void *buffer, qssize_t c
out:
return ptr - reinterpret_cast<unsigned *>(buffer);
}
#else
static qssize_t qt_random_cpu(void *, qssize_t)
{
return 0;
}
#endif
namespace {
#if QT_CONFIG(getentropy)
class SystemRandom
static QBasicMutex globalPRNGMutex;
struct PRNGLocker
{
public:
const bool locked;
PRNGLocker(const QRandomGenerator *that)
: locked(that == nullptr || that == QRandomGenerator::global())
{
if (locked)
globalPRNGMutex.lock();
}
~PRNGLocker()
{
if (locked)
globalPRNGMutex.unlock();
}
};
}
enum {
// may be "overridden" by a member enum
FillBufferNoexcept = true
};
struct QRandomGenerator::SystemGenerator : public QRandomGenerator::SystemGeneratorBase
{
#if QT_CONFIG(getentropy)
static qssize_t fillBuffer(void *buffer, qssize_t count) Q_DECL_NOTHROW
{
// getentropy can read at most 256 bytes, so break the reading
@ -146,94 +173,94 @@ public:
Q_UNUSED(ret);
return count;
}
};
#elif defined(Q_OS_UNIX)
class SystemRandom
{
static QBasicAtomicInt s_fdp1; // "file descriptor plus 1"
static int openDevice();
QBasicAtomicInt fdp1; // "file descriptor plus 1"
int openDevice()
{
int fd = fdp1.loadAcquire() - 1;
if (fd != -1)
return fd;
fd = qt_safe_open("/dev/urandom", O_RDONLY);
if (fd == -1)
fd = qt_safe_open("/dev/random", O_RDONLY | O_NONBLOCK);
if (fd == -1) {
// failed on both, set to -2 so we won't try again
fd = -2;
}
int opened_fdp1;
if (fdp1.testAndSetOrdered(0, fd + 1, opened_fdp1))
return fd;
// failed, another thread has opened the file descriptor
if (fd >= 0)
qt_safe_close(fd);
return opened_fdp1 - 1;
}
#ifdef Q_CC_GNU
// If it's not GCC or GCC-like, then we'll leak the file descriptor
__attribute__((destructor))
#endif
static void closeDevice();
SystemRandom() {}
public:
enum { EfficientBufferFill = true };
static qssize_t fillBuffer(void *buffer, qssize_t count);
};
QBasicAtomicInt SystemRandom::s_fdp1 = Q_BASIC_ATOMIC_INITIALIZER(0);
void SystemRandom::closeDevice()
{
int fd = s_fdp1.loadAcquire() - 1;
if (fd >= 0)
qt_safe_close(fd);
}
int SystemRandom::openDevice()
{
int fd = s_fdp1.loadAcquire() - 1;
if (fd != -1)
return fd;
fd = qt_safe_open("/dev/urandom", O_RDONLY);
if (fd == -1)
fd = qt_safe_open("/dev/random", O_RDONLY | O_NONBLOCK);
if (fd == -1) {
// failed on both, set to -2 so we won't try again
fd = -2;
static void closeDevice()
{
int fd = static_cast<SystemGenerator &>(system()->storage.sys).fdp1.load() - 1;
if (fd >= 0)
qt_safe_close(fd);
}
int opened_fdp1;
if (s_fdp1.testAndSetOrdered(0, fd + 1, opened_fdp1)) {
if (fd >= 0) {
static const SystemRandom closer;
Q_UNUSED(closer);
}
return fd;
SystemGenerator() : fdp1 Q_BASIC_ATOMIC_INITIALIZER(0) {}
qssize_t fillBuffer(void *buffer, qssize_t count)
{
int fd = openDevice();
if (Q_UNLIKELY(fd < 0))
return 0;
qint64 n = qt_safe_read(fd, buffer, count);
return qMax<qssize_t>(n, 0); // ignore any errors
}
// failed, another thread has opened the file descriptor
if (fd >= 0)
qt_safe_close(fd);
return opened_fdp1 - 1;
}
qssize_t SystemRandom::fillBuffer(void *buffer, qssize_t count)
{
int fd = openDevice();
if (Q_UNLIKELY(fd < 0))
return 0;
qint64 n = qt_safe_read(fd, buffer, count);
return qMax<qssize_t>(n, 0); // ignore any errors
}
#endif // Q_OS_UNIX
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
class SystemRandom
{
public:
static qssize_t fillBuffer(void *buffer, qssize_t count) Q_DECL_NOTHROW
#elif defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
qssize_t fillBuffer(void *buffer, qssize_t count) Q_DECL_NOTHROW
{
auto RtlGenRandom = SystemFunction036;
return RtlGenRandom(buffer, ULONG(count)) ? count: 0;
}
};
#elif defined(Q_OS_WINRT)
class SystemRandom
{
public:
static qssize_t fillBuffer(void *, qssize_t) Q_DECL_NOTHROW
qssize_t fillBuffer(void *, qssize_t) Q_DECL_NOTHROW
{
// always use the fallback
return 0;
}
};
#endif // Q_OS_WINRT
} // unnamed namespace
static SystemGenerator &self()
{
return static_cast<SystemGenerator &>(QRandomGenerator::system()->storage.sys);
}
void generate(quint32 *begin, quint32 *end) Q_DECL_NOEXCEPT_EXPR(FillBufferNoexcept);
// For std::mersenne_twister_engine implementations that use something
// other than quint32 (unsigned int) to fill their buffers.
template <typename T> void generate(T *begin, T *end)
{
Q_STATIC_ASSERT(sizeof(T) >= sizeof(quint32));
if (sizeof(T) == sizeof(quint32)) {
// Microsoft Visual Studio uses unsigned long, but that's still 32-bit
generate(reinterpret_cast<quint32 *>(begin), reinterpret_cast<quint32 *>(end));
} else {
// Slow path. Fix your C++ library.
std::generate(begin, end, [this]() {
quint32 datum;
generate(&datum, &datum + 1);
return datum;
});
}
}
};
#if defined(Q_OS_WIN)
static void fallback_update_seed(unsigned) {}
@ -252,6 +279,7 @@ static void fallback_update_seed(unsigned) {}
static void fallback_fill(quint32 *, qssize_t) Q_DECL_NOTHROW
{
// no fallback necessary, getentropy cannot fail under normal circumstances
Q_UNREACHABLE();
}
#elif defined(Q_OS_BSD4)
static void fallback_update_seed(unsigned) {}
@ -347,26 +375,11 @@ static void fallback_fill(quint32 *ptr, qssize_t left) Q_DECL_NOTHROW
}
#endif
static qssize_t fill_cpu(quint32 *buffer, qssize_t count) Q_DECL_NOTHROW
Q_NEVER_INLINE void QRandomGenerator::SystemGenerator::generate(quint32 *begin, quint32 *end)
Q_DECL_NOEXCEPT_EXPR(FillBufferNoexcept)
{
#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND)
if (qCpuHasFeature(RDRND) && (uint(qt_randomdevice_control) & SkipHWRNG) == 0)
return qt_random_cpu(buffer, count);
#else
Q_UNUSED(buffer);
Q_UNUSED(count);
#endif
return 0;
}
static Q_NEVER_INLINE void fill(void *begin, void *end)
Q_DECL_NOEXCEPT_EXPR(noexcept(SystemRandom::fillBuffer(nullptr, 1)))
{
// Verify that the pointers are properly aligned for 32-bit
Q_ASSERT(quintptr(begin) % sizeof(quint32) == 0);
Q_ASSERT(quintptr(end) % sizeof(quint32) == 0);
quint32 *buffer = reinterpret_cast<quint32 *>(begin);
qssize_t count = reinterpret_cast<quint32 *>(end) - buffer;
quint32 *buffer = begin;
qssize_t count = end - begin;
if (Q_UNLIKELY(uint(qt_randomdevice_control) & SetRandomData)) {
uint value = uint(qt_randomdevice_control) & RandomDataMask;
@ -374,10 +387,13 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
return;
}
qssize_t filled = fill_cpu(buffer, count);
qssize_t filled = 0;
if (qt_has_hwrng() && (uint(qt_randomdevice_control) & SkipHWRNG) == 0)
filled += qt_random_cpu(buffer, count);
if (filled != count && (uint(qt_randomdevice_control) & SkipSystemRNG) == 0) {
qssize_t bytesFilled =
SystemRandom::fillBuffer(buffer + filled, (count - filled) * qssize_t(sizeof(*buffer)));
fillBuffer(buffer + filled, (count - filled) * qssize_t(sizeof(*buffer)));
filled += bytesFilled / qssize_t(sizeof(*buffer));
}
if (filled)
@ -392,85 +408,153 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
/*!
\class QRandomGenerator
\inmodule QtCore
\reentrant
\since 5.10
\brief The QRandomGenerator class allows one to obtain random values from a
high-quality, seed-less Random Number Generator.
high-quality Random Number Generator.
QRandomGenerator may be used to generate random values from a high-quality
random number generator. Unlike qrand(), QRandomGenerator does not need to be
seeded. That also means it is not possible to force it to produce a
reliable sequence, which may be needed for debugging.
random number generator. Like the C++ random engines, QRandomGenerator can
be seeded with user-provided values through the constructor.
When seeded, the sequence of numbers generated by this
class is deterministic. That is to say, given the same seed data,
QRandomGenerator will generate the same sequence of numbers. But given
different seeds, the results should be considerably different.
QRandomGenerator::global() returns a global instance of QRandomGenerator
that Qt will ensure to be securely seeded. This object is thread-safe, may
be shared for most uses, and is always seeded from
QRandomGenerator::system()
QRandomGenerator::system() may be used to access the system's
cryptographically-safe random generator. On Unix systems, it's equivalent
to reading from \c {/dev/urandom} or the \c {getrandom()} or \c
{getentropy()} system calls.
The class can generate 32-bit or 64-bit quantities, or fill an array of
those. The most common way of generating new values is to call the generate(),
generate64() or fillRange() functions. One would use it as:
\code
quint32 value = QRandomGenerator::generate();
quint32 value = QRandomGenerator::global()->generate();
\endcode
Additionally, it provides a floating-point function generateDouble() that returns
a number in the range [0, 1) (that is, inclusive of zero and exclusive of
1). There's also a set of convenience functions that facilitate obtaining a
random number in a bounded, integral range.
Additionally, it provides a floating-point function generateDouble() that
returns a number in the range [0, 1) (that is, inclusive of zero and
exclusive of 1). There's also a set of convenience functions that
facilitate obtaining a random number in a bounded, integral range.
\warning This class is not suitable for bulk data creation. See below for the
technical reasons.
\section1 Seeding and determinism
\section1 Frequency and entropy exhaustion
QRandomGenerator may be seeded with specific seed data. When that is done,
the numbers generated by the object will always be the same, as in the
following example:
QRandomGenerator does not need to be seeded and instead uses operating system
or hardware facilities to generate random numbers. On some systems and with
certain hardware, those facilities are true Random Number Generators.
However, if they are true RNGs, those facilities have finite entropy source
and thus may fail to produce any results if the entropy pool is exhausted.
\code
QRandomGenerator prng1(1234), prng2(1234);
Q_ASSERT(prng1.generate32() == prng2.generate32());
Q_ASSERT(prng1.generate64() == prng2.generate64());
\endcode
The seed data takes the form of one or more 32-bit words. The ideal seed
size is approximately equal to the size of the QRandomGenerator class
itself. Due to mixing of the seed data, QRandomGenerator cannot guarantee
that distinct seeds will produce different sequences.
QRandomGenerator::global() is always seeded from
QRandomGenerator::system(), so it's not possible to make it produce
identical sequences.
\section1 Bulk data
When operating in deterministic mode, QRandomGenerator may be used for bulk
data generation. In fact, applications that do not need
cryptographically-secure or true random data are advised to use a regular
QRandomGenerator instead of QRandomGenerator::system() for their random
data needs.
For ease of use, QRandomGenerator provides a global object that can
be easily used, as in the following example:
\code
int x = QRandomGenerator::global()->generate32();
int y = QRandomGenerator::global()->generate32();
int w = QRandomGenerator::global()->bounded(16384);
int h = QRandomGenerator::global()->bounded(16384);
\endcode
\section1 System-wide random number generator
QRandomGenerator::system() may be used to access the system-wide random
number generator, which is cryptographically-safe on all systems that Qt
runs on. This function will use hardware facilities to generate random
numbers where available. On such systems, those facilities are true Random
Number Generators. However, if they are true RNGs, those facilities have
finite entropy sources and thus may fail to produce any results if their
entropy pool is exhausted.
If that happens, first the operating system then QRandomGenerator will fall
back to Pseudo Random Number Generators of decreasing qualities (Qt's
fallback generator being the simplest). Therefore, QRandomGenerator should
not be used for high-frequency random number generation, lest the entropy
pool become empty. As a rule of thumb, this class should not be called upon
to generate more than a kilobyte per second of random data (note: this may
vary from system to system).
fallback generator being the simplest). Whether those generators are still
of cryptographic quality is implementation-defined. Therefore,
QRandomGenerator::system() should not be used for high-frequency random
number generation, lest the entropy pool become empty. As a rule of thumb,
this class should not be called upon to generate more than a kilobyte per
second of random data (note: this may vary from system to system).
If an application needs true RNG data in bulk, it should use the operating
system facilities (such as \c{/dev/random} on Unix systems) directly and
wait for entropy to become available. If true RNG is not required,
applications should instead use a PRNG engines and can use QRandomGenerator to
seed those.
system facilities (such as \c{/dev/random} on Linux) directly and wait for
entropy to become available. If the application requires PRNG engines of
cryptographic quality but not of true randomness,
QRandomGenerator::system() may still be used (see section below).
If neither a true RNG nor a cryptographically secure PRNG are required,
applications should instead use PRNG engines like QRandomGenerator's
deterministic mode and those from the C++ Standard Library.
QRandomGenerator::system() can be used to seed those.
\section2 Fallback quality
QRandomGenerator::system() uses the operating system facilities to obtain
random numbers, which attempt to collect real entropy from the surrounding
environment to produce true random numbers. However, it's possible that the
entropy pool becomes exhausted, in which case the operating system will
fall back to a pseudo-random engine for a time. Under no circumstances will
QRandomGenerator::system() block, waiting for more entropy to be collected.
The following operating systems guarantee that the results from their
random-generation API will be of at least cryptographically-safe quality,
even if the entropy pool is exhausted: Apple OSes (Darwin), BSDs, Linux,
Windows. Barring a system installation problem (such as \c{/dev/urandom}
not being readable by the current process), QRandomGenerator::system() will
therefore have the same guarantees.
On other operating systems, QRandomGenerator will fall back to a PRNG of
good numeric distribution, but it cannot guarantee proper seeding in all
cases. Please consult the OS documentation for more information.
Applications that require QRandomGenerator not to fall back to
non-cryptographic quality generators are advised to check their operating
system documentation or restrict their deployment to one of the above.
\section1 Reentrancy and thread-safety
QRandomGenerator is reentrant, meaning that multiple threads can operate on
this class at the same time, so long as they operate on different objects.
If multiple threads need to share one PRNG sequence, external locking by a
mutex is required.
The exceptions are the objects returned by QRandomGenerator::global() and
QRandomGenerator::system(): those objects are thread-safe and may be used
by any thread without external locking. Note that thread-safety does not
extend to copying those objects: they should always be used by reference.
\section1 Standard C++ Library compatibility
QRandomGenerator is modeled after
\c{\l{http://en.cppreference.com/w/cpp/numeric/random/random_device}{std::random_device}}
and may be used in almost all contexts that the Standard Library can.
QRandomGenerator attempts to use either the same engine that backs
\c{std::random_device} or a better one. Note that \c{std::random_device} is
also allowed to fail if the source entropy pool becomes exhausted, in which
case it will throw an exception. QRandomGenerator never throws, but may abort
program execution instead.
Like the Standard Library class, QRandomGenerator can be used to seed Standard
Library deterministic random engines from \c{<random>}, such as the
Mersenne Twister. Unlike \c{std::random_device}, QRandomGenerator also
implements the API of
\c{\l{http://en.cppreference.com/w/cpp/numeric/random/seed_seq}{std::seed_seq}},
allowing it to seed the deterministic engines directly.
The following code can be used to create and seed the
implementation-defined default deterministic PRNG, then use it to fill a
block range:
\code
QRandomGenerator rd;
std::default_random_engine rng(rd);
std::generate(block.begin(), block.end(), rng);
// equivalent to:
for (auto &v : block)
v = rng();
\endcode
QRandomGenerator is modeled after the requirements for random number
engines in the C++ Standard Library and may be used in almost all contexts
that the Standard Library engines can.
QRandomGenerator is also compatible with the uniform distribution classes
\c{std::uniform_int_distribution} and \c{std:uniform_real_distribution}, as
@ -479,24 +563,85 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
[1, 2.5):
\code
QRandomGenerator64 rd;
std::uniform_real_distribution dist(1, 2.5);
return dist(rd);
return dist(*QRandomGenerator::global());
\endcode
Note the use of the QRandomGenerator64 class instead of QRandomGenerator to
obtain 64 bits of random data in a single call, though it is not required
to make the algorithm work (the Standard Library functions will make as
many calls as required to obtain enough bits of random data for the desired
range).
\sa QRandomGenerator64, qrand()
*/
/*!
\fn QRandomGenerator::QRandomGenerator()
\internal
Defaulted constructor, does nothing.
\fn QRandomGenerator::QRandomGenerator(quint32 seed)
Initializes this QRandomGenerator object with the value \a seed as
the seed. Two objects constructed with the same seed value will
produce the same number sequence.
*/
/*!
\fn QRandomGenerator::QRandomGenerator(const quint32 (&seedBuffer)[N])
\overload
Initializes this QRandomGenerator object with the values found in the
array \a seedBuffer as the seed. Two objects constructed or reseeded with
the same seed value will produce the same number sequence.
*/
/*!
\fn QRandomGenerator::QRandomGenerator(const quint32 *seedBuffer, qssize_t len)
\overload
Initializes this QRandomGenerator object with \a len values found in
the array \a seedBuffer as the seed. Two objects constructed or reseeded
with the same seed value will produce the same number sequence.
This constructor is equivalent to:
\code
std::seed_seq sseq(seedBuffer, seedBuffer + len);
QRandomGenerator generator(sseq);
\endcode
*/
/*!
\fn QRandomGenerator::QRandomGenerator(const quint32 *begin, const quin32 *end)
\overload
Initializes this QRandomGenerator object with the values found in the range
from \a begin to \a end as the seed. Two objects constructed or reseeded
with the same seed value will produce the same number sequence.
This constructor is equivalent to:
\code
std::seed_seq sseq(begin, end);
QRandomGenerator generator(sseq);
\endcode
*/
/*!
\fn QRandomGenerator::QRandomGenerator(std::seed_seq &sseq)
\overload
Initializes this QRandomGenerator object with the seed sequence \a
sseq as the seed. Two objects constructed or reseeded with the same seed
value will produce the same number sequence.
*/
/*!
\fn QRandomGenerator::QRandomGenerator(const QRandomGenerator &other)
Creates a copy of the generator state in the \a other object. If \a other is
QRandomGenerator::system() or a copy of that, this object will also read
from the operating system random-generating facilities. In that case, the
sequences generated by the two objects will be different.
In all other cases, the new QRandomGenerator object will start at the same
position in the deterministic sequence as the \a other object was. Both
objects will generate the same sequence from this point on.
For that reason, it is not adviseable to create a copy of
QRandomGenerator::global(). If one needs an exclusive deterministic
generator, consider instead creating a new object and seeding it from
QRandomGenerator::system().
*/
/*!
@ -512,24 +657,7 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
Generates a 32-bit random quantity and returns it.
\sa QRandomGenerator::generate(), QRandomGenerator::generate64()
*/
/*!
\fn double QRandomGenerator::entropy() const
Returns the estimate of the entropy in the random generator source.
This function exists to comply with the Standard Library requirements for
\c{\l{http://en.cppreference.com/w/cpp/numeric/random/random_device}{std::random_device}}
but it does not and cannot ever work. It is not possible to obtain a
reliable entropy value in a shared entropy pool in a multi-tasking system,
as other processes or threads may use that entropy. Any value non-zero
value that this function could return would be obsolete by the time the
user code reached it.
Since QRandomGenerator attempts to use a hardware Random Number Generator,
this function always returns 0.0.
\sa generate(), generate64()
*/
/*!
@ -537,7 +665,7 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
Returns the minimum value that QRandomGenerator may ever generate. That is, 0.
\sa max(), QRandomGenerator64::max()
\sa max(), QRandomGenerator64::min()
*/
/*!
@ -556,7 +684,7 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
and \a end. This function is equivalent to (and is implemented as):
\code
std::generate(begin, end, []() { return generate(); });
std::generate(begin, end, [this]() { return generate(); });
\endcode
This function complies with the requirements for the function
@ -569,7 +697,7 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
quantities, one can write:
\code
std::generate(begin, end, []() { return QRandomGenerator::generate64(); });
std::generate(begin, end, []() { return QRandomGenerator::global()->generate64(); });
\endcode
If the range refers to contiguous memory (such as an array or the data from
@ -647,26 +775,26 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
*/
/*!
\fn qreal QRandomGenerator::bounded(qreal sup)
\fn qreal QRandomGenerator::bounded(qreal highest)
Generates one random qreal in the range between 0 (inclusive) and \a
sup (exclusive). This function is equivalent to and is implemented as:
highest (exclusive). This function is equivalent to and is implemented as:
\code
return generateDouble() * sup;
return generateDouble() * highest;
\endcode
\sa generateDouble(), bounded()
*/
/*!
\fn quint32 QRandomGenerator::bounded(quint32 sup)
\fn quint32 QRandomGenerator::bounded(quint32 highest)
\overload
Generates one random 32-bit quantity in the range between 0 (inclusive) and
\a sup (exclusive). The same result may also be obtained by using
\a highest (exclusive). The same result may also be obtained by using
\c{\l{http://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution}{std::uniform_int_distribution}}
with parameters 0 and \c{sup - 1}. That class can also be used to obtain
with parameters 0 and \c{highest - 1}. That class can also be used to obtain
quantities larger than 32 bits.
For example, to obtain a value between 0 and 255 (inclusive), one would write:
@ -685,11 +813,11 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
*/
/*!
\fn quint32 QRandomGenerator::bounded(int sup)
\fn quint32 QRandomGenerator::bounded(int highest)
\overload
Generates one random 32-bit quantity in the range between 0 (inclusive) and
\a sup (exclusive). \a sup must not be negative.
\a highest (exclusive). \a highest must not be negative.
Note that this function cannot be used to obtain values in the full 32-bit
range of int. Instead, use generate() and cast to int.
@ -698,13 +826,13 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
*/
/*!
\fn quint32 QRandomGenerator::bounded(quint32 min, quint32 sup)
\fn quint32 QRandomGenerator::bounded(quint32 lowest, quint32 highest)
\overload
Generates one random 32-bit quantity in the range between \a min (inclusive)
and \a sup (exclusive). The same result may also be obtained by using
Generates one random 32-bit quantity in the range between \a lowest (inclusive)
and \a highest (exclusive). The same result may also be obtained by using
\c{\l{http://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution}{std::uniform_int_distribution}}
with parameters \a min and \c{\a sup - 1}. That class can also be used to
with parameters \a lowest and \c{\a highest - 1}. That class can also be used to
obtain quantities larger than 32 bits.
For example, to obtain a value between 1000 (incl.) and 2000 (excl.), one
@ -722,11 +850,11 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
*/
/*!
\fn quint32 QRandomGenerator::bounded(int min, int sup)
\fn quint32 QRandomGenerator::bounded(int lowest, int highest)
\overload
Generates one random 32-bit quantity in the range between \a min
(inclusive) and \a sup (exclusive), both of which may be negative.
Generates one random 32-bit quantity in the range between \a lowest
(inclusive) and \a highest (exclusive), both of which may be negative.
Note that this function cannot be used to obtain values in the full 32-bit
range of int. Instead, use generate() and cast to int.
@ -734,6 +862,54 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
\sa generate(), generate64(), generateDouble()
*/
/*!
\fn QRandomGenerator *QRandomGenerator::system()
\threadsafe
Returns a pointer to a shared QRandomGenerator that always uses the
facilities provided by the operating system to generate random numbers. The
system facilities are considered to be cryptographically safe on at least
the following operating systems: Apple OSes (Darwin), BSDs, Linux, Windows.
That may also be the case on other operating systems.
They are also possibly backed by a true hardware random number generator.
For that reason, the QRandomGenerator returned by this function should not
be used for bulk data generation. Instead, use it to seed QRandomGenerator
or a random engine from the <random> header.
The object returned by this function is thread-safe and may be used in any
thread without locks. It may also be copied and the resulting
QRandomGenerator will also access the operating system facilities, but they
will not generate the same sequence.
\sa global()
*/
/*!
\fn QRandomGenerator *QRandomGenerator::global()
\threadsafe
Returns a pointer to a shared QRandomGenerator that was seeded using
QRandomGenerator::system(). This function should be used to create random data
without the expensive creation of a securely-seeded QRandomGenerator for a
specific use or storing the rather large QRandomGenerator object.
large QRandomGenerator object.
For example, the following creates a random RGB color:
\code
return QColor::fromRgb(QRandomGenerator::global()->generate());
\endcode
Accesses to this object are thread-safe and it may therefore be used in any
thread without locks. The object may also be copied and the sequence
produced by the copy will be the same as the shared object will produce.
Note, however, that if there are other threads accessing the global object,
those threads may obtain samples at unpredictable intervals.
\sa system()
*/
/*!
\class QRandomGenerator64
\inmodule QtCore
@ -755,10 +931,11 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
*/
/*!
\fn QRandomGenerator64::QRandomGenerator64()
\internal
Defaulted constructor, does nothing.
*/
\fn QRandomGenerator64::QRandomGenerator64(const QRandomGenerator &other)
\internal
Creates a copy.
*/
/*!
\typedef QRandomGenerator64::result_type
@ -793,80 +970,78 @@ static Q_NEVER_INLINE void fill(void *begin, void *end)
\sa QRandomGenerator::generate(), QRandomGenerator::generate64()
*/
/*!
\fn double QRandomGenerator64::entropy() const
Returns the estimate of the entropy in the random generator source.
This function exists to comply with the Standard Library requirements for
\c{\l{http://en.cppreference.com/w/cpp/numeric/random/random_device}{std::random_device}}
but it does not and cannot ever work. It is not possible to obtain a
reliable entropy value in a shared entropy pool in a multi-tasking system,
as other processes or threads may use that entropy. Any value non-zero
value that this function could return would be obsolete by the time the
user code reached it.
Since QRandomGenerator64 attempts to use a hardware Random Number Generator,
this function always returns 0.0.
*/
/*!
\fn result_type QRandomGenerator64::min()
Returns the minimum value that QRandomGenerator64 may ever generate. That is, 0.
\sa max(), QRandomGenerator::max()
*/
/*!
\fn result_type QRandomGenerator64::max()
Returns the maximum value that QRandomGenerator64 may ever generate. That is,
\c {std::numeric_limits<result_type>::max()}.
\sa min(), QRandomGenerator::max()
*/
/*!
Generates one 32-bit random value and returns it.
Note about casting to a signed integer: all bits returned by this function
are random, so there's a 50% chance that the most significant bit will be
set. If you wish to cast the returned value to int and keep it positive,
you should mask the sign bit off:
\code
int value = QRandomGenerator::generate() & std::numeric_limits<int>::max();
\endcode
\sa generate64(), generateDouble()
*/
quint32 QRandomGenerator::generate()
inline QRandomGenerator::Storage::Storage()
{
quint32 ret;
fill(&ret, &ret + 1);
return ret;
// nothing
}
/*!
Generates one 64-bit random value and returns it.
Note about casting to a signed integer: all bits returned by this function
are random, so there's a 50% chance that the most significant bit will be
set. If you wish to cast the returned value to qint64 and keep it positive,
you should mask the sign bit off:
\code
qint64 value = QRandomGenerator::generate64() & std::numeric_limits<qint64>::max();
\endcode
\sa generate(), generateDouble(), QRandomGenerator64
*/
quint64 QRandomGenerator::generate64()
inline QRandomGenerator64::QRandomGenerator64(System s)
: QRandomGenerator(s)
{
quint64 ret;
fill(&ret, &ret + 1);
return ret;
}
QRandomGenerator64 *QRandomGenerator64::system()
{
static QRandomGenerator64 system(System{});
return &system;
}
QRandomGenerator64 *QRandomGenerator64::global()
{
PRNGLocker lock(nullptr);
static QRandomGenerator64 global(System{});
if (global.type == SystemRNG) {
// seed with the system CSPRNG and change the type
new (&global.storage.engine()) RandomEngine(static_cast<SystemGenerator &>(system()->storage.sys));
global.type = MersenneTwister;
}
return &global;
}
/// \internal
inline QRandomGenerator::QRandomGenerator(System)
: type(SystemRNG)
{
Q_STATIC_ASSERT(sizeof(storage) >= sizeof(SystemGenerator));
new (&storage) SystemGenerator();
}
QRandomGenerator::QRandomGenerator(const QRandomGenerator &other)
: type(other.type)
{
if (type != SystemRNG) {
PRNGLocker lock(&other);
storage.engine() = other.storage.engine();
}
}
QRandomGenerator &QRandomGenerator::operator=(const QRandomGenerator &other)
{
if (this != &other) {
if (Q_UNLIKELY(this == system()) || Q_UNLIKELY(this == global()))
qFatal("Attempted to overwrite a QRandomGenerator to system() or global().");
if ((type = other.type) != SystemRNG) {
PRNGLocker lock(&other);
storage.engine() = other.storage.engine();
}
}
return *this;
}
QRandomGenerator::QRandomGenerator(std::seed_seq &sseq) Q_DECL_NOTHROW
: type(MersenneTwister)
{
new (&storage.engine()) RandomEngine(sseq);
}
QRandomGenerator::QRandomGenerator(const quint32 *begin, const quint32 *end)
: type(MersenneTwister)
{
std::seed_seq s(begin, end);
new (&storage.engine()) RandomEngine(s);
}
/*!
@ -875,9 +1050,19 @@ quint64 QRandomGenerator::generate64()
Fills the range pointed by \a buffer and \a bufferEnd with 32-bit random
values. The buffer must be correctly aligned.
*/
void QRandomGenerator::fillRange_helper(void *buffer, void *bufferEnd)
void QRandomGenerator::_fillRange(void *buffer, void *bufferEnd)
{
fill(buffer, bufferEnd);
// Verify that the pointers are properly aligned for 32-bit
Q_ASSERT(quintptr(buffer) % sizeof(quint32) == 0);
Q_ASSERT(quintptr(bufferEnd) % sizeof(quint32) == 0);
quint32 *begin = static_cast<quint32 *>(buffer);
quint32 *end = static_cast<quint32 *>(bufferEnd);
if (type == SystemRNG || Q_UNLIKELY(uint(qt_randomdevice_control) & (UseSystemRNG|SetRandomData)))
return SystemGenerator::self().generate(begin, end);
PRNGLocker lock(this);
std::generate(begin, end, [this]() { return storage.engine()(); });
}
#if defined(Q_OS_ANDROID) && (__ANDROID_API__ < 21)

View File

@ -42,6 +42,7 @@
#include <QtCore/qglobal.h>
#include <algorithm> // for std::generate
#include <random> // for std::mt19937
QT_BEGIN_NAMESPACE
@ -51,19 +52,37 @@ class QRandomGenerator
template <typename UInt> using IfValidUInt =
typename std::enable_if<std::is_unsigned<UInt>::value && sizeof(UInt) >= sizeof(uint), bool>::type;
public:
static QRandomGenerator system() { return {}; }
static QRandomGenerator global() { return {}; }
QRandomGenerator() = default;
QRandomGenerator(quint32 seed = 1)
: QRandomGenerator(&seed, 1)
{}
template <qssize_t N> QRandomGenerator(const quint32 (&seedBuffer)[N])
: QRandomGenerator(seedBuffer, seedBuffer + N)
{}
QRandomGenerator(const quint32 *seedBuffer, qssize_t len)
: QRandomGenerator(seedBuffer, seedBuffer + len)
{}
Q_CORE_EXPORT QRandomGenerator(std::seed_seq &sseq) Q_DECL_NOTHROW;
Q_CORE_EXPORT QRandomGenerator(const quint32 *begin, const quint32 *end);
// ### REMOVE BEFORE 5.10
QRandomGenerator *operator->() { return this; }
static quint32 get32() { return generate(); }
static quint64 get64() { return generate64(); }
static qreal getReal() { return generateDouble(); }
// copy constructor & assignment operator (move unnecessary)
Q_CORE_EXPORT QRandomGenerator(const QRandomGenerator &other);
Q_CORE_EXPORT QRandomGenerator &operator=(const QRandomGenerator &other);
static Q_CORE_EXPORT quint32 generate();
static Q_CORE_EXPORT quint64 generate64();
static double generateDouble()
quint32 generate()
{
quint32 ret;
fillRange(&ret, 1);
return ret;
}
quint64 generate64()
{
quint32 buf[2];
fillRange(buf);
return buf[0] | (quint64(buf[1]) << 32);
}
double generateDouble()
{
// IEEE 754 double precision has:
// 1 bit sign
@ -77,87 +96,144 @@ public:
return double(x) / double(limit);
}
static qreal bounded(qreal sup)
double bounded(double highest)
{
return generateDouble() * sup;
return generateDouble() * highest;
}
static quint32 bounded(quint32 sup)
quint32 bounded(quint32 highest)
{
quint64 value = generate();
value *= sup;
value *= highest;
value /= (max)() + quint64(1);
return quint32(value);
}
static int bounded(int sup)
int bounded(int highest)
{
return int(bounded(quint32(sup)));
return int(bounded(quint32(highest)));
}
static quint32 bounded(quint32 min, quint32 sup)
quint32 bounded(quint32 lowest, quint32 highest)
{
return bounded(sup - min) + min;
return bounded(highest - lowest) + lowest;
}
static int bounded(int min, int sup)
int bounded(int lowest, int highest)
{
return bounded(sup - min) + min;
return bounded(highest - lowest) + lowest;
}
template <typename UInt, IfValidUInt<UInt> = true>
static void fillRange(UInt *buffer, qssize_t count)
void fillRange(UInt *buffer, qssize_t count)
{
fillRange_helper(buffer, buffer + count);
_fillRange(buffer, buffer + count);
}
template <typename UInt, size_t N, IfValidUInt<UInt> = true>
static void fillRange(UInt (&buffer)[N])
void fillRange(UInt (&buffer)[N])
{
fillRange_helper(buffer, buffer + N);
_fillRange(buffer, buffer + N);
}
// API like std::seed_seq
template <typename ForwardIterator>
void generate(ForwardIterator begin, ForwardIterator end)
{
auto generator = static_cast<quint32 (*)()>(&QRandomGenerator::generate);
std::generate(begin, end, generator);
std::generate(begin, end, [this]() { return generate(); });
}
void generate(quint32 *begin, quint32 *end)
{
fillRange_helper(begin, end);
_fillRange(begin, end);
}
// API like std::random_device
// API like std:: random engines
typedef quint32 result_type;
result_type operator()() { return generate(); }
double entropy() const Q_DECL_NOTHROW { return 0.0; }
static Q_DECL_CONSTEXPR result_type min() { return (std::numeric_limits<result_type>::min)(); }
static Q_DECL_CONSTEXPR result_type max() { return (std::numeric_limits<result_type>::max)(); }
static inline QRandomGenerator *system();
static inline QRandomGenerator *global();
protected:
enum System {};
QRandomGenerator(System);
private:
static Q_CORE_EXPORT void fillRange_helper(void *buffer, void *bufferEnd);
Q_CORE_EXPORT void _fillRange(void *buffer, void *bufferEnd);
friend class QRandomGenerator64;
struct SystemGeneratorBase {};
struct SystemGenerator;
typedef std::mt19937 RandomEngine;
union Storage {
SystemGeneratorBase sys;
#ifdef Q_COMPILER_UNRESTRICTED_UNIONS
RandomEngine twister;
RandomEngine &engine() { return twister; }
const RandomEngine &engine() const { return twister; }
#else
std::aligned_storage<sizeof(RandomEngine), Q_ALIGNOF(RandomEngine)>::type buffer;
RandomEngine &engine() { return reinterpret_cast<RandomEngine &>(buffer); }
const RandomEngine &engine() const { return reinterpret_cast<const RandomEngine &>(buffer); }
#endif
Q_STATIC_ASSERT_X(std::is_trivially_destructible<RandomEngine>::value,
"std::mersenne_twister not trivially destructible as expected");
Storage();
};
uint type;
Storage storage;
};
class QRandomGenerator64
class QRandomGenerator64 : public QRandomGenerator
{
QRandomGenerator64(System);
public:
static QRandomGenerator64 system() { return {}; }
static QRandomGenerator64 global() { return {}; }
QRandomGenerator64() = default;
// unshadow generate() overloads, since we'll override.
using QRandomGenerator::generate;
quint64 generate() { return generate64(); }
static quint64 generate() { return QRandomGenerator::generate64(); }
// API like std::random_device
typedef quint64 result_type;
result_type operator()() { return QRandomGenerator::generate64(); }
double entropy() const Q_DECL_NOTHROW { return 0.0; }
result_type operator()() { return generate64(); }
#ifndef Q_QDOC
QRandomGenerator64(quint32 seed = 1)
: QRandomGenerator(seed)
{}
template <qssize_t N> QRandomGenerator64(const quint32 (&seedBuffer)[N])
: QRandomGenerator(seedBuffer)
{}
QRandomGenerator64(const quint32 *seedBuffer, qssize_t len)
: QRandomGenerator(seedBuffer, len)
{}
QRandomGenerator64(std::seed_seq &sseq) Q_DECL_NOTHROW
: QRandomGenerator(sseq)
{}
QRandomGenerator64(const quint32 *begin, const quint32 *end)
: QRandomGenerator(begin, end)
{}
QRandomGenerator64(const QRandomGenerator &other) : QRandomGenerator(other) {}
static Q_DECL_CONSTEXPR result_type min() { return (std::numeric_limits<result_type>::min)(); }
static Q_DECL_CONSTEXPR result_type max() { return (std::numeric_limits<result_type>::max)(); }
static Q_CORE_EXPORT QRandomGenerator64 *system();
static Q_CORE_EXPORT QRandomGenerator64 *global();
#endif // Q_QDOC
};
inline QRandomGenerator *QRandomGenerator::system()
{
return QRandomGenerator64::system();
}
inline QRandomGenerator *QRandomGenerator::global()
{
return QRandomGenerator64::global();
}
QT_END_NAMESPACE

View File

@ -52,10 +52,12 @@
//
#include "qglobal_p.h"
#include <private/qsimd_p.h>
QT_BEGIN_NAMESPACE
enum QRandomGeneratorControl {
UseSystemRNG = 1,
SkipSystemRNG = 2,
SkipHWRNG = 4,
SetRandomData = 8,
@ -64,6 +66,11 @@ enum QRandomGeneratorControl {
RandomDataMask = 0xfffffff0
};
enum RNGType {
SystemRNG = 0,
MersenneTwister = 1
};
#if defined(QT_BUILD_INTERNAL) && defined(QT_BUILD_CORE_LIB)
Q_CORE_EXPORT QBasicAtomicInteger<uint> qt_randomdevice_control = Q_BASIC_ATOMIC_INITIALIZER(0U);
#elif defined(QT_BUILD_INTERNAL)
@ -72,6 +79,16 @@ extern Q_CORE_EXPORT QBasicAtomicInteger<uint> qt_randomdevice_control;
enum { qt_randomdevice_control = 0 };
#endif
inline bool qt_has_hwrng()
{
#if defined(Q_PROCESSOR_X86) && QT_COMPILER_SUPPORTS_HERE(RDRND)
return qCpuHasFeature(RDRND);
#else
return false;
#endif
}
QT_END_NAMESPACE
#endif // QRANDOM_P_H

View File

@ -43,8 +43,8 @@
#define COMMA ,
#define QVERIFY_3TIMES(statement) \
do {\
if (!QTest::qVerify(static_cast<bool>(statement), #statement, "1st try", __FILE__, __LINE__))\
if (!QTest::qVerify(static_cast<bool>(statement), #statement, "2nd try", __FILE__, __LINE__))\
if (!static_cast<bool>(statement))\
if (!static_cast<bool>(statement))\
if (!QTest::qVerify(static_cast<bool>(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<decltype(rng64.generate()) COMMA quint64>::value);
Q_STATIC_ASSERT(std::is_same<decltype(system64->generate()) 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<int>::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<uint>("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<quint32>::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<quint8 *>(buffer);
quint8 *end = ptr + sizeof(buffer);
@ -224,20 +370,20 @@ void tst_QRandomGenerator::quality()
template <typename T> 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 <typename T> 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<qulonglong>();
template <typename T> 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 <typename T> 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<quint32>::max();
}) == end(array);
@ -303,12 +449,12 @@ void tst_QRandomGenerator::generateULLong() { generate_template<qulonglong>(); }
void tst_QRandomGenerator::generateNonContiguous()
{
QFETCH(uint, control);
setRNGControl(control);
RandomGenerator rng(control);
QLinkedList<quint64> 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<quint32>::max();
}) == list.end();
@ -326,7 +472,7 @@ void tst_QRandomGenerator::bounded_data()
QTest::addColumn<quint32>("sup");
QTest::addColumn<quint32>("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<quint32> 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 <typename Engine> 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);
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);
}
}
void tst_QRandomGenerator::seedStdRandomEngines()
@ -533,12 +693,16 @@ void tst_QRandomGenerator::stdUniformIntDistribution_data()
QTest::addColumn<uint>("control");
QTest::addColumn<quint32>("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<qreal COMMA 32>(rd);
QVERIFY_3TIMES([&] {
qreal value = std::generate_canonical<qreal COMMA 32>(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<qreal COMMA 32>(rd) !=
std::generate_canonical<qreal COMMA 32>(rd));
QVERIFY_3TIMES(std::generate_canonical<qreal COMMA 32>(rng) !=
std::generate_canonical<qreal COMMA 32>(rng));
#endif
}
@ -649,12 +811,16 @@ void tst_QRandomGenerator::stdUniformRealDistribution_data()
QTest::addColumn<double>("min");
QTest::addColumn<double>("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 <typename Generator> 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<QRandomGenerator>();
stdRandomDistributions_template<QRandomGenerator64>();
}
QTEST_APPLESS_MAIN(tst_QRandomGenerator)
#include "tst_qrandomgenerator.moc"