Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions domain_tests/bitgen_ref_domain_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/random/bit_gen_ref.h"
#include "absl/random/random.h"
Expand All @@ -30,31 +31,36 @@ TEST(BitGenRefDomainTest, DistinctVariatesGeneratedByCallOperator) {
Value v0(domain, bitgen_for_seeding);
Value v1(domain, bitgen_for_seeding);

// Discard the first value, which may be from the data stream.
// If the implementation of BitGenRefDomain changes this may break.
v0.user_value();
v1.user_value();
// The domain uses an instruction stream of at least 7 bytes;
// If the instruction stream has no high-order bits set (0x80), the
// results below will be identical, otherwise they will differ as
// at least one value should be an lcg call.

std::vector<absl::BitGenRef::result_type> a, b;
for (int i = 0; i < 10; ++i) {
a.push_back(v0.user_value());
b.push_back(v1.user_value());
}
EXPECT_NE(a, b);
EXPECT_THAT(a, testing::Not(testing::ElementsAreArray(b)));
}

TEST(BitGenRefDomainTest, AbseilUniformReturnsLowerBoundWhenExhausted) {
TEST(BitGenRefDomainTest, AbseilUniformWrapsAroundWhenExhausted) {
absl::BitGen bitgen_for_seeding;

Domain<absl::BitGenRef> domain = Arbitrary<absl::BitGenRef>();
Value v0(domain, bitgen_for_seeding);

// Discard the first value, which may be from the data stream.
// If the implementation of BitGenRefDomain changes this may break.
v0.user_value();
// When the same domain is used to generate multiple values, the generated
// data sequence should be identical.
std::vector<int> values;
for (int i = 0; i < 20; ++i) {
values.push_back(absl::Uniform<int>(v0.user_value, 0, 100));
}

for (int i = 0; i < 10; ++i) {
EXPECT_EQ(absl::Uniform<int>(v0.user_value, 0, 100), 0);
// Verify repeatability
Value v1(v0, domain);
for (int i = 0; i < 20; ++i) {
EXPECT_EQ(absl::Uniform<int>(v1.user_value, 0, 100), values[i]);
}
}

Expand Down
17 changes: 15 additions & 2 deletions e2e_tests/testdata/fuzz_tests_for_microbenchmarking.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
// i.e., to check that the fuzzer behaves as expected and outputs the expected
// results. E.g., the fuzzer finds the abort() or bug.

#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
Expand All @@ -33,6 +34,7 @@
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -240,15 +242,26 @@ FUZZ_TEST(MySuite, FixedSizeVectorValue)
.WithDomains(fuzztest::VectorOf(fuzztest::Arbitrary<char>()).WithSize(4));

__attribute__((optnone)) void BitGenRef(absl::BitGenRef bitgen) {
// This uses FuzzingBitGen's mocking support for absl::Uniform().
if (absl::Uniform(bitgen, 0, 256) == 'F' &&
absl::Uniform(bitgen, 0, 256) == 'U' &&
absl::Uniform(bitgen, 0, 256) == 'Z' &&
absl::Uniform(bitgen, 0, 256) == 'Z') {
absl::Uniform(bitgen, 32, 128) == 'Z' &&
absl::Uniform(bitgen, 32, 128) == 'Z') {
std::abort(); // Bug!
}
}
FUZZ_TEST(MySuite, BitGenRef);

__attribute__((optnone)) void BitGenRefShuffle(absl::BitGenRef bitgen) {
// This uses FuzzingBitGen's operator().
std::vector<int> v = {4, 1, 3, 2, 5};
std::shuffle(v.begin(), v.end(), bitgen);
if (std::is_sorted(v.begin(), v.end())) {
std::abort(); // Bug!
}
}
FUZZ_TEST(MySuite, BitGenRefShuffle);

__attribute__((optnone)) void WithDomainClass(uint8_t a, double d) {
// This will only crash with a=10, to make it easier to check the results.
// d can have any value.
Expand Down
11 changes: 11 additions & 0 deletions fuzztest/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,17 @@ cc_library(
],
)

cc_test(
name = "fuzzing_bit_gen_test",
srcs = ["fuzzing_bit_gen_test.cc"],
deps = [
":fuzzing_bit_gen",
"@abseil-cpp//absl/random",
"@abseil-cpp//absl/random:bit_gen_ref",
"@googletest//:gtest_main",
],
)

cc_library(
name = "fuzzing_bit_gen",
srcs = ["fuzzing_bit_gen.cc"],
Expand Down
86 changes: 69 additions & 17 deletions fuzztest/fuzzing_bit_gen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,42 +18,89 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <tuple>
#include <utility>

#include "absl/base/fast_type_id.h"
#include "absl/base/no_destructor.h"
#include "absl/container/flat_hash_map.h"
#include "absl/numeric/bits.h"
#include "absl/numeric/int128.h"
#include "absl/types/span.h"
#include "./fuzztest/internal/register_fuzzing_mocks.h"

namespace fuzztest {
namespace {

FuzzingBitGen::FuzzingBitGen(absl::Span<const uint8_t> data_stream)
: data_stream_(data_stream) {
// 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 uint64_t 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);
}

} // namespace

FuzzingBitGen::FuzzingBitGen(absl::Span<const uint8_t> data_stream,
absl::Span<const uint8_t> instruction_stream)
: instruction_stream_(instruction_stream), data_stream_(data_stream) {
// Seed the internal URBG with the first 8 bytes of the data stream.
uint64_t stream_seed = 0x6C7FD535EDC7A62D;
if (!data_stream_.empty()) {
size_t num_bytes = std::min(sizeof(stream_seed), data_stream_.size());
std::memcpy(&stream_seed, data_stream_.data(), num_bytes);
data_stream_.remove_prefix(num_bytes);
}
seed(stream_seed);
}

FuzzingBitGen::result_type FuzzingBitGen::operator()() {
// The non-mockable calls will consume the next 8 bytes from the data
// stream until it is exhausted, then they will return a value from the
// internal URBG.
if (!data_stream_.empty()) {
result_type x = 0;
size_t num_bytes = std::min(sizeof(x), data_stream_.size());
std::memcpy(&x, data_stream_.data(), num_bytes);
data_stream_.remove_prefix(num_bytes);
return x;
uint64_t FuzzingBitGen::operator()() {
if (instruction_stream_.empty() || data_stream_.empty()) {
state_ = lcg(state_);
return mix(state_);
}
// Use the instruction stream to determine the return value.
if (i_ >= instruction_stream_.size()) {
i_ = 0;
}
const uint8_t instruction = instruction_stream_[i_++];
if (instruction & 0x80) {
state_ = lcg(state_);
return mix(state_);
}
switch (instruction & 0x03) {
case 1:
return 0; // min
case 2:
return (std::numeric_limits<uint64_t>::max)(); // max
case 3:
return (std::numeric_limits<uint64_t>::max)() / 2; // mean
default:
break;
}
if (d_ >= data_stream_.size()) {
d_ = 0;
}
uint64_t x = 0;
size_t num_bytes = std::min(sizeof(x), data_stream_.size() - d_);
std::memcpy(&x, data_stream_.data() + d_, num_bytes);
d_ += num_bytes;
return x;
}

// Fallback to the internal URBG.
state_ = lcg(state_);
return mix(state_);
void FuzzingBitGen::seed(result_type seed_value) {
absl::uint128 tmp = seed_value;
state_ = lcg(tmp + increment());
}

bool FuzzingBitGen::InvokeMock(absl::FastTypeIdType key_id, void* args_tuple,
Expand All @@ -73,7 +120,12 @@ bool FuzzingBitGen::InvokeMock(absl::FastTypeIdType key_id, void* args_tuple,
if (it == fuzzing_map->end()) {
return false;
}
it->second(data_stream_, args_tuple, result);
using InstructionStream = std::pair<absl::Span<const uint8_t>, size_t&>;
using DataStream = std::pair<absl::Span<const uint8_t>, size_t&>;

InstructionStream instruction_stream{instruction_stream_, i_};
DataStream data_stream{data_stream_, d_};
it->second(instruction_stream, data_stream, args_tuple, result);
return true;
}

Expand Down
89 changes: 44 additions & 45 deletions fuzztest/fuzzing_bit_gen.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
#ifndef FUZZTEST_FUZZTEST_FUZZING_BIT_GEN_H_
#define FUZZTEST_FUZZTEST_FUZZING_BIT_GEN_H_

#include <cstddef>
#include <cstdint>
#include <cstring>
#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"
Expand All @@ -28,36 +29,50 @@ namespace fuzztest {

/// FuzzingBitGen is a BitGen instance which uses the Abseil mock mechanisms
/// to return distribution specific variates based on the fuzz data stream.
/// 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.
///
/// 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.
/// It is useful to think of the `instruction stream` a sequence of control
/// bytes which modify the behavior of the mocked distribution functions, and
/// distinct from the data stream which is used to provide the random values.
/// The `data_stream` is used to provide the random values for the mocked
/// distribution functions, as well as an internal URBG used for non-mocked
/// functions, such as std::shuffle(...).
///
/// 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.
///
/// Both the data stream and the instruction stream are reused (wrapped around)
/// when they are exhausted.
///
/// 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.
/// internal pnrg which is sometimes used to generate random variates for calls
/// which are not captured through mockable Abseil random distribution methods
/// (for example, calls to std::shuffle(...)).
///
/// 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.
/// It is possible to initialize the FuzzingBitGen in such a way that usage may
/// get stuck in an infinite loop, such as when an underlying distribution
/// function uses rejection sampling, and the generated sequence is
/// deterministic and happens to be be in the rejected set.
///
/// 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.
// Create a FuzzingBitGen from an unowned fuzzed `data_stream` source and an
// optional `instruction_stream` source. Both streams must outlive the
// FuzzingBitGen and are reused (wrapped around) when exhausted.
//
// 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);
// The `instruction_stream` is an opaque control sequence for the mocked
// calls. The `data_stream` is used as the source of random variates for
// both mocked and non-mocked calls; the first 8 bytes are also used to seed
// and internal LCG PRNG.
explicit FuzzingBitGen(absl::Span<const uint8_t> data_stream)
: FuzzingBitGen(data_stream, {}) {}
explicit FuzzingBitGen(absl::Span<const uint8_t> data_stream,
absl::Span<const uint8_t> instruction_stream);

// Disallow copy, assign, and move.
FuzzingBitGen(const FuzzingBitGen&) = delete;
Expand All @@ -75,38 +90,22 @@ class FuzzingBitGen {
return (std::numeric_limits<result_type>::max)();
}

void seed(result_type seed_value = 0) {
absl::uint128 tmp = seed_value;
state_ = lcg(tmp + increment());
}
void seed(result_type seed_value = 0);

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.
absl::uint128 state_ = 0; // Internal URBG state.

absl::Span<const uint8_t> instruction_stream_;
size_t i_ = 0; // Offset into the instruction stream.
absl::Span<const uint8_t> data_stream_;
size_t d_ = 0; // Offset into the data stream.

template <typename>
friend struct ::absl::random_internal::DistributionCaller; // for InvokeMock
Expand Down
Loading
Loading