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
15 changes: 14 additions & 1 deletion centipede/centipede.cc
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ Centipede::Centipede(const Environment &env, CentipedeCallbacks &user_callbacks,
FUZZTEST_CHECK(env_.seed) << "env_.seed must not be zero";
if (!env_.input_filter.empty() && env_.fork_server)
input_filter_cmd_.StartForkServer(TemporaryLocalDirPath(), "input_filter");
if (env_.corpus_weight_method == Corpus::kWeightMethodNameForUniform) {
corpus_weight_method_ = Corpus::WeightMethod::Uniform;
} else if (env_.corpus_weight_method == Corpus::kWeightMethodNameForRecency) {
corpus_weight_method_ = Corpus::WeightMethod::Recency;
} else if (env_.corpus_weight_method == Corpus::kWeightMethodNameForRarity) {
corpus_weight_method_ = Corpus::WeightMethod::Rarity;
} else {
FUZZTEST_LOG(WARNING) << "Unknown corpus weight method "
<< env_.corpus_weight_method << " - fall back to "
<< Corpus::kWeightMethodNameForRarity;
corpus_weight_method_ = Corpus::WeightMethod::Rarity;
}
}

void Centipede::CorpusToFiles(const Environment &env, std::string_view dir) {
Expand Down Expand Up @@ -474,7 +486,8 @@ bool Centipede::RunBatch(
}
}
}
corpus_.UpdateWeights(fs_, coverage_frontier_, env_.exec_time_weight_scaling);
corpus_.UpdateWeights(fs_, coverage_frontier_, corpus_weight_method_,
env_.exec_time_weight_scaling);
return batch_gained_new_coverage;
}

