diff options
author | Rishab Joshi <rishab.joshi@mongodb.com> | 2022-01-30 22:05:05 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-01-30 22:38:32 +0000 |
commit | d726a1820bef623a3220bf69bb77d8c5be069f42 (patch) | |
tree | e7dce5388aa9a04b83be16347cd284e91c1c37e0 | |
parent | 4f5c7688766e550d32863923ae6c23c09e2cb5e3 (diff) | |
download | mongo-d726a1820bef623a3220bf69bb77d8c5be069f42.tar.gz |
SERVER-62902 Fix type conversion issue in JSSRand.
-rw-r--r-- | src/mongo/platform/random.h | 10 | ||||
-rw-r--r-- | 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<int64_t>(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<int64_t>(std::ldexp(1, std::numeric_limits<double>::digits)) + 1; + return nextInt64(maxRepresentableLimit); + } + /** Fill array `buf` with `n` random bytes. */ void fill(void* buf, size_t n) { const auto p = static_cast<uint8_t*>(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<int64_t> prngSeed = boost::none; + boost::optional<int64_t> 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<double>(a.firstElement().safeNumberLong()); + prngSeed = asDouble ? representAs<int64_t>(*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<double>(asInt64); + invariant(asDouble); + prngSeed = representAs<int64_t>(*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<double>(seed); - int64_t asInt64 = static_cast<int64_t>(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) { |