diff options
Diffstat (limited to 'src/corelib/global/qrandom.cpp')
-rw-r--r-- | src/corelib/global/qrandom.cpp | 299 |
1 files changed, 196 insertions, 103 deletions
diff --git a/src/corelib/global/qrandom.cpp b/src/corelib/global/qrandom.cpp index 6a0b86dd6e..21448b8c14 100644 --- a/src/corelib/global/qrandom.cpp +++ b/src/corelib/global/qrandom.cpp @@ -1,60 +1,27 @@ -/**************************************************************************** -** -** Copyright (C) 2019 Intel Corporation. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 Intel Corporation. +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // for rand_s #define _CRT_RAND_S #include "qrandom.h" #include "qrandom_p.h" -#include <qobjectdefs.h> +#include <qendian.h> #include <qmutex.h> -#include <qthreadstorage.h> +#include <qobjectdefs.h> #include <errno.h> -#if !QT_CONFIG(getentropy) && (!defined(Q_OS_BSD4) || defined(__GLIBC__)) && !defined(Q_OS_WIN) +#if QT_CONFIG(getauxval) +# include <sys/auxv.h> +#endif + +#if QT_CONFIG(getentropy) && __has_include(<sys/random.h>) +# include <sys/random.h> +#elif !QT_CONFIG(getentropy) && (!defined(Q_OS_BSD4) || defined(__GLIBC__)) && !defined(Q_OS_WIN) # include "qdeadlinetimer.h" # include "qhashfunctions.h" - -# if QT_CONFIG(getauxval) -# include <sys/auxv.h> -# endif #endif // !QT_CONFIG(getentropy) #ifdef Q_OS_UNIX @@ -72,10 +39,6 @@ DECLSPEC_IMPORT BOOLEAN WINAPI SystemFunction036(PVOID RandomBuffer, ULONG Rando } #endif -#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) -# include <private/qjni_p.h> -#endif - // This file is too low-level for regular Q_ASSERT (the logging framework may // recurse back), so use regular assert() #undef NDEBUG @@ -95,6 +58,10 @@ enum { FillBufferNoexcept = true }; +#if defined(QT_BUILD_INTERNAL) +QBasicAtomicInteger<uint> qt_randomdevice_control = Q_BASIC_ATOMIC_INITIALIZER(0U); +#endif + struct QRandomGenerator::SystemGenerator { #if QT_CONFIG(getentropy) @@ -168,7 +135,7 @@ struct QRandomGenerator::SystemGenerator } #elif defined(Q_OS_WIN) - qsizetype fillBuffer(void *buffer, qsizetype count) noexcept + static qsizetype fillBuffer(void *buffer, qsizetype count) noexcept { auto RtlGenRandom = SystemFunction036; return RtlGenRandom(buffer, ULONG(count)) ? count: 0; @@ -181,7 +148,8 @@ struct QRandomGenerator::SystemGenerator // 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) + template<typename T> + void generate(T *begin, T *end) { static_assert(sizeof(T) >= sizeof(quint32)); if (sizeof(T) == sizeof(quint32)) { @@ -225,10 +193,10 @@ static void fallback_fill(quint32 *ptr, qsizetype left) noexcept arc4random_buf(ptr, left * sizeof(*ptr)); } #else -static QBasicAtomicInteger<unsigned> seed = Q_BASIC_ATOMIC_INITIALIZER(0U); +Q_CONSTINIT static QBasicAtomicInteger<unsigned> seed = Q_BASIC_ATOMIC_INITIALIZER(0U); static void fallback_update_seed(unsigned value) { - // Update the seed to be used for the fallback mechansim, if we need to. + // Update the seed to be used for the fallback mechanism, if we need to. // We can't use QtPrivate::QHashCombine here because that is not an atomic // operation. A simple XOR will have to do then. seed.fetchAndXorRelaxed(value); @@ -349,41 +317,34 @@ struct QRandomGenerator::SystemAndGlobalGenerators // the state in case another thread tries to lock the mutex. It's not // a common scenario, but since sizeof(QRandomGenerator) >= 2560, the // overhead is actually acceptable. - // 2) We use both alignas and std::aligned_storage<..., 64> because - // some implementations of std::aligned_storage can't align to more - // than a primitive type's alignment. + // 2) We use both alignas(T) and alignas(64) because some implementations + // can't align to more than a primitive type's alignment. // 3) We don't store the entire system QRandomGenerator, only the space // used by the QRandomGenerator::type member. This is fine because we // (ab)use the common initial sequence exclusion to aliasing rules. QBasicMutex globalPRNGMutex; struct ShortenedSystem { uint type; } system_; SystemGenerator sys; - alignas(64) std::aligned_storage<sizeof(QRandomGenerator64), 64>::type global_; + alignas(64) struct { + alignas(QRandomGenerator64) uchar data[sizeof(QRandomGenerator64)]; + } global_; -#ifdef Q_COMPILER_CONSTEXPR constexpr SystemAndGlobalGenerators() : globalPRNGMutex{}, system_{0}, sys{}, global_{} {} -#endif void confirmLiteral() { -#if defined(Q_COMPILER_CONSTEXPR) && !defined(Q_CC_MSVC) && !defined(Q_OS_INTEGRITY) - // Currently fails to compile with MSVC 2017, saying QBasicMutex is not - // a literal type. Disassembly with MSVC 2013 and 2015 shows it is - // actually a literal; MSVC 2017 has a bug relating to this, so we're - // withhold judgement for now. Integrity's compiler is unable to - // guarantee g's alignment for some reason. - +#if !defined(Q_OS_INTEGRITY) + // Integrity's compiler is unable to guarantee g's alignment for some reason. constexpr SystemAndGlobalGenerators g = {}; Q_UNUSED(g); - static_assert(std::is_literal_type<SystemAndGlobalGenerators>::value); #endif } static SystemAndGlobalGenerators *self() { - static SystemAndGlobalGenerators g; + Q_CONSTINIT static SystemAndGlobalGenerators g; static_assert(sizeof(g) > sizeof(QRandomGenerator64)); return &g; } @@ -414,7 +375,8 @@ struct QRandomGenerator::SystemAndGlobalGenerators new (&rng->storage.engine()) RandomEngine(self()->sys); } - struct PRNGLocker { + struct PRNGLocker + { const bool locked; PRNGLocker(const QRandomGenerator *that) : locked(that == globalNoInit()) @@ -675,7 +637,7 @@ inline QRandomGenerator::SystemGenerator &QRandomGenerator::SystemGenerator::sel 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 + For that reason, it is not advisable to create a copy of QRandomGenerator::global(). If one needs an exclusive deterministic generator, consider instead using securelySeeded() to obtain a new object that shares no relationship with the QRandomGenerator::global(). @@ -685,18 +647,17 @@ inline QRandomGenerator::SystemGenerator &QRandomGenerator::SystemGenerator::sel \fn bool operator==(const QRandomGenerator &rng1, const QRandomGenerator &rng2) \relates QRandomGenerator - Returns true if the two the two engines \a rng1 and \a rng2 are at the same + Returns true if the two engines \a rng1 and \a rng2 are at the same state or if they are both reading from the operating system facilities, false otherwise. */ /*! - \fn bool operator!=(const QRandomGenerator &rng1, const QRandomGenerator &rng2) - \relates QRandomGenerator + \fn bool QRandomGenerator::operator!=(const QRandomGenerator &rng1, const QRandomGenerator &rng2) - Returns true if the two the two engines \a rng1 and \a rng2 are at + Returns \c true if the two engines \a rng1 and \a rng2 are at different states or if one of them is reading from the operating system - facilities and the other is not, false otherwise. + facilities and the other is not, \c false otherwise. */ /*! @@ -804,7 +765,7 @@ inline QRandomGenerator::SystemGenerator &QRandomGenerator::SystemGenerator::sel */ /*! - \fn template <typename UInt> void QRandomGenerator::fillRange(UInt *buffer, qsizetype count) + \fn template <typename UInt, QRandomGenerator::IfValidUInt<UInt> = true> void QRandomGenerator::fillRange(UInt *buffer, qsizetype count) Generates \a count 32- or 64-bit quantities (depending on the type \c UInt) and stores them in the buffer pointed by \a buffer. This is the most @@ -820,7 +781,7 @@ inline QRandomGenerator::SystemGenerator &QRandomGenerator::SystemGenerator::sel */ /*! - \fn template <typename UInt, size_t N> void QRandomGenerator::fillRange(UInt (&buffer)[N]) + \fn template <typename UInt, size_t N, QRandomGenerator::IfValidUInt<UInt> = true> void QRandomGenerator::fillRange(UInt (&buffer)[N]) Generates \c N 32- or 64-bit quantities (depending on the type \c UInt) and stores them in the \a buffer array. This is the most efficient way to @@ -876,7 +837,8 @@ inline QRandomGenerator::SystemGenerator &QRandomGenerator::SystemGenerator::sel \a highest (exclusive). The same result may also be obtained by using \l{http://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution}{\c std::uniform_int_distribution} with parameters 0 and \c{highest - 1}. That class can also be used to obtain - quantities larger than 32 bits. + quantities larger than 32 bits; for 64 bits, the 64-bit bounded() overload + can be used too. For example, to obtain a value between 0 and 255 (inclusive), one would write: @@ -905,6 +867,45 @@ inline QRandomGenerator::SystemGenerator &QRandomGenerator::SystemGenerator::sel */ /*! + \fn quint64 QRandomGenerator::bounded(quint64 highest) + \overload + + Generates one random 64-bit quantity in the range between 0 (inclusive) and + \a highest (exclusive). The same result may also be obtained by using + \l{http://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution}{\c std::uniform_int_distribution<quint64>} + with parameters 0 and \c{highest - 1}. + + Note that this function cannot be used to obtain values in the full 64-bit + range of \c{quint64}. Instead, use generate64(). + + \note This function is implemented as a loop, which depends on the random + value obtained. On the long run, on average it should loop just under 2 + times, but if the random generator is defective, this function may take + considerably longer to execute. + + \sa generate(), generate64(), generateDouble() + */ + +/*! + \fn qint64 QRandomGenerator::bounded(qint64 highest) + \overload + + Generates one random 64-bit quantity in the range between 0 (inclusive) and + \a highest (exclusive). \a highest must be positive. + + Note that this function cannot be used to obtain values in the full 64-bit + range of \c{qint64}. Instead, use generate64() and cast to qint64 or instead + use the unsigned version of this function. + + \note This function is implemented as a loop, which depends on the random + value obtained. On the long run, on average it should loop just under 2 + times, but if the random generator is defective, this function may take + considerably longer to execute. + + \sa generate(), generate64(), generateDouble() + */ + +/*! \fn quint32 QRandomGenerator::bounded(quint32 lowest, quint32 highest) \overload @@ -943,6 +944,60 @@ inline QRandomGenerator::SystemGenerator &QRandomGenerator::SystemGenerator::sel */ /*! + \fn quint64 QRandomGenerator::bounded(quint64 lowest, quint64 highest) + \overload + + Generates one random 64-bit quantity in the range between \a lowest + (inclusive) and \a highest (exclusive). The \a highest parameter must be + greater than \a lowest. + + The same result may also be obtained by using + \l{http://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution}{\c std::uniform_int_distribution<quint64>} + with parameters \a lowest and \c{\a highest - 1}. + + Note that this function cannot be used to obtain values in the full 64-bit + range of \c{quint64}. Instead, use generate64(). + + \note This function is implemented as a loop, which depends on the random + value obtained. On the long run, on average it should loop just under 2 + times, but if the random generator is defective, this function may take + considerably longer to execute. + + \sa generate(), generate64(), generateDouble() + */ + +/*! + \fn qint64 QRandomGenerator::bounded(qint64 lowest, qint64 highest) + \overload + + Generates one random 64-bit quantity in the range between \a lowest + (inclusive) and \a highest (exclusive), both of which may be negative, but + \a highest must be greater than \a lowest. + + Note that this function cannot be used to obtain values in the full 64-bit + range of \c{qint64}. Instead, use generate64() and cast to qint64. + + \note This function is implemented as a loop, which depends on the random + value obtained. On the long run, on average it should loop just under 2 + times, but if the random generator is defective, this function may take + considerably longer to execute. + + \sa generate(), generate64(), generateDouble() + */ + +/*! + \fn qint64 QRandomGenerator::bounded(int lowest, qint64 highest) + \fn qint64 QRandomGenerator::bounded(qint64 lowest, int highest) + \fn quint64 QRandomGenerator::bounded(unsigned lowest, quint64 highest) + \fn quint64 QRandomGenerator::bounded(quint64 lowest, unsigned highest) + \overload + + This function exists to help with overload resolution when the types of the + parameters don't exactly match. They will promote the smaller type to the + type of the larger one and call the correct overload. + */ + +/*! \fn QRandomGenerator *QRandomGenerator::system() \threadsafe @@ -1177,44 +1232,82 @@ bool operator==(const QRandomGenerator &rng1, const QRandomGenerator &rng2) /*! \internal - Fills the range pointed by \a buffer and \a bufferEnd with 32-bit random - values. The buffer must be correctly aligned. + Fills the range pointed by \a buffer with \a count 32-bit random values. + The buffer must be correctly aligned. + + Returns the value of the first two 32-bit entries as a \c{quint64}. */ -void QRandomGenerator::_fillRange(void *buffer, void *bufferEnd) +quint64 QRandomGenerator::_fillRange(void *buffer, qptrdiff count) { // 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); + Q_ASSERT(count >= 0); + Q_ASSERT(buffer || count <= 2); + + quint64 dummy; + quint32 *begin = static_cast<quint32 *>(buffer ? buffer : &dummy); + quint32 *end = begin + count; + + if (type == SystemRNG || Q_UNLIKELY(uint(qt_randomdevice_control.loadAcquire()) & (UseSystemRNG|SetRandomData))) { + SystemGenerator::self().generate(begin, end); + } else { + SystemAndGlobalGenerators::PRNGLocker lock(this); + std::generate(begin, end, [this]() { return storage.engine()(); }); + } - if (type == SystemRNG || Q_UNLIKELY(uint(qt_randomdevice_control.loadAcquire()) & (UseSystemRNG|SetRandomData))) - return SystemGenerator::self().generate(begin, end); + if (end - begin == 1) + return *begin; + return begin[0] | (quint64(begin[1]) << 32); +} - SystemAndGlobalGenerators::PRNGLocker lock(this); - std::generate(begin, end, [this]() { return storage.engine()(); }); +// helper function to call fillBuffer, since we need something to be +// argument-dependent +template <typename Generator, typename FillBufferType, typename T> +static qsizetype callFillBuffer(FillBufferType f, T *v) +{ + if constexpr (std::is_member_function_pointer_v<FillBufferType>) { + // member function, need an object + return (Generator::self().*f)(v, sizeof(*v)); + } else { + // static, call directly + return f(v, sizeof(*v)); + } } -namespace { -struct QRandEngine +/*! + \internal + + Returns an initial random value (useful for QHash's global seed). This + function attempts to use OS-provided random values to avoid initializing + QRandomGenerator::system() and qsimd.cpp. + + Note: on some systems, this functionn may rerturn the same value every time + it is called. + */ +QRandomGenerator::InitialRandomData qt_initial_random_value() noexcept { - std::minstd_rand engine; - QRandEngine() : engine(1) {} +#if QT_CONFIG(getauxval) && defined(AT_RANDOM) + auto at_random_ptr = reinterpret_cast<size_t *>(getauxval(AT_RANDOM)); + if (at_random_ptr) + return qFromUnaligned<QRandomGenerator::InitialRandomData>(at_random_ptr); +#endif - int generate() - { - std::minstd_rand::result_type v = engine(); - if (std::numeric_limits<int>::max() != RAND_MAX) - v %= uint(RAND_MAX) + 1; + // bypass the hardware RNG, which would mean initializing qsimd.cpp - return int(v); - } + QRandomGenerator::InitialRandomData v; + for (int attempts = 16; attempts; --attempts) { + using Generator = QRandomGenerator::SystemGenerator; + auto fillBuffer = &Generator::fillBuffer; + if (callFillBuffer<Generator>(fillBuffer, &v) != sizeof(v)) + continue; - void seed(std::minstd_rand::result_type q) - { - engine.seed(q); + return v; } -}; + + quint32 data[sizeof(v) / sizeof(quint32)]; + fallback_fill(data, std::size(data)); + memcpy(v.data, data, sizeof(v.data)); + return v; } QT_END_NAMESPACE |