blob: f613892e7295644d936ea0ef57ea00834e031629 [file]
// 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_