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
22 changes: 11 additions & 11 deletions domain_tests/aggregate_combinators_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -207,27 +207,27 @@ TEST(ConstructorOf, WorksWithMoveConstructors) {

TEST(VariantOf, InitGenerateValidValues) {
using X = std::variant<int, int, std::string>;
Domain<X> domain =
VariantOf(ElementOf({5, 10}), InRange(50, 500), PrintableAsciiString());
Domain<X> domain = VariantOf(
ElementOf({5, 10}), InRange(50, 500),
PrintableAsciiString().WithSeeds({"goober", "doober", "skoober"}));
absl::BitGen bitgen;
std::vector<absl::flat_hash_set<int>> int_unique_value_arr(2);
absl::flat_hash_set<std::string> string_unique_values;
constexpr int n = 20;
while (int_unique_value_arr[0].size() < 2 ||
int_unique_value_arr[1].size() < n ||
string_unique_values.size() < n) {
X value = Value(domain, bitgen).user_value;
if (value.index() == 0) {
int val = std::get<0>(value);
int_unique_value_arr[1].size() < 20 ||
string_unique_values.size() < 4) {
auto value = Value(domain, bitgen);
if (value.user_value.index() == 0) {
int val = std::get<0>(value.user_value);
EXPECT_THAT(val, AnyOf(5, 10));
int_unique_value_arr[0].insert(val);
} else if (value.index() == 1) {
int val = std::get<1>(value);
} else if (value.user_value.index() == 1) {
int val = std::get<1>(value.user_value);
EXPECT_LE(val, 500);
EXPECT_GE(val, 50);
int_unique_value_arr[1].insert(val);
} else {
string_unique_values.insert(std::get<2>(value));
string_unique_values.insert(std::get<2>(value.user_value));
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions domain_tests/arbitrary_domains_protobuf_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -571,8 +571,7 @@ TEST(ProtocolBuffer, WithFieldsAlwaysSetResetsWithMaxRepeatedFieldsSize) {
},
1)
.WithFieldsAlwaysSet();

EXPECT_THAT(GenerateInitialValues(domain, 1000),
EXPECT_THAT(GenerateNonUniqueValues(domain, 100),
Contains(ResultOf(
[](const Value<Domain<TestProtobuf>>& val) {
return val.user_value.rep_i32_size();
Expand Down
14 changes: 14 additions & 0 deletions domain_tests/container_combinators_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,20 @@ TEST(UniqueElementsVectorOf, VerifyRoundTripThroughConversion) {
VerifyRoundTripThroughConversion(GenerateValues(domain), domain);
}

// Reproducer for b/394904065
TEST(UniqueElementsVectorOf, GeneratesValuesFromInnerDomainWithMaxSize) {
const int kNumElementsThatWillWork = 3 /* num chars */ + 1 /* empty string */
+ 1;
auto string_domain =
StringOf(fuzztest::InRange<char>('a', 'c')).WithMaxSize(400);
auto domain =
UniqueElementsVectorOf(string_domain).WithSize(kNumElementsThatWillWork);
for (const auto& value : GenerateValues(domain)) {
ASSERT_THAT(value.user_value, Each(MatchesRegex("[abc]*")));
ASSERT_THAT(value.user_value, SizeIs(kNumElementsThatWillWork));
}
}

TEST(ContainerCombinatorTest, ArrayOfOne) {
// A domain of std::array<T, 1> values can be defined in two ways:
auto with_explicit_size = ArrayOf<1>(InRange(0.0, 1.0));
Expand Down
9 changes: 6 additions & 3 deletions domain_tests/container_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,15 @@ void TestMinMaxContainerSize(Domain domain, size_t min_size, size_t max_size) {

ASSERT_THAT(v.user_value, size_match);
sizes.insert(v.user_value.size());
v.Mutate(domain, bitgen, {}, false);
ASSERT_THAT(v.user_value, size_match);
for (int j = 0; j < 10; ++j) {
v.Mutate(domain, bitgen, {}, false);
ASSERT_THAT(v.user_value, size_match);
sizes.insert(v.user_value.size());
}

// Mutating the value can reach the max, but only for small max sizes
// because it would otherwise take too long.
if (max_size <= 10) {
if (max_size <= v.user_value.size() + 10) {
auto max_v = v;
while (max_v.user_value.size() < max_size) {
v.Mutate(domain, bitgen, {}, false);
Expand Down
11 changes: 7 additions & 4 deletions domain_tests/domain_testing.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,10 +267,12 @@ auto GenerateValues(Domain domain, int num_seeds = 10, int num_mutations = 100,
absl::BitGen bitgen;

absl::flat_hash_set<Value<Domain>> seeds;
// Make sure we can make some unique seeds.
// Randomness might create duplicates so keep going until we got them.
while (seeds.size() < num_seeds) {
seeds.insert(Value(domain, bitgen));
auto value = Value(domain, bitgen);
while (!seeds.insert(value).second) {
value.Mutate(domain, bitgen, domain_implementor::MutationMetadata(),
/*only_shrink=*/false);
}
}

auto values = seeds;
Expand Down Expand Up @@ -326,7 +328,8 @@ auto GenerateInitialValues(Domain domain, int n) {
absl::BitGen bitgen;
values.reserve(n);
for (int i = 0; i < n; ++i) {
values.push_back(Value(domain, bitgen));
auto value = Value(domain, bitgen);
values.push_back(std::move(value));
}
return values;
}
Expand Down
13 changes: 13 additions & 0 deletions domain_tests/map_filter_combinator_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ TEST(FlatMap, MutationAcceptsShrinkingOutputDomains) {
// Generate something shrinkable
while (!value.has_value() || value->user_value.empty()) {
value = Value(domain, bitgen);
value->Mutate(domain, bitgen, domain_implementor::MutationMetadata(),
/*only_shrink=*/false);
}
auto mutated = value->corpus_value;
while (!domain.GetValue(mutated).empty()) {
Expand Down Expand Up @@ -336,6 +338,17 @@ TEST(Filter, CanFilterInitCalls) {
EXPECT_THAT(seen, UnorderedElementsAre(2, 4, 6, 8, 10));
}

TEST(Filter, CanFilterInitCallsOnUtf8String) {
Domain<std::string> domain =
Filter([](const std::string& s) { return !s.empty(); }, Utf8String());
absl::BitGen bitgen;
Set<std::string> seen;
while (seen.size() < 2) {
seen.insert(Value(domain, bitgen).user_value);
}
EXPECT_THAT(seen, Not(IsEmpty()));
}

TEST(Filter, CanFilterMutateCalls) {
Domain<int> domain = Filter([](int i) { return i % 2 == 0; }, InRange(1, 10));
absl::BitGen bitgen;
Expand Down
13 changes: 10 additions & 3 deletions domain_tests/string_domains_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,12 @@ TYPED_TEST(HandleTypeTest, Arbitrary) {
Set<TypeParam> unique;

for (int i = 0; i < 100; ++i) {
values.emplace_back(domain, bitgen);
auto value = Value(domain, bitgen);
for (int j = 0; j < 10; ++j) {
value.Mutate(domain, bitgen, domain_implementor::MutationMetadata(),
/*only_shrink=*/false);
}
values.emplace_back(value, domain);
unique.insert(values.back().user_value);
}

Expand Down Expand Up @@ -163,8 +168,10 @@ TEST(Domain, PrintableAsciiString) {

TEST(Domain, Utf8StringWorksWithSeeds) {
auto domain = Utf8String().WithSeeds({"\u0414\u0430!\n"});
EXPECT_THAT(GenerateValues(domain),
Contains(Value(domain, "\u0414\u0430!\n")));
EXPECT_THAT(
GenerateInitialValues(
domain, IterationsToHitAll(/*num_cases=*/1, /*hit_probability=*/0.5)),
Contains(Value(domain, "\u0414\u0430!\n")));
}

TEST(Domain, Utf8StringIgnoresInvalideSeeds) {
Expand Down
47 changes: 4 additions & 43 deletions fuzztest/internal/domains/container_of_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -353,21 +353,6 @@ class ContainerOfImplBase
protected:
InnerDomainT inner_;

size_t ChooseRandomInitialSize(absl::BitGenRef prng) {
// The container size should not be empty (unless max_size_ = 0) because the
// initialization should be random if possible.
// TODO(changochen): Increase the number of generated elements.
// Currently we make container generate zero or one element to avoid
// infinite recursion in recursive data structures. For the example, we want
// to build a domain for `struct X{ int leaf; vector<X> recursive`, the
// expected generated length is `E(X) = E(leaf) + E(recursive)`. If the
// container generate `0-10` elements when calling `Init`, then
// `E(recursive) = 4.5 E(X)`, which will make `E(X) = Infinite`.
// Make some smallish random seed containers.
return absl::Uniform(prng, min_size(),
std::min(max_size() + 1, min_size() + 2));
}

size_t min_size() const { return min_size_; }
size_t max_size() const {
return max_size_.value_or(std::max(min_size_, kDefaultContainerMaxSize));
Expand Down Expand Up @@ -431,10 +416,9 @@ class AssociativeContainerOfImpl

corpus_type Init(absl::BitGenRef prng) {
if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
const size_t size = this->ChooseRandomInitialSize(prng);

corpus_type val;
Grow(val, prng, size, 10000);
Grow(val, prng, this->min_size(), 10000);
if (val.size() < this->min_size()) {
// We tried to make a container with the minimum specified size and we
// could not after a lot of attempts. This could be caused by an
Expand Down Expand Up @@ -468,17 +452,6 @@ Please verify that the inner domain can provide enough values.
Grow(val, prng, 1, kFailuresAllowed);
}

auto GetRandomInnerValue(absl::BitGenRef prng) {
constexpr int kMaxMutations = 100;
const int num_mutations = absl::Zipf(prng, /*hi=*/kMaxMutations);
auto element = this->inner_.Init(prng);
for (int i = 0; i < num_mutations; ++i) {
this->inner_.Mutate(element, prng, domain_implementor::MutationMetadata(),
/*only_shrink=*/false);
}
return element;
}

// Try to grow `val` by `n` elements.
void Grow(corpus_type& val, absl::BitGenRef prng, size_t n,
size_t failures_allowed) {
Expand All @@ -492,7 +465,7 @@ Please verify that the inner domain can provide enough values.
auto real_value = this->GetValue(val);
const size_t final_size = real_value.size() + n;
while (real_value.size() < final_size) {
auto new_element = GetRandomInnerValue(prng);
auto new_element = this->inner_.GetRandomCorpusValue(prng);
if (real_value.insert(this->inner_.GetValue(new_element)).second) {
val.push_back(std::move(new_element));
} else {
Expand Down Expand Up @@ -550,10 +523,9 @@ class SequenceContainerOfImplBase

corpus_type Init(absl::BitGenRef prng) {
if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
const size_t size = this->ChooseRandomInitialSize(prng);
corpus_type val;
while (val.size() < size) {
val.insert(val.end(), GetRandomInnerValue(prng));
while (val.size() < this->min_size()) {
val.insert(val.end(), this->inner_.GetRandomCorpusValue(prng));
}
return val;
}
Expand Down Expand Up @@ -587,17 +559,6 @@ class SequenceContainerOfImplBase
this->inner_.Init(prng));
}

auto GetRandomInnerValue(absl::BitGenRef prng) {
constexpr int kMaxMutations = 100;
int num_mutations = absl::Zipf<int>(prng, /*hi=*/kMaxMutations);
auto element = this->inner_.Init(prng);
for (int i = 0; i < num_mutations; ++i) {
this->inner_.Mutate(element, prng, domain_implementor::MutationMetadata(),
/*only_shrink=*/false);
}
return element;
}

void MutateElement(corpus_type&, absl::BitGenRef prng,
const domain_implementor::MutationMetadata& metadata,
bool only_shrink, typename corpus_type::iterator it) {
Expand Down
6 changes: 6 additions & 0 deletions fuzztest/internal/domains/domain.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@ class Domain {
return Mutate(corpus_value, prng, {}, only_shrink);
}

// See the important notes on GetRandomValue() above on caveats of getting
// random values.
corpus_type GetRandomCorpusValue(absl::BitGenRef prng) {
return inner_->UntypedGetRandomCorpusValue(prng);
}

// The methods below are responsible for transforming between the above
// described three types that domains deal with. Here's a quick overview:
//
Expand Down
30 changes: 15 additions & 15 deletions fuzztest/internal/domains/domain_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,21 +203,6 @@ class DomainBase {
return derived();
}

protected:
// `Derived::Init()` can use this to sample seeds for this domain.
std::optional<CorpusType> MaybeGetRandomSeed(absl::BitGenRef prng) {
if (seed_provider_ != nullptr) {
WithSeeds(std::invoke(seed_provider_));
seed_provider_ = nullptr;
}

static constexpr double kProbabilityToReturnSeed = 0.5;
if (seeds_.empty() || !absl::Bernoulli(prng, kProbabilityToReturnSeed)) {
return std::nullopt;
}
return seeds_[fuzztest::internal::ChooseOffset(seeds_.size(), prng)];
}

// Default implementation of GetRandomCorpusValue() without guarantees on the
// distribution of the returned values. If possible, the derived domain should
// override this with a better implementation.
Expand All @@ -235,6 +220,21 @@ class DomainBase {
return corpus_val;
}

protected:
// `Derived::Init()` can use this to sample seeds for this domain.
std::optional<CorpusType> MaybeGetRandomSeed(absl::BitGenRef prng) {
if (seed_provider_ != nullptr) {
WithSeeds(std::invoke(seed_provider_));
seed_provider_ = nullptr;
}

static constexpr double kProbabilityToReturnSeed = 0.5;
if (seeds_.empty() || !absl::Bernoulli(prng, kProbabilityToReturnSeed)) {
return std::nullopt;
}
return seeds_[absl::Uniform(prng, 0u, seeds_.size())];
}

private:
Derived& derived() { return static_cast<Derived&>(*this); }
const Derived& derived() const { return static_cast<const Derived&>(*this); }
Expand Down
8 changes: 8 additions & 0 deletions fuzztest/internal/domains/domain_type_erasure.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class UntypedDomainConcept {
virtual uint64_t UntypedMutateSelectedField(
GenericDomainCorpusType&, absl::BitGenRef,
const domain_implementor::MutationMetadata&, bool, uint64_t) = 0;
virtual GenericDomainCorpusType UntypedGetRandomCorpusValue(
absl::BitGenRef prng) = 0;
virtual GenericDomainValueType UntypedGetValue(
const GenericDomainCorpusType& v) const = 0;
virtual void UntypedPrintCorpusValue(
Expand Down Expand Up @@ -167,6 +169,12 @@ class DomainModel final : public TypedDomainConcept<value_type_t<D>> {
}
}

GenericDomainCorpusType UntypedGetRandomCorpusValue(
absl::BitGenRef prng) final {
return GenericDomainCorpusType(std::in_place_type<CorpusType>,
domain_.GetRandomCorpusValue(prng));
}

std::optional<GenericDomainCorpusType> UntypedParseCorpus(
const IRObject& obj) const final {
if (auto res = domain_.ParseCorpus(obj)) {
Expand Down
2 changes: 1 addition & 1 deletion fuzztest/internal/domains/filter_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class FilterImpl
corpus_type Init(absl::BitGenRef prng) {
if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
while (true) {
auto v = inner_.Init(prng);
auto v = inner_.GetRandomCorpusValue(prng);
if (RunFilter(v)) return v;
}
}
Expand Down