blob: de3b7f382aae0d228100e210b49fba239fce3889 [file] [log] [blame]
// 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 {
/// 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;
};
namespace internal {
/// 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 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(Domain<T>, Domains...) {
return Domain<T>{};
}
// https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#oneof
template <typename T>
auto Just(T) {
return 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 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 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, Domain<T>) {
return 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 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 Domain<T>{};
}
template <typename T>
auto BitFlagCombinationOf(std::initializer_list<T>) {
return 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(Domain<U>) {
return internal::ContainerDomain<T>{};
}
template <template <typename, typename...> class T,
int&... ExplicitArgumentBarrier,
typename U>
auto ContainerOf(Domain<U>) {
return internal::ContainerDomain<T<U>>{};
}
template <typename T, int&... ExplicitArgumentBarrier, typename U>
auto UniqueElementsContainerOf(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(Domain<T>, Domains... others) {
return Domain<std::array<T, 1 + sizeof...(others)>>{};
}
template <int N, int&... ExplicitArgumentBarrier, typename T>
auto ArrayOf(const Domain<T>&) {
return Domain<std::array<T, N>>{};
}
template <typename T, int&... ExplicitArgumentBarrier, typename... Inner>
auto StructOf(Inner...) {
return Domain<T>{};
}
template <typename T, int&... ExplicitArgumentBarrier>
auto ConstructorOf() {
return Domain<T>{};
}
template <typename T,
int&... ExplicitArgumentBarrier,
typename U,
typename... Inner>
auto ConstructorOf(Domain<U>, Inner... inner) {
return ConstructorOf<T>(inner...);
}
template <int&... ExplicitArgumentBarrier, typename T1, typename T2>
auto PairOf(Domain<T1>, Domain<T2>) {
return Domain<std::pair<T1, T2>>{};
}
template <int&... ExplicitArgumentBarrier, typename... Inner>
auto TupleOf(Inner...) {
return Domain<std::tuple<typename Inner::value_type...>>{};
}
template <typename T, int&... ExplicitArgumentBarrier, typename... Inner>
auto VariantOf(Inner...) {
return Domain<T>{};
}
template <int&... ExplicitArgumentBarrier, typename... Inner>
auto VariantOf(Inner...) {
return Domain<std::variant<typename Inner::value_type...>>{};
}
template <template <typename> class Optional,
int&... ExplicitArgumentBarrier,
typename T>
auto OptionalOf(Domain<T>) {
return internal::OptionalDomain<Optional<T>>{};
}
template <int&... ExplicitArgumentBarrier, typename T>
auto OptionalOf(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