blob: 596a09cb98d073652023cbf6f7ccdd4afad7d6bf [file]
// 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_