From b57316ee3c29a90de5556c8f60b211c1919f7622 Mon Sep 17 00:00:00 2001 From: Rishab Joshi Date: Thu, 24 Feb 2022 12:01:14 +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(-) 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 79f89b28c57..e4ff0454131 100644 --- a/src/mongo/shell/shell_utils.cpp +++ b/src/mongo/shell/shell_utils.cpp @@ -62,6 +62,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" @@ -305,25 +306,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