From d726a1820bef623a3220bf69bb77d8c5be069f42 Mon Sep 17 00:00:00 2001 From: Rishab Joshi Date: Sun, 30 Jan 2022 22:05:05 +0000 Subject: SERVER-62902 Fix type conversion issue in JSSRand. --- src/mongo/platform/random.h | 10 ++++++++++ src/mongo/shell/shell_utils.cpp | 36 ++++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 16 deletions(-) (limited to 'src/mongo') diff --git a/src/mongo/platform/random.h b/src/mongo/platform/random.h index 3521399c896..def8ddf9067 100644 --- a/src/mongo/platform/random.h +++ b/src/mongo/platform/random.h @@ -131,6 +131,16 @@ public: return std::uniform_int_distribution(0, max - 1)(_urbg); } + /** + A number uniformly distributed over all possible values that can be safely represented as + double without loosing precision. + */ + int64_t nextInt64SafeDoubleRepresentable() { + const int64_t maxRepresentableLimit = + static_cast(std::ldexp(1, std::numeric_limits::digits)) + 1; + return nextInt64(maxRepresentableLimit); + } + /** Fill array `buf` with `n` random bytes. */ void fill(void* buf, size_t n) { const auto p = static_cast(buf); diff --git a/src/mongo/shell/shell_utils.cpp b/src/mongo/shell/shell_utils.cpp index aa9b5fe05b2..4c3ca9a7c78 100644 --- a/src/mongo/shell/shell_utils.cpp +++ b/src/mongo/shell/shell_utils.cpp @@ -63,6 +63,7 @@ #include "mongo/util/fail_point.h" #include "mongo/util/processinfo.h" #include "mongo/util/quick_exit.h" +#include "mongo/util/represent_as.h" #include "mongo/util/text.h" #include "mongo/util/version.h" @@ -306,25 +307,28 @@ BSONObj JSGetMemInfo(const BSONObj& args, void* data) { thread_local auto _prng = PseudoRandom(0); BSONObj JSSrand(const BSONObj& a, void* data) { - int64_t seed; - // grab the least significant bits of either the supplied argument or - // a random number from SecureRandom. + boost::optional prngSeed = boost::none; + boost::optional asDouble = boost::none; + + // Grab the least significant bits of either the supplied argument or a random number from + // SecureRandom. if (a.nFields() == 1 && a.firstElement().isNumber()) { - seed = a.firstElement().safeNumberLong(); + asDouble = representAs(a.firstElement().safeNumberLong()); + prngSeed = asDouble ? representAs(*asDouble) : boost::none; + uassert(6290200, "Cannot represent seed as 64 bit integral or double value", prngSeed); } else { - seed = SecureRandom().nextInt64(); + // Use secure random number generator to get the seed value that can be safely + // represented as double. + auto asInt64 = SecureRandom().nextInt64SafeDoubleRepresentable(); + asDouble = representAs(asInt64); + invariant(asDouble); + prngSeed = representAs(*asDouble); } - // Make sure the seed is representable as both an int64_t and a double, so that the value we - // return (as a double) can be fed back in to JSSrand() to initialize the prng (as an int64_t) - // to the same state. To do so, we cast to the double first which may lose precision for large - // numbers. Then after the potential precision loss we go back to an int64_t which should not - // change precision at all. Using that (potentially) new int64_t as the seed, we can now - // confidently return the double version and know it can be used to set the same exact seed - // later. - double asDouble = static_cast(seed); - int64_t asInt64 = static_cast(asDouble); - _prng = PseudoRandom(asInt64); - return BSON("" << asDouble); + + // The seed is representable as both an int64_t and a double, so that the value we return (as a + // double) can be fed back in to JSSrand() to initialize the prng (as an int64_t). + _prng = PseudoRandom(*prngSeed); + return BSON("" << *asDouble); } BSONObj JSRand(const BSONObj& a, void* data) { -- cgit v1.2.1