| // 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 <iostream> |
| #include <tuple> |
| #include <type_traits> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/strings/str_format.h" |
| #include "absl/types/span.h" |
| #include "./fuzztest/domain.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. |
| |
| template <typename Fixture, typename TargetFunction, typename = void> |
| struct DefaultRegistrationBase; |
| |
| // Initial base class. No custom domain, no seeds. |
| template <typename Fixture, typename BaseFixture, typename... Args> |
| struct DefaultRegistrationBase< |
| Fixture, void (BaseFixture::*)(Args...), |
| std::enable_if_t<std::is_base_of_v<BaseFixture, Fixture>>> { |
| static constexpr bool kHasDomain = false; |
| static constexpr bool kHasSeeds = false; |
| |
| auto GetDomains() const { |
| return TupleOf(Arbitrary<std::decay_t<Args>>()...); |
| } |
| |
| using SeedT = std::tuple<std::decay_t<Args>...>; |
| }; |
| |
| // A custom domain was specified. |
| template <typename Domain> |
| struct RegistrationWithDomainsBase { |
| static constexpr bool kHasDomain = true; |
| static constexpr bool kHasSeeds = false; |
| |
| Domain 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 Fixture, typename TargetFunction, |
| typename Base = DefaultRegistrationBase<Fixture, TargetFunction>, |
| typename = void> |
| class Registration; |
| |
| struct RegistrationToken; |
| |
| template <typename RegBase, typename Fixture, typename TargetFunction, typename> |
| class FixtureDriver; |
| |
| template <typename Fixture, typename BaseFixture, typename... Args, |
| typename Base> |
| class Registration<Fixture, void (BaseFixture::*)(Args...), Base, |
| std::enable_if_t<std::is_base_of_v<BaseFixture, Fixture>>> |
| : private Base { |
| using TargetFunction = void (BaseFixture::*)(Args...); |
| using SeedT = typename Base::SeedT; |
| |
| public: |
| explicit Registration(BasicTestInfo info, TargetFunction target_function) |
| : test_info_(info), target_function_(target_function) {} |
| |
| template <typename... NewDomains> |
| auto WithDomains(NewDomains&&... domains) && { |
| static_assert(!Registration::kHasDomain, |
| "WithDomains can only be called once."); |
| static_assert(!Registration::kHasSeeds, |
| "WithDomains can not be called after WithSeeds."); |
| static_assert( |
| sizeof...(Args) == sizeof...(NewDomains), |
| "Number of domains specified in .WithDomains() does not match " |
| "the number of function parameters."); |
| auto domain = TupleOf(std::forward<NewDomains>(domains)...); |
| return Registration<Fixture, TargetFunction, |
| RegistrationWithDomainsBase<decltype(domain)>>( |
| test_info_, target_function_, |
| RegistrationWithDomainsBase<decltype(domain)>{std::move(domain)}); |
| } |
| |
| auto WithSeeds(absl::Span<const SeedT> seeds) && { |
| if constexpr (!Registration::kHasSeeds) { |
| return Registration<Fixture, TargetFunction, |
| RegistrationWithSeedsBase<Base>>( |
| 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) { |
| using value_type = std::decay_t<std::tuple_element_t<I, SeedT>>; |
| AutodetectTypePrinter<value_type>().PrintUserValue( |
| std::get<I>(seed), &std::cerr, PrintMode::kHumanReadable); |
| if (!first) absl::FPrintF(stderr, ", "); |
| first = false; |
| }; |
| ApplyIndex<sizeof...(Args)>( |
| [&](auto... I) { (print_one_arg(I), ...); }); |
| |
| absl::FPrintF(stderr, "}\n"); |
| found_error = true; |
| } |
| } |
| if (found_error) exit(1); |
| return std::move(*this); |
| } |
| } |
| |
| private: |
| template <typename, typename, typename, typename> |
| friend class Registration; |
| friend struct RegistrationToken; |
| friend class FixtureDriver< |
| Base, Fixture, TargetFunction, |
| std::enable_if_t<std::is_base_of_v<BaseFixture, Fixture>>>; |
| |
| 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_ |