1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15
16 /// @file
17 /// Stubs for the Pigweed-compatible subset of the FuzzTest interface
18 ///
19 /// @rst
20 /// This header provides stubs for the portion of the FuzzTest interface that
21 /// only depends on permitted C++ standard library `headers`_, including
22 /// `macros`_ and `domains`_.
23 ///
24 /// This header is included when FuzzTest is disabled, e.g. for GN, when
25 /// ``dir_pw_third_party_fuzztest`` or ``pw_toolchain_FUZZING_ENABLED`` are not
26 /// set. Otherwise, ``//pw_fuzzer/public/pw_fuzzer/internal/fuzztest.h`` is used
27 /// instead.
28 ///
29 /// .. _domains:
30 /// https://github.com/google/fuzztest/blob/main/doc/domains-reference.md
31 /// .. _headers: https://pigweed.dev/docs/style_guide.html#permitted-headers
32 /// .. _macros:
33 /// https://github.com/google/fuzztest/blob/main/doc/fuzz-test-macro.md
34 /// @endrst
35
36 #include <array>
37 #include <cmath>
38 #include <initializer_list>
39 #include <limits>
40 #include <optional>
41 #include <string_view>
42 #include <tuple>
43 #include <type_traits>
44 #include <utility>
45 #include <variant>
46
47 #define FUZZ_TEST(test_suite_name, test_name) \
48 TEST(test_suite_name, DISABLED_##test_name) {} \
49 auto _pw_fuzzer_##test_suite_name##_##test_name##_FUZZTEST_NOT_PRESENT = \
50 fuzztest::internal::TypeCheckFuzzTest(test_name).IgnoreFunction()
51
52 #define FUZZ_TEST_F(test_fixture, test_name) \
53 TEST_F(test_fixture, DISABLED_##test_name) {} \
54 auto _pw_fuzzer_##test_fixture##_##test_name##_FUZZTEST_NOT_PRESENT = \
55 fuzztest::internal::TypeCheckFuzzTest(test_name).IgnoreFunction()
56
57 namespace fuzztest {
58
59 /// Stub for a FuzzTest domain that produces values.
60 ///
61 /// In FuzzTest, domains are used to provide values of specific types when
62 /// fuzzing. However, FuzzTest is only optionally supported on host with Clang.
63 /// For other build configurations, this struct provides a FuzzTest-compatible
64 /// stub that can be used to perform limited type-checking at build time.
65 ///
66 /// Fuzzer authors must not invoke this type directly. Instead, use the factory
67 /// methods for domains such as `Arbitrary`, `VectorOf`, `Map`, etc.
68 template <typename T>
69 struct Domain {
70 using value_type = T;
71 };
72
73 namespace internal {
74
75 /// Stub for a FuzzTest domain that produces containers of values.
76 ///
77 /// This struct is an extension of `Domain` that add stubs for the methods that
78 /// control container size.
79 template <typename T>
80 struct ContainerDomain : public Domain<T> {
81 template <typename U, typename = std::enable_if_t<std::is_integral_v<U>>>
WithSizeContainerDomain82 ContainerDomain<T>& WithSize(U) {
83 return *this;
84 }
85
86 template <typename U, typename = std::enable_if_t<std::is_integral_v<U>>>
WithMinSizeContainerDomain87 ContainerDomain<T>& WithMinSize(U) {
88 return *this;
89 }
90
91 template <typename U, typename = std::enable_if_t<std::is_integral_v<U>>>
WithMaxSizeContainerDomain92 ContainerDomain<T>& WithMaxSize(U) {
93 return *this;
94 }
95 };
96
97 /// Stub for a FuzzTest domain that produces optional values.
98 ///
99 /// This struct is an extension of `Domain` that add stubs for the methods that
100 /// control nullability.
101 template <typename T>
102 struct OptionalDomain : public Domain<T> {
SetAlwaysNullOptionalDomain103 OptionalDomain<T>& SetAlwaysNull() { return *this; }
SetWithoutNullOptionalDomain104 OptionalDomain<T>& SetWithoutNull() { return *this; }
105 };
106
107 /// Register a FuzzTest stub.
108 ///
109 /// FuzzTest is only optionally supported on host with Clang. For other build
110 /// configurations, this struct provides a FuzzTest-compatible stub of a test
111 /// registration that only performs limited type-checking at build time.
112 ///
113 /// Fuzzer authors must not invoke this type directly. Instead, use the
114 /// `FUZZ_TEST` and/or `FUZZ_TEST_F` macros.
115 template <typename TargetFunction>
116 struct TypeCheckFuzzTest {
TypeCheckFuzzTestTypeCheckFuzzTest117 TypeCheckFuzzTest(TargetFunction) {}
118
IgnoreFunctionTypeCheckFuzzTest119 TypeCheckFuzzTest<TargetFunction>& IgnoreFunction() { return *this; }
120
121 template <int&... ExplicitArgumentBarrier,
122 typename... Domains,
123 typename T = std::decay_t<
124 std::invoke_result_t<TargetFunction,
125 const typename Domains::value_type&...>>>
WithDomainsTypeCheckFuzzTest126 TypeCheckFuzzTest<TargetFunction>& WithDomains(Domains...) {
127 return *this;
128 }
129
130 template <int&... ExplicitArgumentBarrier,
131 typename... Seeds,
132 typename T = std::decay_t<
133 std::invoke_result_t<TargetFunction, const Seeds&...>>>
WithSeedsTypeCheckFuzzTest134 TypeCheckFuzzTest<TargetFunction>& WithSeeds(Seeds...) {
135 return *this;
136 }
137 };
138
139 } // namespace internal
140
141 // The remaining functions match those defined by fuzztest/fuzztest.h.
142 //
143 // This namespace is here only as a way to disable ADL (argument-dependent
144 // lookup). Names should be used from the fuzztest:: namespace.
145 namespace internal_no_adl {
146
147 ////////////////////////////////////////////////////////////////
148 // Arbitrary domains
149 // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#arbitrary-domains
150
151 template <typename T>
Arbitrary()152 auto Arbitrary() {
153 return Domain<T>{};
154 }
155
156 ////////////////////////////////////////////////////////////////
157 // Other miscellaneous domains
158 // These typically appear later in docs and tests. They are placed early in this
159 // file to allow other domains to be defined using them.
160
161 // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#oneof
162 template <int&... ExplicitArgumentBarrier, typename T, typename... Domains>
OneOf(Domain<T>,Domains...)163 auto OneOf(Domain<T>, Domains...) {
164 return Domain<T>{};
165 }
166
167 // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#oneof
168 template <typename T>
Just(T)169 auto Just(T) {
170 return Domain<T>{};
171 }
172
173 // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#map
174 template <int&... ExplicitArgumentBarrier, typename Mapper, typename... Inner>
Map(Mapper,Inner...)175 auto Map(Mapper, Inner...) {
176 return Domain<std::decay_t<
177 std::invoke_result_t<Mapper, typename Inner::value_type&...>>>{};
178 }
179
180 // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#flatmap
181 template <typename FlatMapper, typename... Inner>
182 using FlatMapOutputDomain = std::decay_t<
183 std::invoke_result_t<FlatMapper, typename Inner::value_type&...>>;
184 template <int&... ExplicitArgumentBarrier,
185 typename FlatMapper,
186 typename... Inner>
FlatMap(FlatMapper,Inner...)187 auto FlatMap(FlatMapper, Inner...) {
188 return Domain<
189 typename FlatMapOutputDomain<FlatMapper, Inner...>::value_type>{};
190 }
191
192 // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#filter
193 template <int&... ExplicitArgumentBarrier, typename T, typename Pred>
Filter(Pred,Domain<T>)194 auto Filter(Pred, Domain<T>) {
195 return Domain<T>{};
196 }
197
198 ////////////////////////////////////////////////////////////////
199 // Numerical domains
200 // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#numerical-domains
201
202 template <typename T>
InRange(T min,T max)203 auto InRange(T min, T max) {
204 return Filter([min, max](T t) { return min <= t && t <= max; },
205 Arbitrary<T>());
206 }
207
208 template <typename T>
NonZero()209 auto NonZero() {
210 return Filter([](T t) { return t != 0; }, Arbitrary<T>());
211 }
212
213 template <typename T>
Positive()214 auto Positive() {
215 if constexpr (std::is_floating_point_v<T>) {
216 return InRange<T>(std::numeric_limits<T>::denorm_min(),
217 std::numeric_limits<T>::max());
218 } else {
219 return InRange<T>(T{1}, std::numeric_limits<T>::max());
220 }
221 }
222
223 template <typename T>
NonNegative()224 auto NonNegative() {
225 return InRange<T>(T{}, std::numeric_limits<T>::max());
226 }
227
228 template <typename T>
Negative()229 auto Negative() {
230 static_assert(!std::is_unsigned_v<T>,
231 "Negative<T>() can only be used with with signed T-s! "
232 "For char, consider using signed char.");
233 if constexpr (std::is_floating_point_v<T>) {
234 return InRange<T>(std::numeric_limits<T>::lowest(),
235 -std::numeric_limits<T>::denorm_min());
236 } else {
237 return InRange<T>(std::numeric_limits<T>::min(), T{-1});
238 }
239 }
240
241 template <typename T>
NonPositive()242 auto NonPositive() {
243 static_assert(!std::is_unsigned_v<T>,
244 "NonPositive<T>() can only be used with with signed T-s! "
245 "For char, consider using signed char.");
246 return InRange<T>(std::numeric_limits<T>::lowest(), T{});
247 }
248
249 template <typename T>
Finite()250 auto Finite() {
251 static_assert(std::is_floating_point_v<T>,
252 "Finite<T>() can only be used with floating point types!");
253 return Filter([](T f) { return std::isfinite(f); }, Arbitrary<T>());
254 }
255
256 ////////////////////////////////////////////////////////////////
257 // Character domains
258 // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#character-domains
259
NonZeroChar()260 inline auto NonZeroChar() { return Positive<char>(); }
NumericChar()261 inline auto NumericChar() { return InRange<char>('0', '9'); }
LowerChar()262 inline auto LowerChar() { return InRange<char>('a', 'z'); }
UpperChar()263 inline auto UpperChar() { return InRange<char>('A', 'Z'); }
AlphaChar()264 inline auto AlphaChar() { return OneOf(LowerChar(), UpperChar()); }
AlphaNumericChar()265 inline auto AlphaNumericChar() { return OneOf(AlphaChar(), NumericChar()); }
AsciiChar()266 inline auto AsciiChar() { return InRange<char>(0, 127); }
PrintableAsciiChar()267 inline auto PrintableAsciiChar() { return InRange<char>(32, 126); }
268
269 ////////////////////////////////////////////////////////////////
270 // Regular expression domains
271
272 // TODO: b/285775246 - Add support for `fuzztest::InRegexp`.
273 // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#inregexp-domains
274 // inline auto InRegexp(std::string_view) {
275 // return Domain<std::string_view>{};
276 // }
277
278 ////////////////////////////////////////////////////////////////
279 // Enumerated domains
280 // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#elementof-domains
281
282 template <typename T>
ElementOf(std::initializer_list<T>)283 auto ElementOf(std::initializer_list<T>) {
284 return Domain<T>{};
285 }
286
287 template <typename T>
BitFlagCombinationOf(std::initializer_list<T>)288 auto BitFlagCombinationOf(std::initializer_list<T>) {
289 return Domain<T>{};
290 }
291
292 ////////////////////////////////////////////////////////////////
293 // Container domains
294 // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#container-combinators
295
296 template <typename T, int&... ExplicitArgumentBarrier, typename U>
ContainerOf(Domain<U>)297 auto ContainerOf(Domain<U>) {
298 return internal::ContainerDomain<T>{};
299 }
300
301 template <template <typename, typename...> class T,
302 int&... ExplicitArgumentBarrier,
303 typename U>
ContainerOf(Domain<U>)304 auto ContainerOf(Domain<U>) {
305 return internal::ContainerDomain<T<U>>{};
306 }
307
308 template <typename T, int&... ExplicitArgumentBarrier, typename U>
UniqueElementsContainerOf(Domain<U>)309 auto UniqueElementsContainerOf(Domain<U>) {
310 return internal::ContainerDomain<T>{};
311 }
312
313 template <int&... ExplicitArgumentBarrier, typename T>
NonEmpty(internal::ContainerDomain<T> inner)314 auto NonEmpty(internal::ContainerDomain<T> inner) {
315 return inner.WithMinSize(1);
316 }
317
318 ////////////////////////////////////////////////////////////////
319 // Aggregate domains
320 // https://github.com/google/fuzztest/blob/main/doc/domains-reference.md#container-combinators
321
322 template <int&... ExplicitArgumentBarrier, typename T, typename... Domains>
ArrayOf(Domain<T>,Domains...others)323 auto ArrayOf(Domain<T>, Domains... others) {
324 return Domain<std::array<T, 1 + sizeof...(others)>>{};
325 }
326
327 template <int N, int&... ExplicitArgumentBarrier, typename T>
ArrayOf(const Domain<T> &)328 auto ArrayOf(const Domain<T>&) {
329 return Domain<std::array<T, N>>{};
330 }
331
332 template <typename T, int&... ExplicitArgumentBarrier, typename... Inner>
StructOf(Inner...)333 auto StructOf(Inner...) {
334 return Domain<T>{};
335 }
336
337 template <typename T, int&... ExplicitArgumentBarrier>
ConstructorOf()338 auto ConstructorOf() {
339 return Domain<T>{};
340 }
341
342 template <typename T,
343 int&... ExplicitArgumentBarrier,
344 typename U,
345 typename... Inner>
ConstructorOf(Domain<U>,Inner...inner)346 auto ConstructorOf(Domain<U>, Inner... inner) {
347 return ConstructorOf<T>(inner...);
348 }
349
350 template <int&... ExplicitArgumentBarrier, typename T1, typename T2>
PairOf(Domain<T1>,Domain<T2>)351 auto PairOf(Domain<T1>, Domain<T2>) {
352 return Domain<std::pair<T1, T2>>{};
353 }
354
355 template <int&... ExplicitArgumentBarrier, typename... Inner>
TupleOf(Inner...)356 auto TupleOf(Inner...) {
357 return Domain<std::tuple<typename Inner::value_type...>>{};
358 }
359
360 template <typename T, int&... ExplicitArgumentBarrier, typename... Inner>
VariantOf(Inner...)361 auto VariantOf(Inner...) {
362 return Domain<T>{};
363 }
364
365 template <int&... ExplicitArgumentBarrier, typename... Inner>
VariantOf(Inner...)366 auto VariantOf(Inner...) {
367 return Domain<std::variant<typename Inner::value_type...>>{};
368 }
369
370 template <template <typename> class Optional,
371 int&... ExplicitArgumentBarrier,
372 typename T>
OptionalOf(Domain<T>)373 auto OptionalOf(Domain<T>) {
374 return internal::OptionalDomain<Optional<T>>{};
375 }
376
377 template <int&... ExplicitArgumentBarrier, typename T>
OptionalOf(Domain<T> inner)378 auto OptionalOf(Domain<T> inner) {
379 return OptionalOf<std::optional>(inner);
380 }
381
382 template <typename T>
NullOpt()383 auto NullOpt() {
384 return internal::OptionalDomain<std::optional<T>>{}.SetAlwaysNull();
385 }
386
387 template <int&... ExplicitArgumentBarrier, typename T>
NonNull(internal::OptionalDomain<T> inner)388 auto NonNull(internal::OptionalDomain<T> inner) {
389 return inner.SetWithoutNull();
390 }
391
392 } // namespace internal_no_adl
393
394 // Inject the names from internal_no_adl into fuzztest, without allowing for
395 // ADL. Note that an `inline` namespace would not have this effect (ie it would
396 // still allow ADL to trigger).
397 using namespace internal_no_adl; // NOLINT
398
399 } // namespace fuzztest
400