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
37 changes: 20 additions & 17 deletions domain_tests/bitgen_ref_domain_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstdint>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/random/bit_gen_ref.h"
#include "absl/random/random.h"
Expand All @@ -23,38 +25,39 @@
namespace fuzztest {
namespace {

TEST(BitGenRefDomainTest, DistinctVariatesGeneratedByCallOperator) {
TEST(BitGenRefDomainTest, DefaultInitializationIsRepeatable) {
absl::BitGen bitgen_for_seeding;

Domain<absl::BitGenRef> domain = Arbitrary<absl::BitGenRef>();
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();

std::vector<absl::BitGenRef::result_type> a, b;
std::vector<uint64_t> variates;
for (int i = 0; i < 10; ++i) {
a.push_back(v0.user_value());
b.push_back(v1.user_value());
variates.push_back(v0.user_value());
}
EXPECT_NE(a, b);
// The default domain does not require either data or control streams.
// So the initial sequences may be the same, but we generally don't expect
// them all to be the same.
EXPECT_THAT(variates, testing::Not(testing::Each(testing::Eq(variates[0]))));
}

TEST(BitGenRefDomainTest, AbseilUniformReturnsLowerBoundWhenExhausted) {
TEST(BitGenRefDomainTest, AbseilUniformIsFunctionalWhenExhausted) {
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
7 changes: 7 additions & 0 deletions e2e_tests/functional_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1707,6 +1707,13 @@ TEST_P(FuzzingModeCrashFindingTest, BitGenRefTestFindsAbortInFuzzingMode) {
ExpectTargetAbort(status, std_err);
}

TEST_P(FuzzingModeCrashFindingTest,
BitGenRefShuffleTestFindsAbortInFuzzingMode) {
auto [status, std_out, std_err] = Run("MySuite.BitGenRefShuffle");
EXPECT_THAT_LOG(std_err, HasSubstr("argument 0: absl::BitGenRefShuffle{}"));
ExpectTargetAbort(status, std_err);
}

TEST_P(FuzzingModeCrashFindingTest,
FixedSizeVectorValueTestFindsAbortInFuzzingMode) {
auto [status, std_out, std_err] = Run("MySuite.FixedSizeVectorValue");
Expand Down
2 changes: 2 additions & 0 deletions e2e_tests/testdata/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ cc_binary(
"@abseil-cpp//absl/strings:str_format",
"@abseil-cpp//absl/strings:string_view",
"@com_google_fuzztest//fuzztest",
"@com_google_fuzztest//fuzztest:fuzzing_bit_gen",
"@com_google_fuzztest//fuzztest:fuzztest_gtest_main",
"@com_google_fuzztest//fuzztest/internal:test_protobuf_cc_proto",
],
Expand Down Expand Up @@ -95,6 +96,7 @@ cc_binary(
"@com_google_fuzztest//common:logging",
"@com_google_fuzztest//fuzztest",
"@com_google_fuzztest//fuzztest:flatbuffers",
"@com_google_fuzztest//fuzztest:fuzzing_bit_gen",
"@com_google_fuzztest//fuzztest:fuzztest_gtest_main",
"@com_google_fuzztest//fuzztest:googletest_fixture_adapter",
"@com_google_fuzztest//fuzztest/internal:test_flatbuffers_cc_fbs",
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
12 changes: 12 additions & 0 deletions fuzztest/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,23 @@ 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"],
hdrs = ["fuzzing_bit_gen.h"],
deps = [
"@abseil-cpp//absl/base:core_headers",
"@abseil-cpp//absl/base:fast_type_id",
"@abseil-cpp//absl/base:no_destructor",
"@abseil-cpp//absl/container:flat_hash_map",
Expand Down
126 changes: 103 additions & 23 deletions fuzztest/fuzzing_bit_gen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,42 +18,111 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#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) {
// 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);
// Minimal implementation of a PCG64 engine equivalent to xsl_rr_128_64.
inline constexpr absl::uint128 multiplier() {
return absl::MakeUint128(0x2360ed051fc65da4, 0x4385df649fccf645);
}
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);
}

enum class Instruction : uint8_t {
kDataStreamVariate = 0,
kLCGVariate = 1,
kMin = 2,
kMax = 3,
kMean = 4,
kAlternateVariate = 5,
};

Instruction byte_to_instruction(uint8_t byte) {
return static_cast<Instruction>(byte % 6);
}

} // namespace

FuzzingBitGen::FuzzingBitGen(absl::Span<const uint8_t> data_stream,
absl::Span<const uint8_t> control_stream,
uint64_t seed_value)
: control_stream_(control_stream), data_stream_(data_stream) {
seed(seed_value);
}

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;
void FuzzingBitGen::DataStreamFn(bool use_lcg, void* result,
size_t result_size) {
if (!use_lcg && !data_stream_.empty()) {
// Consume up to result_size bytes from the data stream.
size_t n =
result_size < data_stream_.size() ? result_size : data_stream_.size();
memcpy(result, data_stream_.data(), n);
data_stream_.remove_prefix(n);
return;
}

// Fallback to the internal URBG.
// The stream is expired. Generate up to 16 bytes from the LCG.
state_ = lcg(state_);
return mix(state_);
uint64_t x = mix(state_);
memcpy(result, &x, result_size > sizeof(x) ? sizeof(x) : result_size);
if (result_size > sizeof(x)) {
state_ = lcg(state_);
uint64_t x = mix(state_);
memcpy(static_cast<uint8_t*>(result) + sizeof(x), &x,
result_size - sizeof(x) > sizeof(x) ? sizeof(x)
: result_size - sizeof(x));
}
}

uint64_t FuzzingBitGen::operator()() {
// Use the control stream to determine the return value.
if (c_ >= control_stream_.size()) {
c_ = 0;
}
Instruction instruction =
control_stream_.empty()
? (data_stream_.empty() ? Instruction::kLCGVariate
: Instruction::kDataStreamVariate)
: byte_to_instruction(control_stream_[c_++]);
switch (instruction) {
case Instruction::kMin:
return 0; // min
case Instruction::kMax:
return (std::numeric_limits<uint64_t>::max)(); // max
case Instruction::kMean:
return (std::numeric_limits<uint64_t>::max)() / 2; // mean
default:
break;
}
uint64_t x = 0;
DataStreamFn(instruction == Instruction::kLCGVariate, &x, sizeof(x));
return x;
}

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 +142,18 @@ bool FuzzingBitGen::InvokeMock(absl::FastTypeIdType key_id, void* args_tuple,
if (it == fuzzing_map->end()) {
return false;
}
it->second(data_stream_, args_tuple, result);

if (c_ >= control_stream_.size()) {
c_ = 0;
}
uint8_t control_byte = control_stream_.empty() ? 0 : control_stream_[c_++];
const bool use_lcg =
byte_to_instruction(control_byte) == Instruction::kLCGVariate;
auto data_stream_fn = [this, use_lcg](void* result, size_t n) {
this->DataStreamFn(use_lcg, result, n);
};

it->second(data_stream_fn, control_byte, args_tuple, result);
return true;
}

Expand Down
Loading
Loading