| // 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_REGISTRATION_H_ |
| #define FUZZTEST_FUZZTEST_INTERNAL_REGISTRATION_H_ |
| |
| #include <cstdio> |
| #include <cstdlib> |
| #include <functional> |
| #include <iostream> |
| #include <tuple> |
| #include <type_traits> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/functional/any_invocable.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/types/span.h" |
| #include "./fuzztest/domain.h" |
| #include "./fuzztest/internal/domains/aggregate_of_impl.h" |
| #include "./fuzztest/internal/domains/domain_base.h" |
| #include "./fuzztest/internal/meta.h" |
| #include "./fuzztest/internal/type_support.h" |
| |
| namespace fuzztest::internal { |
| |
| struct BasicTestInfo { |
| const char* suite_name = nullptr; |
| const char* test_name = nullptr; |
| const char* file = nullptr; |
| int line = 0; |
| bool uses_fixture = false; |
| }; |
| |
| // Use base classes to progressively add members/behavior to the registerer |
| // object. This way we can statically assert that certain functions are called |
| // in the right order. |
| |
| struct NoFixture {}; |
| |
| // Initial base class. No custom domain, no seeds. |
| template <typename... Args> |
| struct DefaultRegistrationBase { |
| static constexpr bool kHasDomain = false; |
| static constexpr bool kHasSeeds = false; |
| static constexpr bool kHasSeedProvider = false; |
| static constexpr size_t kNumArgs = sizeof...(Args); |
| |
| static_assert((std::is_same_v<Args, std::decay_t<Args>> && ...)); |
| |
| Domain<std::tuple<Args...>> GetDomains() const { |
| return TupleOf(Arbitrary<Args>()...); |
| } |
| |
| using SeedT = std::tuple<Args...>; |
| }; |
| |
| template <typename Fixture, typename BaseFixture, typename... Args> |
| DefaultRegistrationBase<std::decay_t<Args>...> DefaultRegistrationBaseImpl( |
| Fixture*, void (BaseFixture::*)(Args...)); |
| |
| template <typename... Args> |
| DefaultRegistrationBase<std::decay_t<Args>...> DefaultRegistrationBaseImpl( |
| NoFixture*, void (*)(Args...)); |
| |
| template <typename Fixture, typename TargetFunction> |
| using DefaultRegistrationBaseT = decltype(DefaultRegistrationBaseImpl( |
| static_cast<Fixture*>(nullptr), static_cast<TargetFunction>(nullptr))); |
| |
| // A custom domain was specified. |
| template <typename... Args> |
| struct RegistrationWithDomainsBase { |
| static constexpr bool kHasDomain = true; |
| static constexpr bool kHasSeeds = false; |
| static constexpr bool kHasSeedProvider = false; |
| static constexpr size_t kNumArgs = sizeof...(Args); |
| |
| Domain<std::tuple<Args...>> domains_; |
| |
| const auto& GetDomains() const { return domains_; } |
| |
| using SeedT = decltype(domains_.GetValue({})); |
| }; |
| |
| // Seeds were specified. It derived from the existing base to augment it. |
| template <typename Base> |
| struct RegistrationWithSeedsBase : Base { |
| static constexpr bool kHasSeeds = true; |
| |
| explicit RegistrationWithSeedsBase(Base base) : Base(std::move(base)) {} |
| |
| std::vector< |
| corpus_type_t<std::decay_t<decltype(std::declval<Base>().GetDomains())>>> |
| seeds_; |
| }; |
| |
| template <typename Base, typename SeedProvider> |
| struct RegistrationWithSeedProviderBase : Base { |
| static constexpr bool kHasSeedProvider = true; |
| |
| explicit RegistrationWithSeedProviderBase(Base base, |
| SeedProvider seed_provider) |
| : Base(std::move(base)), seed_provider_(std::move(seed_provider)) {} |
| |
| SeedProvider seed_provider_; |
| }; |
| |
| class PerIterationFixture; |
| struct RegistrationToken; |
| |
| template <typename Fixture, typename TargetFunction, |
| typename Base = DefaultRegistrationBaseT<Fixture, TargetFunction>, |
| typename SeedProvider = void*> |
| class Registration : private Base { |
| using SeedT = typename Base::SeedT; |
| |
| public: |
| explicit Registration(BasicTestInfo info, TargetFunction target_function) |
| : test_info_(info), target_function_(target_function) {} |
| |
| // Registers domains for a property function as a `TupleOf` individual |
| // domains. This is useful when the domains are specified indirectly, e.g., |
| // when they are returned from a helper function. For example: |
| // |
| // auto StringAndIndex(int size) { |
| // return TupleOf(String().WithSize(size), InRange(0, size - 1)); |
| // } |
| // |
| // void MyProperty(std::string s, int i) { ... } |
| // FUZZ_TEST(MySuite, MyProperty).WithDomains(StringAndIndex(10)); |
| template <typename... NewDomains> |
| auto WithDomains(AggregateOfImpl<std::tuple<value_type_t<NewDomains>...>, |
| RequireCustomCorpusType::kNo, NewDomains...> |
| domain) && { |
| static_assert(!Registration::kHasDomain, |
| "WithDomains can only be called once."); |
| static_assert(!Registration::kHasSeeds, |
| "WithDomains can not be called after WithSeeds."); |
| static_assert(!Registration::kHasSeedProvider, |
| "WithDomains can not be called after WithSeedProvider."); |
| static_assert( |
| Base::kNumArgs == sizeof...(NewDomains), |
| "Number of domains specified in .WithDomains() does not match " |
| "the number of function parameters."); |
| using NewBase = RegistrationWithDomainsBase<value_type_t<NewDomains>...>; |
| return Registration<Fixture, TargetFunction, NewBase, SeedProvider>( |
| test_info_, target_function_, NewBase{std::move(domain)}); |
| } |
| |
| // Registers a domain for each parameter of the property function. This is the |
| // recommended approach when domains are explicitly listed as part of the fuzz |
| // test definition. For example: |
| // |
| // void MyProperty(std::string s, int n) { ... } |
| // FUZZ_TEST(MySuite, MyProperty).WithDomains(String(), Positive<int>()); |
| template <typename... NewDomains> |
| auto WithDomains(NewDomains&&... domains) && { |
| return std::move(*this).WithDomains( |
| TupleOf(std::forward<NewDomains>(domains)...)); |
| } |
| |
| auto WithSeeds(absl::Span<const SeedT> seeds) && { |
| if constexpr (!Registration::kHasSeeds) { |
| return Registration<Fixture, TargetFunction, |
| RegistrationWithSeedsBase<Base>, SeedProvider>( |
| test_info_, target_function_, |
| RegistrationWithSeedsBase<Base>(std::move(*this))) |
| .WithSeeds(seeds); |
| } else { |
| const auto& domains = this->GetDomains(); |
| bool found_error = false; |
| for (const auto& seed : seeds) { |
| if (auto from_value = domains.FromValue(seed)) { |
| this->seeds_.push_back(*std::move(from_value)); |
| } else { |
| // TODO(sbenzaquen): Should we abort the process or make the test fail |
| // when seeds are ignored? |
| absl::FPrintF( |
| stderr, |
| "[!] Error using `WithSeeds()` in %s.%s:\n\n%s:%d: Invalid seed " |
| "value:\n\n{", |
| test_info_.suite_name, test_info_.test_name, test_info_.file, |
| test_info_.line); |
| |
| // We use a direct call to PrintUserValue because we don't have a |
| // corpus_type object to pass to PrintValue. |
| bool first = true; |
| const auto print_one_arg = [&](auto I) { |
| if (!first) absl::FPrintF(stderr, ", "); |
| using value_type = std::decay_t<std::tuple_element_t<I, SeedT>>; |
| AutodetectTypePrinter<value_type>().PrintUserValue( |
| std::get<I>(seed), &std::cerr, PrintMode::kHumanReadable); |
| first = false; |
| }; |
| ApplyIndex<Base::kNumArgs>( |
| [&](auto... I) { (print_one_arg(I), ...); }); |
| |
| absl::FPrintF(stderr, "}\n"); |
| found_error = true; |
| } |
| } |
| if (found_error) exit(1); |
| return std::move(*this); |
| } |
| } |
| |
| // Registers a (free) function that returns a vector of seeds. This can be |
| // used when the seeds need to be initialized dynamically. For example, if the |
| // seeds depend on any global variables, this is a way to resolve the static |
| // initialization order fiasco. |
| auto WithSeeds( |
| absl::AnyInvocable<std::vector<SeedT>() const> seed_provider) && { |
| static_assert( |
| !Registration::kHasSeedProvider, |
| "WithSeeds that registers a seed provider can only be called once."); |
| return Registration< |
| Fixture, TargetFunction, |
| RegistrationWithSeedProviderBase<Base, decltype(seed_provider)>, |
| decltype(seed_provider)>( |
| test_info_, target_function_, |
| RegistrationWithSeedProviderBase<Base, decltype(seed_provider)>( |
| std::move(*this), std::move(seed_provider))); |
| } |
| |
| // Registers a member function of `Fixture` (or any of its base classes |
| // `BaseFixture`) that returns a vector of seeds. This is useful if the seeds |
| // not only need to be initialized dynamically (e.g., to avoid the static |
| // initialization order fiasco), but they also depend on the fixture's |
| // internal state. |
| // |
| // NOTE: `Fixture` cannot be a fixture that uses the "per-iteration" semantics |
| // (see GoogleTest fixture adapters for details). |
| template <typename BaseFixture> |
| auto WithSeeds(std::vector<SeedT> (BaseFixture::*seed_provider)()) && { |
| static_assert(std::is_base_of_v<BaseFixture, Fixture>, |
| "Seed provider that is a member function must be a member of " |
| "the fixture."); |
| static_assert(!std::is_base_of_v<PerIterationFixture, Fixture>, |
| "Seed provider cannot be a member of a fixture that uses " |
| "\"per-iteration\" semantics."); |
| static_assert( |
| !Registration::kHasSeedProvider, |
| "WithSeeds that registers a seed provider can only be called once."); |
| using NewSeedProvider = |
| absl::AnyInvocable<std::vector<SeedT>(BaseFixture*) const>; |
| using NewBase = RegistrationWithSeedProviderBase<Base, NewSeedProvider>; |
| return Registration<Fixture, TargetFunction, NewBase, NewSeedProvider>( |
| test_info_, target_function_, |
| NewBase(std::move(*this), std::mem_fn(seed_provider))); |
| } |
| |
| private: |
| std::vector<GenericDomainCorpusType> seeds() const { |
| if constexpr (Base::kHasSeeds) { |
| return this->seeds_; |
| } else { |
| return {}; |
| } |
| } |
| |
| SeedProvider seed_provider() { |
| if constexpr (Base::kHasSeedProvider) { |
| return std::move(this->seed_provider_); |
| } else { |
| return {}; |
| } |
| } |
| |
| template <typename, typename, typename, typename> |
| friend class Registration; |
| friend struct RegistrationToken; |
| |
| explicit Registration(BasicTestInfo info, TargetFunction target_function, |
| Base base) |
| : Base(std::move(base)), |
| test_info_(info), |
| target_function_(target_function) {} |
| |
| BasicTestInfo test_info_; |
| TargetFunction target_function_; |
| }; |
| |
| } // namespace fuzztest::internal |
| |
| #endif // FUZZTEST_FUZZTEST_INTERNAL_REGISTRATION_H_ |