| // Copyright 2025 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef FUZZTEST_FUZZTEST_FUZZING_BIT_GEN_H_ |
| #define FUZZTEST_FUZZTEST_FUZZING_BIT_GEN_H_ |
| |
| #include <cstdint> |
| #include <limits> |
| |
| #include "absl/base/fast_type_id.h" |
| #include "absl/numeric/bits.h" |
| #include "absl/numeric/int128.h" |
| #include "absl/random/bit_gen_ref.h" |
| #include "absl/types/span.h" |
| |
| namespace fuzztest { |
| |
| /// FuzzingBitGen is a BitGen instance which uses the Abseil mock mechanisms |
| /// to return distribution specific variates based on the fuzz data stream. |
| /// |
| /// It is perhaps useful to think of the data stream as a sequence of structured |
| /// variates with semantic meaning, rather than just values. Recombinations of, |
| /// and modifications to, the sequence are useful in exploring the behavior of |
| /// the code under test in ways where a mere random-number generator sequence |
| /// would not, as changing the seed mutates the entire sequence. |
| /// |
| /// NOTE: The first 8 bytes of the fuzzed data stream may be used to seed an |
| /// internal pnrg which is used to generate random variates for calls which |
| /// are not captured through mockable Abseil random distribution methods |
| /// (for example, calls to std::shuffle(...)). Otherwise the data stream is |
| /// treated as a stream where the next value in the sequence maps to the output |
| /// of the next distribution method. Note that the specific sequence generated |
| /// by a FuzzingBitGen may vary due to the underlying code paths and whether |
| /// implementation details change, such as adding support for new distributions, |
| /// etc. |
| /// |
| /// When the data stream is exhausted, absl::MockingBitGen mockable calls will |
| /// continue to return an arbitrary legal value, typically the minimum or mean |
| /// value of the distribution. |
| /// |
| /// This type is thread-compatible, but not thread-safe. |
| class FuzzingBitGen { |
| public: |
| // Create a FuzzingBitGen from an unowned fuzzed `data` source, which must |
| // outlive the FuzzingBitGen instance. |
| // |
| // The first 8 bytes of the data stream are used to seed an internal URBG used |
| // for calls which are not mockable. |
| explicit FuzzingBitGen(absl::Span<const uint8_t> data_stream); |
| |
| // Disallow copy, assign, and move. |
| FuzzingBitGen(const FuzzingBitGen&) = delete; |
| FuzzingBitGen& operator=(const FuzzingBitGen&) = delete; |
| FuzzingBitGen(FuzzingBitGen&&) = default; |
| FuzzingBitGen& operator=(FuzzingBitGen&&) = default; |
| |
| using result_type = uint64_t; |
| |
| static constexpr result_type(min)() { |
| return (std::numeric_limits<result_type>::min)(); |
| } |
| |
| static constexpr result_type(max)() { |
| return (std::numeric_limits<result_type>::max)(); |
| } |
| |
| void seed(result_type seed_value = 0) { |
| absl::uint128 tmp = seed_value; |
| state_ = lcg(tmp + increment()); |
| } |
| |
| result_type operator()(); |
| |
| private: |
| // Minimal implementation of a PCG64 engine equivalent to xsl_rr_128_64. |
| static inline constexpr absl::uint128 multiplier() { |
| return absl::MakeUint128(0x2360ed051fc65da4, 0x4385df649fccf645); |
| } |
| static inline constexpr absl::uint128 increment() { |
| return absl::MakeUint128(0x5851f42d4c957f2d, 0x14057b7ef767814f); |
| } |
| inline absl::uint128 lcg(absl::uint128 s) { |
| return s * multiplier() + increment(); |
| } |
| inline result_type mix(absl::uint128 state) { |
| uint64_t h = absl::Uint128High64(state); |
| uint64_t rotate = h >> 58u; |
| uint64_t s = absl::Uint128Low64(state) ^ h; |
| return absl::rotr(s, rotate); |
| } |
| |
| // InvokeMock meets the requirements of absl::BitGenRef::InvokeMock. |
| // This method detects whether the key has been registered as supported, |
| // and, if so, returns a value derived from `data_stream_`. |
| bool InvokeMock(absl::FastTypeIdType key_id, void* args_tuple, void* result); |
| |
| absl::Span<const uint8_t> data_stream_; // Mock data stream. |
| absl::uint128 state_ = 0; // Internal URBG state. |
| |
| template <typename> |
| friend struct ::absl::random_internal::DistributionCaller; // for InvokeMock |
| friend class ::absl::random_internal::MockHelpers; // for InvokeMock |
| friend class ::absl::BitGenRef; // for InvokeMock |
| }; |
| |
| } // namespace fuzztest |
| |
| #endif // FUZZTEST_FUZZTEST_FUZZING_BIT_GEN_H_ |