Expand Down
1 change: 1 addition & 0 deletions centipede/centipede.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ class Centipede {

FeatureSet fs_;
Corpus corpus_;
Corpus::WeightMethod corpus_weight_method_;
CoverageFrontier coverage_frontier_;
size_t num_runs_ = 0; // counts executed inputs

Expand Down
3 changes: 3 additions & 0 deletions centipede/centipede_flags.inc
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ CENTIPEDE_FLAG(
bool, use_corpus_weights, true,
"If true, use weighted distribution when choosing the corpus element "
"to mutate. This flag is mostly for Centipede developers.")
CENTIPEDE_FLAG(std::string, corpus_weight_method, "rarity",
"The weight method to use on corpus. Available options are "
"`uniform`, `recency`, and `rarity` (fallback).")
CENTIPEDE_FLAG(
bool, exec_time_weight_scaling, true,
"If true, scale the corpus weight by the execution time of each input.")
Expand Down
31 changes: 22 additions & 9 deletions centipede/corpus.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,12 @@ namespace fuzztest::internal {
// Corpus
//------------------------------------------------------------------------------

// Returns the weight of `fv` computed using `fs` and `coverage_frontier`.
static size_t ComputeWeight(const FeatureVec &fv, const FeatureSet &fs,
const CoverageFrontier &coverage_frontier) {
size_t weight = fs.ComputeWeight(fv);
// Returns the weight of `fv` computed using `coverage_frontier`.
static size_t ComputeFrontierWeight(const FeatureVec& fv,
const CoverageFrontier& coverage_frontier) {
// The following is checking for the cases where PCTable is not present. In
// such cases, we cannot use any ControlFlow related features.
if (coverage_frontier.MaxPcIndex() == 0) return weight;
if (coverage_frontier.MaxPcIndex() == 0) return 1;
size_t frontier_weights_sum = 0;
for (const auto feature : fv) {
if (!feature_domains::kPCs.Contains(feature)) continue;
Expand All @@ -63,7 +62,7 @@ static size_t ComputeWeight(const FeatureVec &fv, const FeatureSet &fs,
frontier_weights_sum += coverage_frontier.FrontierWeight(pc_index);
}
}
return weight * (frontier_weights_sum + 1); // Multiply by at least 1.
return frontier_weights_sum + 1; // Multiply by at least 1.
}

std::pair<size_t, size_t> Corpus::MaxAndAvgSize() const {
Expand All @@ -79,14 +78,27 @@ std::pair<size_t, size_t> Corpus::MaxAndAvgSize() const {

void Corpus::UpdateWeights(const FeatureSet& fs,
const CoverageFrontier& coverage_frontier,
bool scale_by_exec_time) {
WeightMethod method, bool scale_by_exec_time) {
std::vector<double> weights;
weights.resize(records_.size());
for (size_t i = 0, n = records_.size(); i < n; ++i) {
auto& record = records_[i];
const size_t unseen = fs.PruneFeaturesAndCountUnseen(record.features);
FUZZTEST_CHECK_EQ(unseen, 0);
weights[i] = fs.ComputeWeight(record.features);
switch (method) {
case WeightMethod::Uniform:
weights[i] = 1;
break;
case WeightMethod::Recency:
weights[i] = i + 1;
break;
case WeightMethod::Rarity:
weights[i] = fs.ComputeRarityWeight(record.features);
break;
default:
FUZZTEST_LOG(FATAL) << "Unknown corpus weight method";
}
weights[i] *= ComputeFrontierWeight(record.features, coverage_frontier);
}
if (scale_by_exec_time) {
double total_exec_time_usec = 0;
Expand Down Expand Up @@ -199,7 +211,8 @@ void Corpus::Add(const ByteArray& data, const FeatureVec& fv,
<< "Got request to add empty element to corpus: ignoring";
FUZZTEST_CHECK_EQ(records_.size(), weighted_distribution_.size());
records_.push_back({data, fv, metadata, stats});
weighted_distribution_.AddWeight(ComputeWeight(fv, fs, coverage_frontier));
// Will be updated by `UpdateWeights`.
weighted_distribution_.AddWeight(0);
}

const CorpusRecord& Corpus::WeightedRandom(absl::BitGenRef rng) const {
Expand Down
18 changes: 14 additions & 4 deletions centipede/corpus.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ struct CorpusRecord {
// Allows to prune (forget) inputs that become uninteresting.
class Corpus {
public:
enum class WeightMethod {
Uniform,
Recency,
Rarity,
};

static constexpr std::string_view kWeightMethodNameForUniform = "uniform";
static constexpr std::string_view kWeightMethodNameForRecency = "recency";
static constexpr std::string_view kWeightMethodNameForRarity = "rarity";

Corpus() = default;

Corpus(const Corpus &) = default;
Expand All @@ -120,12 +130,12 @@ class Corpus {
// Returns the number of removed elements.
size_t Prune(const FeatureSet &fs, const CoverageFrontier &coverage_frontier,
size_t max_corpus_size, Rng &rng);
// Updates the corpus weights according to `fs` and `coverage_frontier`. If
// `scale_by_exec_time` is set, scales the weights by the corpus execution
// time relative to the average.
// Updates the corpus weights according to `fs` and `coverage_frontier` using
// the weight `method`. If `scale_by_exec_time` is set, scales the weights by
// the corpus execution time relative to the average.
void UpdateWeights(const FeatureSet& fs,
const CoverageFrontier& coverage_frontier,
bool scale_by_exec_time);
WeightMethod method, bool scale_by_exec_time);

// Accessors.

Expand Down
12 changes: 8 additions & 4 deletions centipede/corpus_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ TEST(Corpus, Prune) {
Add({{2}, {30, 40}});
Add({{3}, {40, 50}});
Add({{4}, {10, 20}});
corpus.UpdateWeights(fs, coverage_frontier, /*scale_by_exec_time=*/false);
corpus.UpdateWeights(fs, coverage_frontier, Corpus::WeightMethod::Rarity,
/*scale_by_exec_time=*/false);

// Prune. Features 20 and 40 are frequent => input {0} will be removed.
EXPECT_EQ(corpus.NumActive(), 5);
Expand All @@ -124,7 +125,8 @@ TEST(Corpus, Prune) {
VerifyActiveInputs({{1}, {2}, {3}, {4}});

Add({{5}, {30, 60}});
corpus.UpdateWeights(fs, coverage_frontier, /*scale_by_exec_time=*/false);
corpus.UpdateWeights(fs, coverage_frontier, Corpus::WeightMethod::Rarity,
/*scale_by_exec_time=*/false);

EXPECT_EQ(corpus.NumTotal(), 6);
// Prune. Feature 30 is now frequent => inputs {1} and {2} will be removed.
Expand Down Expand Up @@ -181,14 +183,16 @@ TEST(Corpus, ScalesWeightsWithExecTime) {
};

// The weights should be equal without exec time scaling.
corpus.UpdateWeights(fs, coverage_frontier, /*scale_by_exec_time=*/false);
corpus.UpdateWeights(fs, coverage_frontier, Corpus::WeightMethod::Rarity,
/*scale_by_exec_time=*/false);
ComputeFreq();
EXPECT_NEAR(freq[0], kNumIter / 3, 100);
EXPECT_NEAR(freq[1], kNumIter / 3, 100);
EXPECT_NEAR(freq[2], kNumIter / 3, 100);

// The weights should favor {0} over {1} over {2} with exec time scaling.
corpus.UpdateWeights(fs, coverage_frontier, /*scale_by_exec_time=*/true);
corpus.UpdateWeights(fs, coverage_frontier, Corpus::WeightMethod::Rarity,
/*scale_by_exec_time=*/true);
ComputeFreq();
EXPECT_GT(freq[0], freq[1] + 100);
EXPECT_GT(freq[1], freq[2] + 100);
Expand Down
5 changes: 3 additions & 2 deletions centipede/feature_set.cc
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ void FeatureSet::MergeFeatures(const FeatureVec& features) {
}

__attribute__((noinline)) // to see it in profile.
uint64_t
FeatureSet::ComputeWeight(const FeatureVec &features) const {
double FeatureSet::ComputeRarityWeight(const FeatureVec& features) const {
// Use uint64_t to keep the previous behavior. Maybe we want to switch it to
// double later.
uint64_t weight = 0;
for (auto feature : features) {
// The less frequent is the feature, the more valuable it is.
Expand Down
8 changes: 4 additions & 4 deletions centipede/feature_set.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ class FeatureSet {
return frequencies_[feature];
}

// Computes combined weight of `features`.
// The less frequent the feature is, the bigger its weight.
// The weight of a FeatureVec is a sum of individual feature weights.
uint64_t ComputeWeight(const FeatureVec &features) const;
// Computes combined weight of `features` based on the feature rarity that
// scales linearly. The less frequent the feature is, the bigger its
// weight. The weight of a FeatureVec is a sum of individual feature weights.
double ComputeRarityWeight(const FeatureVec& features) const;

// Returns a debug string representing the state of *this.
std::string DebugString() const;
Expand Down
8 changes: 4 additions & 4 deletions centipede/feature_set_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ namespace {
TEST(FeatureSet, ComputeWeight) {
FeatureSet feature_set(10, {});

auto W = [&](const FeatureVec &features) -> uint64_t {
return feature_set.ComputeWeight(features);
auto W = [&](const FeatureVec& features) -> uint64_t {
return feature_set.ComputeRarityWeight(features);
};

feature_set.MergeFeatures({1, 2, 3});
Expand Down Expand Up @@ -60,8 +60,8 @@ TEST(FeatureSet, ComputeWeightWithDifferentDomains) {
/* three features from domain #3 */ f3, f3 + 1,
f3 + 2});

auto weight = [&](const FeatureVec &features) -> uint64_t {
return feature_set.ComputeWeight(features);
auto weight = [&](const FeatureVec& features) -> uint64_t {
return feature_set.ComputeRarityWeight(features);
};

// Test that features from a less frequent domain have more weight.
Expand Down
Loading