| // Copyright 2022 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_ONE_OF_IMPL_H_ |
| #define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ONE_OF_IMPL_H_ |
| |
| #include <cstddef> |
| #include <optional> |
| #include <tuple> |
| #include <type_traits> |
| #include <variant> |
| |
| #include "absl/random/bit_gen_ref.h" |
| #include "absl/random/distributions.h" |
| #include "./fuzztest/internal/domains/domain_base.h" |
| #include "./fuzztest/internal/domains/serialization_helpers.h" |
| #include "./fuzztest/internal/meta.h" |
| #include "./fuzztest/internal/serialization.h" |
| #include "./fuzztest/internal/status.h" |
| #include "./fuzztest/internal/type_support.h" |
| |
| namespace fuzztest::internal { |
| |
| template <typename... Inner> |
| class OneOfImpl |
| : public DomainBase< |
| OneOfImpl<Inner...>, |
| value_type_t<std::tuple_element_t<0, std::tuple<Inner...>>>, |
| std::variant<corpus_type_t<Inner>...>> { |
| public: |
| using typename OneOfImpl::DomainBase::corpus_type; |
| using typename OneOfImpl::DomainBase::value_type; |
| |
| // All value_types of inner domains must be the same. (Though note that they |
| // can have different corpus_types!) |
| static_assert( |
| std::conjunction_v<std::is_same<value_type, value_type_t<Inner>>...>, |
| "All domains in a OneOf must have the same value_type."); |
| |
| explicit OneOfImpl(Inner... domains) : domains_(std::move(domains)...) {} |
| |
| corpus_type Init(absl::BitGenRef prng) { |
| if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed; |
| // TODO(b/191368509): Consider the cardinality of the subdomains to weight |
| // them. |
| return Switch<kNumDomains>( |
| absl::Uniform(prng, size_t{}, kNumDomains), [&](auto I) { |
| return corpus_type(std::in_place_index<I>, |
| std::get<I>(domains_).Init(prng)); |
| }); |
| } |
| |
| void Mutate(corpus_type& val, absl::BitGenRef prng, bool only_shrink) { |
| // Switch to another domain 1% of the time when not reducing. |
| if (kNumDomains > 1 && !only_shrink && absl::Bernoulli(prng, 0.01)) { |
| // Choose a different index. |
| size_t offset = absl::Uniform<size_t>(prng, 1, kNumDomains); |
| size_t index = static_cast<size_t>(val.index()); |
| index += offset; |
| if (index >= kNumDomains) index -= kNumDomains; |
| Switch<kNumDomains>(index, [&](auto I) { |
| auto& domain = std::get<I>(domains_); |
| val.template emplace<I>(domain.Init(prng)); |
| }); |
| } else { |
| Switch<kNumDomains>(val.index(), [&](auto I) { |
| auto& domain = std::get<I>(domains_); |
| domain.Mutate(std::get<I>(val), prng, only_shrink); |
| }); |
| } |
| } |
| |
| value_type GetValue(const corpus_type& v) const { |
| return Switch<kNumDomains>(v.index(), [&](auto I) -> value_type { |
| auto domain = std::get<I>(domains_); |
| return domain.GetValue(std::get<I>(v)); |
| }); |
| } |
| |
| std::optional<corpus_type> FromValue(const value_type& v) const { |
| std::optional<corpus_type> res; |
| const auto try_one_corpus = [&](auto I) { |
| auto corpus_value = std::get<I>(domains_).FromValue(v); |
| if (!corpus_value.has_value()) return false; |
| |
| const absl::Status valid = |
| std::get<I>(domains_).ValidateCorpusValue(*corpus_value); |
| if (!valid.ok()) return false; |
| |
| res.emplace(std::in_place_index<I>, *std::move(corpus_value)); |
| return true; |
| }; |
| |
| ApplyIndex<kNumDomains>([&](auto... I) { |
| // Try them in order, break on first success. |
| (try_one_corpus(I) || ...); |
| }); |
| |
| return res; |
| } |
| |
| auto GetPrinter() const { return OneOfPrinter<Inner...>{domains_}; } |
| |
| std::optional<corpus_type> ParseCorpus(const IRObject& obj) const { |
| return ParseWithDomainVariant(domains_, obj); |
| } |
| |
| IRObject SerializeCorpus(const corpus_type& v) const { |
| return SerializeWithDomainVariant(domains_, v); |
| } |
| |
| absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const { |
| return Switch<kNumDomains>(corpus_value.index(), [&](auto I) { |
| const absl::Status s = |
| std::get<I>(domains_).ValidateCorpusValue(std::get<I>(corpus_value)); |
| return Prefix(s, "Invalid value for OneOf() domain"); |
| }); |
| } |
| |
| private: |
| static constexpr size_t kNumDomains = sizeof...(Inner); |
| static_assert(kNumDomains > 0, "OneOf requires a non-empty list."); |
| |
| std::tuple<Inner...> domains_; |
| }; |
| |
| } // namespace fuzztest::internal |
| |
| #endif // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ONE_OF_IMPL_H_ |