Pull out the Domain<T> class into a separate header. This is so that the domain implementations can include it without including the user-facing domain combinators. PiperOrigin-RevId: 604383395
diff --git a/fuzztest/BUILD b/fuzztest/BUILD index df3ddfd..e21c923 100644 --- a/fuzztest/BUILD +++ b/fuzztest/BUILD
@@ -295,6 +295,7 @@ "internal/domains/container_mutation_helpers.h", "internal/domains/container_of_impl.h", "internal/domains/domain_base.h", + "internal/domains/domain_concept.h", "internal/domains/element_of_impl.h", "internal/domains/filter_impl.h", "internal/domains/flat_map_impl.h",
diff --git a/fuzztest/CMakeLists.txt b/fuzztest/CMakeLists.txt index c84a26e..fc2c3f2 100644 --- a/fuzztest/CMakeLists.txt +++ b/fuzztest/CMakeLists.txt
@@ -241,6 +241,7 @@ "internal/domains/container_mutation_helpers.h" "internal/domains/container_of_impl.h" "internal/domains/domain_base.h" + "internal/domains/domain_concept.h" "internal/domains/element_of_impl.h" "internal/domains/filter_impl.h" "internal/domains/flat_map_impl.h"
diff --git a/fuzztest/domain_core.h b/fuzztest/domain_core.h index b6cc860..e79f3af 100644 --- a/fuzztest/domain_core.h +++ b/fuzztest/domain_core.h
@@ -49,6 +49,7 @@ #include "./fuzztest/internal/domains/bit_flag_combination_of_impl.h" #include "./fuzztest/internal/domains/container_of_impl.h" #include "./fuzztest/internal/domains/domain_base.h" +#include "./fuzztest/internal/domains/domain_concept.h" // IWYU pragma: export #include "./fuzztest/internal/domains/element_of_impl.h" #include "./fuzztest/internal/domains/filter_impl.h" #include "./fuzztest/internal/domains/flat_map_impl.h" @@ -66,153 +67,6 @@ namespace fuzztest { -// Type erased version of the domain concept. -// It can be constructed from any object that follows the domain concept for the -// right `value_type`. This class implements the domain concept too. -// TODO(sbenzaquen): Document the domain concept when it is stable enough. - -template <typename T> -class Domain { - public: - using value_type = T; - using corpus_type = internal::GenericDomainCorpusType; - static constexpr bool has_custom_corpus_type = true; - - // Intentionally not marked as explicit to allow implicit conversion from the - // inner domain implementations. - template <typename Inner, typename CorpusType> - Domain(const internal::DomainBase<Inner, T, CorpusType>& inner) - : inner_(new auto(static_cast<const Inner&>(inner))) {} - - Domain(const Domain& other) { *this = other; } - Domain& operator=(const Domain& other) { - inner_.reset(static_cast<internal::TypedDomainInterface<T>*>( - other.inner_->Clone().release())); - return *this; - } - // No default constructor or move operations to avoid a null state. - - // GetRandomValue() returns a random user value from the domain. This is - // useful e.g., for generation-based black-box fuzzing, when coverage-guided - // fuzzing is not possible, or for other use cases when manually sampling the - // domain makes sense (e.g., getting random values for benchmarking). These - // are the only uses cases when the users should use domains directly, and - // this is the only method that the users should call. - // - // In general, GetRandomValue() doesn't provide any guarantees on the - // distribution of the returned values. - // - // Note about stability: GetRandomValue() doesn't guarantee stability of the - // generated values even if `prng` is seeded with a fixed seed. With a seeded - // `prng`, it is possible to reproduce the sequence of generated values by - // setting the environment variable FUZZTEST_PRNG_SEED to the value output to - // stderr on the first invocation. However, this is only guaranteed to work - // with the same version of the binary. - // - value_type GetRandomValue(absl::BitGenRef prng) { - return inner_->TypedGetRandomValue(prng); - } - - // The methods below are used by the FuzzTest framework and custom domain - // implementations. - - // Init() generates a random value of corpus_type. - // - // The generated value can often be a "special value" (e.g., 0, MAX_INT, NaN, - // infinity, empty vector, etc.). For basic, fixed sized data types (e.g., - // optional<int>) Init() might give any value. For variable-sized data types - // (e.g., containers, linked lists, trees, etc) Init() typically returns a - // smaller sized value. Larger sized values however can be created through - // Mutate() calls. - // - // ENSURES: That Init() is non-deterministic, i.e., it doesn't always return - // the same value. This is because Mutate() often relies on Init() giving - // different values (e.g., when growing a std::set<T> and adding new T - // values). - corpus_type Init(absl::BitGenRef prng) { return inner_->UntypedInit(prng); } - - // Mutate() makes a relatively small modification on `val` of corpus_type. - // - // When `only_shrink` is enabled, the mutated value is always "simpler" (e.g., - // smaller). - // - // ENSURES: That the mutated value is not the same as the original. - void Mutate(corpus_type& val, absl::BitGenRef prng, bool only_shrink) { - return inner_->UntypedMutate(val, prng, only_shrink); - } - - // Try to update the dynamic memory dictionary. - // If it propagates to a domain that's compatible with dynamic - // dictionary, it will try to match and save dictionary entries from - // dynamic data collected by SanCov. - void UpdateMemoryDictionary(const corpus_type& val) { - return inner_->UntypedUpdateMemoryDictionary(val); - } - - auto GetPrinter() const { return Printer{*inner_}; } - - value_type GetValue(const corpus_type& v) const { - return inner_->TypedGetValue(v); - } - - std::optional<corpus_type> FromValue(const value_type& v) const { - return inner_->TypedFromValue(v); - } - - // Parses corpus value _without validating it_. Validation must be done with - // ValidateCorpusValue(). - // - // TODO(lszekeres): Return StatusOr<corpus_type>. - std::optional<corpus_type> ParseCorpus(const internal::IRObject& obj) const { - return inner_->UntypedParseCorpus(obj); - } - - internal::IRObject SerializeCorpus(const corpus_type& v) const { - return inner_->UntypedSerializeCorpus(v); - } - - // After creating a corpus value, either via ParseCorpus() or via FromValue() - // this method is used to determine if the corpus value is valid. - absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const { - return inner_->UntypedValidateCorpusValue(corpus_value); - } - - // TODO(JunyangShao): Get rid of this API so it won't be exposed - // to outside. - // Return the field counts of `val` if `val` is - // a `ProtobufDomainImpl::corpus_type`. Otherwise propagate it - // to inner domains and returns the sum of inner results. - uint64_t CountNumberOfFields(const corpus_type& val) { - return inner_->UntypedCountNumberOfFields(val); - } - - // Mutate the selected protobuf field using `selected_field_index`. - // Return value is the same as CountNumberOfFields. - uint64_t MutateSelectedField(corpus_type& val, absl::BitGenRef prng, - bool only_shrink, - uint64_t selected_field_index) { - return inner_->UntypedMutateSelectedField(val, prng, only_shrink, - selected_field_index); - } - - auto Clone() const { return inner_->Clone(); } - - private: - // Have a subinterface just for the type traits to not expose more than - // necessary through GetPrinter(). - friend class DomainBuilder; - - struct Printer { - const internal::UntypedDomainInterface& inner; - void PrintCorpusValue(const corpus_type& val, absl::FormatRawSink out, - internal::PrintMode mode) const { - inner.UntypedPrintCorpusValue(val, out, mode); - } - }; - - std::unique_ptr<internal::TypedDomainInterface<T>> inner_; -}; - class DomainBuilder { public: DomainBuilder()
diff --git a/fuzztest/internal/domains/domain_concept.h b/fuzztest/internal/domains/domain_concept.h new file mode 100644 index 0000000..672ef8c --- /dev/null +++ b/fuzztest/internal/domains/domain_concept.h
@@ -0,0 +1,180 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_DOMAIN_CONCEPT_H_ +#define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_DOMAIN_CONCEPT_H_ + +#include <cstdint> +#include <memory> +#include <optional> + +#include "absl/random/bit_gen_ref.h" +#include "absl/status/status.h" +#include "absl/strings/str_format.h" +#include "./fuzztest/internal/domains/domain_base.h" +#include "./fuzztest/internal/serialization.h" +#include "./fuzztest/internal/type_support.h" + +namespace fuzztest { + +// Type erased version of the domain concept. +// It can be constructed from any object that follows the domain concept for the +// right `value_type`. This class implements the domain concept too. +// TODO(sbenzaquen): Document the domain concept when it is stable enough. + +template <typename T> +class Domain { + public: + using value_type = T; + using corpus_type = internal::GenericDomainCorpusType; + static constexpr bool has_custom_corpus_type = true; + + // Intentionally not marked as explicit to allow implicit conversion from the + // inner domain implementations. + template <typename Inner, typename CorpusType> + Domain(const internal::DomainBase<Inner, T, CorpusType>& inner) + : inner_(new auto(static_cast<const Inner&>(inner))) {} + + Domain(const Domain& other) { *this = other; } + Domain& operator=(const Domain& other) { + inner_.reset(static_cast<internal::TypedDomainInterface<T>*>( + other.inner_->Clone().release())); + return *this; + } + // No default constructor or move operations to avoid a null state. + + // GetRandomValue() returns a random user value from the domain. This is + // useful e.g., for generation-based black-box fuzzing, when coverage-guided + // fuzzing is not possible, or for other use cases when manually sampling the + // domain makes sense (e.g., getting random values for benchmarking). These + // are the only uses cases when the users should use domains directly, and + // this is the only method that the users should call. + // + // In general, GetRandomValue() doesn't provide any guarantees on the + // distribution of the returned values. + // + // Note about stability: GetRandomValue() doesn't guarantee stability of the + // generated values even if `prng` is seeded with a fixed seed. With a seeded + // `prng`, it is possible to reproduce the sequence of generated values by + // setting the environment variable FUZZTEST_PRNG_SEED to the value output to + // stderr on the first invocation. However, this is only guaranteed to work + // with the same version of the binary. + // + value_type GetRandomValue(absl::BitGenRef prng) { + return inner_->TypedGetRandomValue(prng); + } + + // The methods below are used by the FuzzTest framework and custom domain + // implementations. + + // Init() generates a random value of corpus_type. + // + // The generated value can often be a "special value" (e.g., 0, MAX_INT, NaN, + // infinity, empty vector, etc.). For basic, fixed sized data types (e.g., + // optional<int>) Init() might give any value. For variable-sized data types + // (e.g., containers, linked lists, trees, etc) Init() typically returns a + // smaller sized value. Larger sized values however can be created through + // Mutate() calls. + // + // ENSURES: That Init() is non-deterministic, i.e., it doesn't always return + // the same value. This is because Mutate() often relies on Init() giving + // different values (e.g., when growing a std::set<T> and adding new T + // values). + corpus_type Init(absl::BitGenRef prng) { return inner_->UntypedInit(prng); } + + // Mutate() makes a relatively small modification on `val` of corpus_type. + // + // When `only_shrink` is enabled, the mutated value is always "simpler" (e.g., + // smaller). + // + // ENSURES: That the mutated value is not the same as the original. + void Mutate(corpus_type& val, absl::BitGenRef prng, bool only_shrink) { + return inner_->UntypedMutate(val, prng, only_shrink); + } + + // Try to update the dynamic memory dictionary. + // If it propagates to a domain that's compatible with dynamic + // dictionary, it will try to match and save dictionary entries from + // dynamic data collected by SanCov. + void UpdateMemoryDictionary(const corpus_type& val) { + return inner_->UntypedUpdateMemoryDictionary(val); + } + + auto GetPrinter() const { return Printer{*inner_}; } + + value_type GetValue(const corpus_type& v) const { + return inner_->TypedGetValue(v); + } + + std::optional<corpus_type> FromValue(const value_type& v) const { + return inner_->TypedFromValue(v); + } + + // Parses corpus value _without validating it_. Validation must be done with + // ValidateCorpusValue(). + // + // TODO(lszekeres): Return StatusOr<corpus_type>. + std::optional<corpus_type> ParseCorpus(const internal::IRObject& obj) const { + return inner_->UntypedParseCorpus(obj); + } + + internal::IRObject SerializeCorpus(const corpus_type& v) const { + return inner_->UntypedSerializeCorpus(v); + } + + // After creating a corpus value, either via ParseCorpus() or via FromValue() + // this method is used to determine if the corpus value is valid. + absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const { + return inner_->UntypedValidateCorpusValue(corpus_value); + } + + // TODO(JunyangShao): Get rid of this API so it won't be exposed + // to outside. + // Return the field counts of `val` if `val` is + // a `ProtobufDomainImpl::corpus_type`. Otherwise propagate it + // to inner domains and returns the sum of inner results. + uint64_t CountNumberOfFields(const corpus_type& val) { + return inner_->UntypedCountNumberOfFields(val); + } + + // Mutate the selected protobuf field using `selected_field_index`. + // Return value is the same as CountNumberOfFields. + uint64_t MutateSelectedField(corpus_type& val, absl::BitGenRef prng, + bool only_shrink, + uint64_t selected_field_index) { + return inner_->UntypedMutateSelectedField(val, prng, only_shrink, + selected_field_index); + } + + auto Clone() const { return inner_->Clone(); } + + private: + // Have a subinterface just for the type traits to not expose more than + // necessary through GetPrinter(). + friend class DomainBuilder; + + struct Printer { + const internal::UntypedDomainInterface& inner; + void PrintCorpusValue(const corpus_type& val, absl::FormatRawSink out, + internal::PrintMode mode) const { + inner.UntypedPrintCorpusValue(val, out, mode); + } + }; + + std::unique_ptr<internal::TypedDomainInterface<T>> inner_; +}; + +} // namespace fuzztest + +#endif // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_DOMAIN_CONCEPT_H_