| // Copyright 2023 The Pigweed Authors |
| // |
| // 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 |
| // |
| // https://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. |
| #pragma once |
| |
| /// @file |
| /// Stubs for the Pigweed-compatible subset of the FuzzTest interface |
| /// |
| /// @rst |
| /// This header provides stubs for the portion of the FuzzTest interface that |
| /// only depends on permitted C++ standard library `headers`_, including |
| /// `macros`_ and `domains`_. |
| /// |
| /// This header is included when FuzzTest is disabled, e.g. for GN, when |
| /// ``dir_pw_third_party_fuzztest`` or ``pw_toolchain_FUZZING_ENABLED`` are not |
| /// set. Otherwise, ``//pw_fuzzer/public/pw_fuzzer/internal/fuzztest.h`` is used |
| /// instead. |
| /// |
| /// .. _domains: |
| /// https://github.com/google/fuzztest/blob/main/doc/domains-reference.md |
| /// .. _headers: https://pigweed.dev/docs/style_guide.html#permitted-headers |
| /// .. _macros: |
| /// https://github.com/google/fuzztest/blob/main/doc/fuzz-test-macro.md |
| /// @endrst |
| |
| #include <array> |
| #include <cmath> |
| #include <initializer_list> |
| #include <limits> |
| #include <optional> |
| #include <string_view> |
| #include <tuple> |
| #include <type_traits> |
| #include <utility> |
| #include <variant> |
| |
| #define FUZZ_TEST(test_suite_name, test_name) \ |
| TEST(test_suite_name, DISABLED_##test_name) {} \ |
| auto _pw_fuzzer_##test_suite_name##_##test_name##_FUZZTEST_NOT_PRESENT = \ |
| fuzztest::internal::TypeCheckFuzzTest(test_name).IgnoreFunction() |
| |
| #define FUZZ_TEST_F(test_fixture, test_name) \ |
| TEST_F(test_fixture, DISABLED_##test_name) {} \ |
| auto _pw_fuzzer_##test_fixture##_##test_name##_FUZZTEST_NOT_PRESENT = \ |
| fuzztest::internal::TypeCheckFuzzTest(test_name).IgnoreFunction() |
| |
| namespace fuzztest { |
| namespace internal { |
| |
| /// Stub for a FuzzTest domain that produces values. |
| /// |
| /// In FuzzTest, domains are used to provide values of specific types when |
| /// fuzzing. However, FuzzTest is only optionally supported on host with Clang. |
| /// For other build configurations, this struct provides a FuzzTest-compatible |
| /// stub that can be used to perform limited type-checking at build time. |
| /// |
| /// Fuzzer authors must not invoke this type directly. Instead, use the factory |
| /// methods for domains such as `Arbitrary`, `VectorOf`, `Map`, etc. |
| template <typename T> |
| struct Domain { |
| using value_type = T; |
| }; |
| |
| /// Stub for a FuzzTest domain that produces containers of values. |
| /// |
| /// This struct is an extension of `Domain` that add stubs for the methods that |
| /// control container size. |
| template <typename T> |
| struct ContainerDomain : public Domain<T> { |
| template <typename U, typename = std::enable_if_t<std::is_integral_v<U>>> |
| ContainerDomain<T>& WithSize(U) { |
| return *this; |
| } |
| |
| template <typename U, typename = std::enable_if_t<std::is_integral_v<U>>> |
| ContainerDomain<T>& WithMinSize(U) { |
| return *this; |
| } |
| |
| template <typename U, typename = std::enable_if_t<std::is_integral_v<U>>> |
| ContainerDomain<T>& WithMaxSize(U) { |
| return *this; |
| } |
| }; |
| |
| /// Stub for a FuzzTest domain that produces optional values. |
| /// |
| /// This struct is an extension of `Domain` that add stubs for the methods that |
| /// control nullability. |
| template <typename T> |
| struct OptionalDomain : public Domain<T> { |
| OptionalDomain<T>& SetAlwaysNull() { return *this; } |
| OptionalDomain<T>& SetWithoutNull() { return *this; } |
| }; |
| |
| /// Register a FuzzTest stub. |
| /// |
| /// FuzzTest is only optionally supported on host with Clang. For other build |
| /// configurations, this struct provides a FuzzTest-compatible stub of a test |
| /// registration that only performs limited type-checking at build time. |
| /// |
| /// Fuzzer authors must not invoke this type directly. Instead, use the |
| /// `FUZZ_TEST` and/or `FUZZ_TEST_F` macros. |
| template <typename TargetFunction> |
| struct TypeCheckFuzzTest { |
| TypeCheckFuzzTest(TargetFunction) {} |
| |
| TypeCheckFuzzTest<TargetFunction>& IgnoreFunction() { return *this; } |
| |
| template <int&... ExplicitArgumentBarrier, |
| typename... Domains, |
| typename T = std::decay_t< |
| std::invoke_result_t<TargetFunction, |
| const typename Domains::value_type&...>>> |
| TypeCheckFuzzTest<TargetFunction>& WithDomains(Domains...) { |
| return *this; |
| } |
| |
| template <int&... ExplicitArgumentBarrier, |
| typename... Seeds, |
| typename T = std::decay_t< |
| std::invoke_result_t<TargetFunction, const Seeds&...>>> |
| TypeCheckFuzzTest<TargetFunction>& WithSeeds(Seeds...) { |
| return *this; |
| } |
| }; |
| |
| } // namespace internal |
| |
| // The remaining functions match those defined by fuzztest/fuzztest.h. |
| // |
| // This namespace is here only as a way to disable ADL (argument-dependent |
| // lookup). Names should be used from the fuzztest:: namespace. |
| namespace internal_no_adl { |
| |
| //////////////////////////////////////////////////////////////// |
| // Arbitrary domains |
| // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#arbitrary-domains |
| |
| template <typename T> |
| auto Arbitrary() { |
| return internal::Domain<T>{}; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| // Other miscellaneous domains |
| // These typically appear later in docs and tests. They are placed early in this |
| // file to allow other domains to be defined using them. |
| |
| // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#oneof |
| template <int&... ExplicitArgumentBarrier, typename T, typename... Domains> |
| auto OneOf(internal::Domain<T>, Domains...) { |
| return internal::Domain<T>{}; |
| } |
| |
| // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#oneof |
| template <typename T> |
| auto Just(T) { |
| return internal::Domain<T>{}; |
| } |
| |
| // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#map |
| template <int&... ExplicitArgumentBarrier, typename Mapper, typename... Inner> |
| auto Map(Mapper, Inner...) { |
| return internal::Domain<std::decay_t< |
| std::invoke_result_t<Mapper, typename Inner::value_type&...>>>{}; |
| } |
| |
| // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#flatmap |
| template <typename FlatMapper, typename... Inner> |
| using FlatMapOutputDomain = std::decay_t< |
| std::invoke_result_t<FlatMapper, typename Inner::value_type&...>>; |
| template <int&... ExplicitArgumentBarrier, |
| typename FlatMapper, |
| typename... Inner> |
| auto FlatMap(FlatMapper, Inner...) { |
| return internal::Domain< |
| typename FlatMapOutputDomain<FlatMapper, Inner...>::value_type>{}; |
| } |
| |
| // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#filter |
| template <int&... ExplicitArgumentBarrier, typename T, typename Pred> |
| auto Filter(Pred, internal::Domain<T>) { |
| return internal::Domain<T>{}; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| // Numerical domains |
| // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#numerical-domains |
| |
| template <typename T> |
| auto InRange(T min, T max) { |
| return Filter([min, max](T t) { return min <= t && t <= max; }, |
| Arbitrary<T>()); |
| } |
| |
| template <typename T> |
| auto NonZero() { |
| return Filter([](T t) { return t != 0; }, Arbitrary<T>()); |
| } |
| |
| template <typename T> |
| auto Positive() { |
| if constexpr (std::is_floating_point_v<T>) { |
| return InRange<T>(std::numeric_limits<T>::denorm_min(), |
| std::numeric_limits<T>::max()); |
| } else { |
| return InRange<T>(T{1}, std::numeric_limits<T>::max()); |
| } |
| } |
| |
| template <typename T> |
| auto NonNegative() { |
| return InRange<T>(T{}, std::numeric_limits<T>::max()); |
| } |
| |
| template <typename T> |
| auto Negative() { |
| static_assert(!std::is_unsigned_v<T>, |
| "Negative<T>() can only be used with with signed T-s! " |
| "For char, consider using signed char."); |
| if constexpr (std::is_floating_point_v<T>) { |
| return InRange<T>(std::numeric_limits<T>::lowest(), |
| -std::numeric_limits<T>::denorm_min()); |
| } else { |
| return InRange<T>(std::numeric_limits<T>::min(), T{-1}); |
| } |
| } |
| |
| template <typename T> |
| auto NonPositive() { |
| static_assert(!std::is_unsigned_v<T>, |
| "NonPositive<T>() can only be used with with signed T-s! " |
| "For char, consider using signed char."); |
| return InRange<T>(std::numeric_limits<T>::lowest(), T{}); |
| } |
| |
| template <typename T> |
| auto Finite() { |
| static_assert(std::is_floating_point_v<T>, |
| "Finite<T>() can only be used with floating point types!"); |
| return Filter([](T f) { return std::isfinite(f); }, Arbitrary<T>()); |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| // Character domains |
| // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#character-domains |
| |
| inline auto NonZeroChar() { return Positive<char>(); } |
| inline auto NumericChar() { return InRange<char>('0', '9'); } |
| inline auto LowerChar() { return InRange<char>('a', 'z'); } |
| inline auto UpperChar() { return InRange<char>('A', 'Z'); } |
| inline auto AlphaChar() { return OneOf(LowerChar(), UpperChar()); } |
| inline auto AlphaNumericChar() { return OneOf(AlphaChar(), NumericChar()); } |
| inline auto AsciiChar() { return InRange<char>(0, 127); } |
| inline auto PrintableAsciiChar() { return InRange<char>(32, 126); } |
| |
| //////////////////////////////////////////////////////////////// |
| // Regular expression domains |
| |
| // TODO: b/285775246 - Add support for `fuzztest::InRegexp`. |
| // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#inregexp-domains |
| // inline auto InRegexp(std::string_view) { |
| // return internal::Domain<std::string_view>{}; |
| // } |
| |
| //////////////////////////////////////////////////////////////// |
| // Enumerated domains |
| // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#elementof-domains |
| |
| template <typename T> |
| auto ElementOf(std::initializer_list<T>) { |
| return internal::Domain<T>{}; |
| } |
| |
| template <typename T> |
| auto BitFlagCombinationOf(std::initializer_list<T>) { |
| return internal::Domain<T>{}; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| // Container domains |
| // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#container-combinators |
| |
| template <typename T, int&... ExplicitArgumentBarrier, typename U> |
| auto ContainerOf(internal::Domain<U>) { |
| return internal::ContainerDomain<T>{}; |
| } |
| |
| template <template <typename, typename...> class T, |
| int&... ExplicitArgumentBarrier, |
| typename U> |
| auto ContainerOf(internal::Domain<U>) { |
| return internal::ContainerDomain<T<U>>{}; |
| } |
| |
| template <typename T, int&... ExplicitArgumentBarrier, typename U> |
| auto UniqueElementsContainerOf(internal::Domain<U>) { |
| return internal::ContainerDomain<T>{}; |
| } |
| |
| template <int&... ExplicitArgumentBarrier, typename T> |
| auto NonEmpty(internal::ContainerDomain<T> inner) { |
| return inner.WithMinSize(1); |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| // Aggregate domains |
| // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#container-combinators |
| |
| template <int&... ExplicitArgumentBarrier, typename T, typename... Domains> |
| auto ArrayOf(internal::Domain<T>, Domains... others) { |
| return internal::Domain<std::array<T, 1 + sizeof...(others)>>{}; |
| } |
| |
| template <int N, int&... ExplicitArgumentBarrier, typename T> |
| auto ArrayOf(const internal::Domain<T>&) { |
| return internal::Domain<std::array<T, N>>{}; |
| } |
| |
| template <typename T, int&... ExplicitArgumentBarrier, typename... Inner> |
| auto StructOf(Inner...) { |
| return internal::Domain<T>{}; |
| } |
| |
| template <typename T, int&... ExplicitArgumentBarrier> |
| auto ConstructorOf() { |
| return internal::Domain<T>{}; |
| } |
| |
| template <typename T, |
| int&... ExplicitArgumentBarrier, |
| typename U, |
| typename... Inner> |
| auto ConstructorOf(internal::Domain<U>, Inner... inner) { |
| return ConstructorOf<T>(inner...); |
| } |
| |
| template <int&... ExplicitArgumentBarrier, typename T1, typename T2> |
| auto PairOf(internal::Domain<T1>, internal::Domain<T2>) { |
| return internal::Domain<std::pair<T1, T2>>{}; |
| } |
| |
| template <int&... ExplicitArgumentBarrier, typename... Inner> |
| auto TupleOf(Inner...) { |
| return internal::Domain<std::tuple<typename Inner::value_type...>>{}; |
| } |
| |
| template <typename T, int&... ExplicitArgumentBarrier, typename... Inner> |
| auto VariantOf(Inner...) { |
| return internal::Domain<T>{}; |
| } |
| |
| template <int&... ExplicitArgumentBarrier, typename... Inner> |
| auto VariantOf(Inner...) { |
| return internal::Domain<std::variant<typename Inner::value_type...>>{}; |
| } |
| |
| template <template <typename> class Optional, |
| int&... ExplicitArgumentBarrier, |
| typename T> |
| auto OptionalOf(internal::Domain<T>) { |
| return internal::OptionalDomain<Optional<T>>{}; |
| } |
| |
| template <int&... ExplicitArgumentBarrier, typename T> |
| auto OptionalOf(internal::Domain<T> inner) { |
| return OptionalOf<std::optional>(inner); |
| } |
| |
| template <typename T> |
| auto NullOpt() { |
| return internal::OptionalDomain<std::optional<T>>{}.SetAlwaysNull(); |
| } |
| |
| template <int&... ExplicitArgumentBarrier, typename T> |
| auto NonNull(internal::OptionalDomain<T> inner) { |
| return inner.SetWithoutNull(); |
| } |
| |
| } // namespace internal_no_adl |
| |
| // Inject the names from internal_no_adl into fuzztest, without allowing for |
| // ADL. Note that an `inline` namespace would not have this effect (ie it would |
| // still allow ADL to trigger). |
| using namespace internal_no_adl; // NOLINT |
| |
| } // namespace fuzztest |