diff --git a/absl/random/bit_gen_ref.h b/absl/random/bit_gen_ref.h index df8675b1234..9f7cdcc3244 100644 --- a/absl/random/bit_gen_ref.h +++ b/absl/random/bit_gen_ref.h @@ -26,6 +26,7 @@ #include #include +#include #include #include "absl/base/attributes.h" @@ -69,13 +70,25 @@ class BitGenRef { struct detector>, Args...> : std::true_type {}; + // Detects a conversion operator callable on an lvalue, so that types may + // ref-qualify it (`operator BitGenRef() &`) to prevent binding to + // temporaries. template using has_conversion_operator_t = - decltype(std::declval().operator BitGenRef()); + decltype(std::declval().operator BitGenRef()); template using HasConversionOperator = detector; + // True when BitGenRef binds directly to a URBG, rather than copying + // (BitGenRef and derived types) or converting (types which provide + // `operator BitGenRef()`). + template + static constexpr bool kBindsDirectly = + !std::is_same_v && !std::is_base_of_v && + !HasConversionOperator::value && + random_internal::is_urbg::value; + public: BitGenRef(const BitGenRef&) = default; BitGenRef(BitGenRef&&) = default; @@ -84,27 +97,20 @@ class BitGenRef { template , typename std::enable_if_t< - (!std::is_same_v && - !std::is_base_of_v && - !HasConversionOperator::value && - random_internal::is_urbg::value && - !RandomMockingAccess::HasInvokeMock::value)>* = nullptr> + kBindsDirectly && + !std::is_const_v>>* = nullptr> BitGenRef(URBGRef&& gen ABSL_ATTRIBUTE_LIFETIME_BOUND) // NOLINT - : t_erased_gen_ptr_(reinterpret_cast(&gen)), - mock_call_(NotAMock), + : t_erased_gen_ptr_(std::addressof(gen)), + mock_call_(MockCallFn()), generate_impl_fn_(ImplFn) {} + // Generating a variate mutates the generator, so a BitGenRef may not bind to + // a const generator. template , typename std::enable_if_t< - (!std::is_same_v && - !std::is_base_of_v && - !HasConversionOperator::value && - random_internal::is_urbg::value && - RandomMockingAccess::HasInvokeMock::value)>* = nullptr> - BitGenRef(URBGRef&& gen ABSL_ATTRIBUTE_LIFETIME_BOUND) // NOLINT - : t_erased_gen_ptr_(reinterpret_cast(&gen)), - mock_call_(MockCall), - generate_impl_fn_(ImplFn) {} + kBindsDirectly && + std::is_const_v>>* = nullptr> + BitGenRef(URBGRef&& gen) = delete; // NOLINT using result_type = uint64_t; @@ -119,35 +125,41 @@ class BitGenRef { result_type operator()() { return generate_impl_fn_(t_erased_gen_ptr_); } private: - using impl_fn = result_type (*)(uintptr_t); - using mock_call_fn = bool (*)(uintptr_t, FastTypeIdType, void*, void*); + using impl_fn = result_type (*)(void*); + using mock_call_fn = bool (*)(void*, FastTypeIdType, void*, void*); template - static result_type ImplFn(uintptr_t ptr) { + static result_type ImplFn(void* ptr) { // Ensure that the return values from operator() fill the entire // range promised by result_type, min() and max(). absl::random_internal::FastUniformBits fast_uniform_bits; - return fast_uniform_bits(*reinterpret_cast(ptr)); + return fast_uniform_bits(*static_cast(ptr)); } // Get a type-erased InvokeMock pointer. template - static bool MockCall(uintptr_t gen_ptr, FastTypeIdType key_id, - void* args_tuple, void* result) { - return RandomMockingAccess::InvokeMock(reinterpret_cast(gen_ptr), - key_id, args_tuple, result); + static bool MockCall(void* gen_ptr, FastTypeIdType key_id, void* args_tuple, + void* result) { + return RandomMockingAccess::InvokeMock(static_cast(gen_ptr), key_id, + args_tuple, result); } - static bool NotAMock(uintptr_t, FastTypeIdType, void*, void*) { - return false; + + // Returns MockCall if URBG supports mocking, otherwise nullptr. + template + static constexpr mock_call_fn MockCallFn() { + if constexpr (RandomMockingAccess::HasInvokeMock::value) { + return MockCall; + } else { + return nullptr; + } } - inline bool InvokeMock(FastTypeIdType key_id, void* args_tuple, - void* result) { - if (mock_call_ == NotAMock) return false; // avoids an indirect call. + bool InvokeMock(FastTypeIdType key_id, void* args_tuple, void* result) { + if (mock_call_ == nullptr) return false; // avoids an indirect call. return mock_call_(t_erased_gen_ptr_, key_id, args_tuple, result); } - uintptr_t t_erased_gen_ptr_; + void* t_erased_gen_ptr_; mock_call_fn mock_call_; impl_fn generate_impl_fn_; diff --git a/absl/random/bit_gen_ref_test.cc b/absl/random/bit_gen_ref_test.cc index ae5c1db80a8..9c2ca0ebe44 100644 --- a/absl/random/bit_gen_ref_test.cc +++ b/absl/random/bit_gen_ref_test.cc @@ -114,13 +114,13 @@ struct MinStdRand { // Implicit conversions allow passing MinStdRand to functions taking // absl::BitGenRef as well as explicitly constructing an absl::BitGenRef from // a MinStdRand. - operator absl::BitGenRef() const { + operator absl::BitGenRef() { conversion_count++; return absl::BitGenRef(minstd_gen); } std::minstd_rand minstd_gen; - mutable int conversion_count = 0; + int conversion_count = 0; }; TEST(BitGenRefTest, IsConvertibleTest) { @@ -145,6 +145,44 @@ TEST(BitGenRefTest, IsConvertibleTest) { } } +// Generating a variate mutates the generator, so binding to a const generator +// is disallowed. +static_assert(std::is_constructible_v); +static_assert(std::is_constructible_v); +static_assert(!std::is_constructible_v); +static_assert(!std::is_constructible_v); +static_assert(!std::is_constructible_v); + +// A URBG whose conversion operator is lvalue-only, which prevents binding an +// absl::BitGenRef to a temporary. +struct LvalueOnlyConversion { + using result_type = absl::BitGen::result_type; + static constexpr result_type(min)() { return (absl::BitGen::min)(); } + static constexpr result_type(max)() { return (absl::BitGen::max)(); } + result_type operator()() { return 0; } + + operator absl::BitGenRef() & { + conversion_count++; + return absl::BitGenRef(minstd_gen); + } + + std::minstd_rand minstd_gen; + int conversion_count = 0; +}; + +static_assert(std::is_convertible_v); +static_assert(!std::is_convertible_v); + +TEST(BitGenRefTest, LvalueOnlyConversionIsUsed) { + // The conversion operator, rather than the URBG interface, is used. + LvalueOnlyConversion gen; + absl::BitGenRef gen_ref(gen); + EXPECT_EQ(gen.conversion_count, 1); + FnTest(gen); + EXPECT_EQ(gen.conversion_count, 2); + (void)gen_ref; +} + } // namespace ABSL_NAMESPACE_END } // namespace absl