Skip to content

Commit

Permalink
#Centipede Copy the crossover algorithm of the legacy mutator to the …
Browse files Browse the repository at this point in the history
…new mutator.

Also share the crossover knobs across the two mutators.

PiperOrigin-RevId: 578273646
  • Loading branch information
xinhaoyuan authored and copybara-github committed Oct 31, 2023
1 parent 8aaf126 commit eb90e7a
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 13 deletions.
3 changes: 3 additions & 0 deletions centipede/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -834,8 +834,10 @@ cc_library(
srcs = ["fuzztest_mutator.cc"],
hdrs = ["fuzztest_mutator.h"],
deps = [
":byte_array_mutator",
":defs",
":execution_metadata",
":knobs",
":mutation_input",
"@com_google_absl//absl/random",
"@com_google_fuzztest//fuzztest:domain_core",
Expand Down Expand Up @@ -1368,6 +1370,7 @@ cc_test(
deps = [
":defs",
":fuzztest_mutator",
":knobs",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/strings",
"@com_google_googletest//:gtest_main",
Expand Down
5 changes: 2 additions & 3 deletions centipede/byte_array_mutator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ void ByteArrayMutator::CrossOverOverwrite(ByteArray &data,
data.begin() + pos);
}

static const KnobId knob_cross_over_insert_or_overwrite =
const KnobId knob_cross_over_insert_or_overwrite =
Knobs::NewId("cross_over_insert_or_overwrite");

void ByteArrayMutator::CrossOver(ByteArray &data, const ByteArray &other) {
Expand All @@ -316,8 +316,7 @@ void ByteArrayMutator::CrossOver(ByteArray &data, const ByteArray &other) {
// Controls how much crossover is used during mutations.
// https://en.wikipedia.org/wiki/Crossover_(genetic_algorithm)
// TODO(kcc): add tests with different values of knobs.
static const KnobId knob_mutate_or_crossover =
Knobs::NewId("mutate_or_crossover");
const KnobId knob_mutate_or_crossover = Knobs::NewId("mutate_or_crossover");

void ByteArrayMutator::MutateMany(const std::vector<MutationInputRef> &inputs,
size_t num_mutants,
Expand Down
8 changes: 8 additions & 0 deletions centipede/byte_array_mutator.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,14 @@ class ByteArrayMutator {
CmpDictionary cmp_dictionary_;
};

// Controls how much crossover is used during mutations.
// https://en.wikipedia.org/wiki/Crossover_(genetic_algorithm)
// TODO(kcc): add tests with different values of knobs.
extern const KnobId knob_mutate_or_crossover;
// Controls how much crossver inserts data from the other input instead of
// overwriting.
extern const KnobId knob_cross_over_insert_or_overwrite;

} // namespace centipede

#endif // THIRD_PARTY_CENTIPEDE_BYTE_ARRAY_MUTATOR_H_
2 changes: 1 addition & 1 deletion centipede/centipede_callbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class CentipedeCallbacks {
CentipedeCallbacks(const Environment &env)
: env_(env),
byte_array_mutator_(env.knobs, GetRandomSeed(env.seed)),
fuzztest_mutator_(GetRandomSeed(env.seed)),
fuzztest_mutator_(env.knobs, GetRandomSeed(env.seed)),
inputs_blobseq_(shmem_name1_.c_str(), env.shmem_size_mb << 20,
env.use_posix_shmem),
outputs_blobseq_(shmem_name2_.c_str(), env.shmem_size_mb << 20,
Expand Down
58 changes: 55 additions & 3 deletions centipede/fuzztest_mutator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@

#include "./centipede/fuzztest_mutator.h"

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <random>

#include "absl/random/random.h"
#include "./centipede/byte_array_mutator.h"
#include "./centipede/defs.h"
#include "./centipede/knobs.h"
#include "./fuzztest/domain_core.h"

namespace centipede {
Expand All @@ -31,8 +40,9 @@ class FuzzTestMutator::MutatorDomain : public MutatorDomainBase {
: MutatorDomainBase(fuzztest::VectorOf(fuzztest::Arbitrary<uint8_t>())) {}
};

FuzzTestMutator::FuzzTestMutator(uint64_t seed)
: prng_(std::seed_seq({seed, seed >> 32})),
FuzzTestMutator::FuzzTestMutator(const Knobs &knobs, uint64_t seed)
: knobs_(knobs),
prng_(std::seed_seq({seed, seed >> 32})),
domain_(std::make_unique<MutatorDomain>()) {
domain_->WithMinSize(1).WithMaxSize(max_len_);
if (fuzztest::internal::GetExecutionCoverage() == nullptr) {
Expand All @@ -44,6 +54,41 @@ FuzzTestMutator::FuzzTestMutator(uint64_t seed)

FuzzTestMutator::~FuzzTestMutator() = default;

void FuzzTestMutator::CrossOverInsert(ByteArray &data, const ByteArray &other) {
// insert other[first:first+size] at data[pos]
const auto size = absl::Uniform<size_t>(
prng_, 1, std::min(max_len_ - data.size(), other.size()) + 1);
const auto first = absl::Uniform<size_t>(prng_, 0, other.size() - size + 1);
const auto pos = absl::Uniform<size_t>(prng_, 0, data.size() + 1);
data.insert(data.begin() + pos, other.begin() + first,
other.begin() + first + size);
}

void FuzzTestMutator::CrossOverOverwrite(ByteArray &data,
const ByteArray &other) {
// Overwrite data[pos:pos+size] with other[first:first+size].
// Overwrite no more than half of data.
size_t max_size = std::max(1UL, data.size() / 2);
const auto first = absl::Uniform<size_t>(prng_, 0, other.size());
max_size = std::min(max_size, other.size() - first);
const auto size = absl::Uniform<size_t>(prng_, 1, max_size + 1);
const auto pos = absl::Uniform<size_t>(prng_, 0, data.size() - size + 1);
std::copy(other.begin() + first, other.begin() + first + size,
data.begin() + pos);
}

void FuzzTestMutator::CrossOver(ByteArray &data, const ByteArray &other) {
if (data.size() >= max_len_) {
CrossOverOverwrite(data, other);
} else {
if (knobs_.GenerateBool(knob_cross_over_insert_or_overwrite, prng_())) {
CrossOverInsert(data, other);
} else {
CrossOverOverwrite(data, other);
}
}
}

void FuzzTestMutator::MutateMany(const std::vector<MutationInputRef>& inputs,
size_t num_mutants,
std::vector<ByteArray>& mutants) {
Expand All @@ -57,7 +102,14 @@ void FuzzTestMutator::MutateMany(const std::vector<MutationInputRef>& inputs,
for (int i = 0; i < num_mutants; ++i) {
auto mutant = inputs[absl::Uniform<size_t>(prng_, 0, inputs.size())].data;
if (mutant.size() > max_len_) mutant.resize(max_len_);
domain_->Mutate(mutant, prng_, /*only_shrink=*/false);
if (knobs_.GenerateBool(knob_mutate_or_crossover, prng_())) {
// Perform crossover with some other input. It may be the same input.
const auto &other_input =
inputs[absl::Uniform<size_t>(prng_, 0, inputs.size())].data;
CrossOver(mutant, other_input);
} else {
domain_->Mutate(mutant, prng_, /*only_shrink=*/false);
}
mutants.push_back(std::move(mutant));
}
}
Expand Down
12 changes: 10 additions & 2 deletions centipede/fuzztest_mutator.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "absl/random/random.h"
#include "./centipede/defs.h"
#include "./centipede/execution_metadata.h"
#include "./centipede/knobs.h"
#include "./centipede/mutation_input.h"

namespace centipede {
Expand All @@ -31,8 +32,8 @@ namespace centipede {
// This class is thread-compatible.
class FuzzTestMutator {
public:
// Initialize the mutator with the given RNG `seed`.
explicit FuzzTestMutator(uint64_t seed);
// Initialize the mutator with the given `knobs` and RNG `seed`.
explicit FuzzTestMutator(const Knobs &knobs, uint64_t seed);
~FuzzTestMutator();

// Takes non-empty `inputs`, produces `num_mutants` mutations in `mutants`.
Expand All @@ -56,10 +57,17 @@ class FuzzTestMutator {
// Propagates the execution `metadata` to the internal mutation dictionary.
void SetMetadata(const ExecutionMetadata& metadata);

// The crossover algorithm based on the legacy ByteArrayMutator.
// TODO: Implement and use the domain level crossover.
void CrossOverInsert(ByteArray &data, const ByteArray &other);
void CrossOverOverwrite(ByteArray &data, const ByteArray &other);
void CrossOver(ByteArray &data, const ByteArray &other);

// Size limits on the cmp entries to be used in mutation.
static constexpr uint8_t kMaxCmpEntrySize = 15;
static constexpr uint8_t kMinCmpEntrySize = 2;

const Knobs &knobs_;
absl::BitGen prng_;
size_t max_len_ = 1000;
std::unique_ptr<MutatorDomain> domain_;
Expand Down
79 changes: 75 additions & 4 deletions centipede/fuzztest_mutator_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,23 @@
#include "absl/container/flat_hash_set.h"
#include "absl/strings/str_join.h"
#include "./centipede/defs.h"
#include "./centipede/knobs.h"

namespace centipede {

namespace {

using ::testing::AllOf;
using ::testing::Each;
using ::testing::IsSupersetOf;
using ::testing::Le;
using ::testing::SizeIs;
using ::testing::Values;

TEST(FuzzTestMutator, DifferentRngSeedsLeadToDifferentMutantSequences) {
FuzzTestMutator mutator[2]{FuzzTestMutator(/*seed=*/1),
FuzzTestMutator(/*seed=*/2)};
const Knobs knobs;
FuzzTestMutator mutator[2]{FuzzTestMutator(knobs, /*seed=*/1),
FuzzTestMutator(knobs, /*seed=*/2)};

std::vector<ByteArray> res[2];
for (size_t i = 0; i < 2; i++) {
Expand All @@ -56,7 +59,8 @@ TEST(FuzzTestMutator, DifferentRngSeedsLeadToDifferentMutantSequences) {

TEST(FuzzTestMutator, MutateManyWorksWithInputsLargerThanMaxLen) {
constexpr size_t kMaxLen = 4;
FuzzTestMutator mutator(/*seed=*/1);
const Knobs knobs;
FuzzTestMutator mutator(knobs, /*seed=*/1);
EXPECT_TRUE(mutator.set_max_len(kMaxLen));
constexpr size_t kNumMutantsToGenerate = 10000;
std::vector<ByteArray> mutants;
Expand All @@ -75,6 +79,72 @@ TEST(FuzzTestMutator, MutateManyWorksWithInputsLargerThanMaxLen) {
AllOf(SizeIs(kNumMutantsToGenerate), Each(SizeIs(Le(kMaxLen)))));
}

TEST(FuzzTestMutator, CrossOverInsertsDataFromOtherInputs) {
const Knobs knobs;
FuzzTestMutator mutator(knobs, /*seed=*/1);
constexpr size_t kNumMutantsToGenerate = 100000;
std::vector<ByteArray> mutants;

mutator.MutateMany(
{
{.data = {0, 1, 2, 3}},
{.data = {4, 5, 6, 7}},
},
kNumMutantsToGenerate, mutants);

EXPECT_THAT(mutants, IsSupersetOf(std::vector<ByteArray>{
// The entire other input
{4, 5, 6, 7, 0, 1, 2, 3},
{0, 1, 4, 5, 6, 7, 2, 3},
{0, 1, 2, 3, 4, 5, 6, 7},
// The prefix of other input
{4, 5, 6, 0, 1, 2, 3},
{0, 1, 4, 5, 6, 2, 3},
{0, 1, 2, 3, 4, 5, 6},
// The suffix of other input
{5, 6, 7, 0, 1, 2, 3},
{0, 1, 5, 6, 7, 2, 3},
{0, 1, 2, 3, 5, 6, 7},
// The middle of other input
{5, 6, 0, 1, 2, 3},
{0, 1, 5, 6, 2, 3},
{0, 1, 2, 3, 5, 6},
}));
}

TEST(FuzzTestMutator, CrossOverOverwritesDataFromOtherInputs) {
const Knobs knobs;
FuzzTestMutator mutator(knobs, /*seed=*/1);
constexpr size_t kNumMutantsToGenerate = 100000;
std::vector<ByteArray> mutants;

mutator.MutateMany(
{
{.data = {0, 1, 2, 3, 4, 5, 6, 7}},
{.data = {100, 101, 102, 103}},
},
kNumMutantsToGenerate, mutants);

EXPECT_THAT(mutants, IsSupersetOf(std::vector<ByteArray>{
// The entire other input
{100, 101, 102, 103, 4, 5, 6, 7},
{0, 1, 100, 101, 102, 103, 6, 7},
{0, 1, 2, 3, 100, 101, 102, 103},
// The prefix of other input
{100, 101, 102, 3, 4, 5, 6, 7},
{0, 1, 2, 100, 101, 102, 6, 7},
{0, 1, 2, 3, 4, 100, 101, 102},
// The suffix of other input
{101, 102, 103, 3, 4, 5, 6, 7},
{0, 1, 2, 101, 102, 103, 6, 7},
{0, 1, 2, 3, 4, 101, 102, 103},
// The middle of other input
{101, 102, 2, 3, 4, 5, 6, 7},
{0, 1, 2, 101, 102, 5, 6, 7},
{0, 1, 2, 3, 4, 5, 101, 102},
}));
}

// Test parameter containing the mutation settings and the expectations of a
// single mutation step.
struct MutationStepTestParameter {
Expand Down Expand Up @@ -102,7 +172,8 @@ class MutationStepTest
: public testing::TestWithParam<MutationStepTestParameter> {};

TEST_P(MutationStepTest, GeneratesExpectedMutantsAndAvoidsUnexpectedMutants) {
FuzzTestMutator mutator(/*seed=*/1);
const Knobs knobs;
FuzzTestMutator mutator(knobs, /*seed=*/1);
ASSERT_LE(GetParam().min_num_iterations, GetParam().max_num_iterations);
if (GetParam().max_len.has_value())
EXPECT_TRUE(mutator.set_max_len(*GetParam().max_len));
Expand Down

0 comments on commit eb90e7a

Please sign in to comment.