diff --git a/doc/domains-reference.md b/doc/domains-reference.md index 4db65653c..9a73f5e31 100644 --- a/doc/domains-reference.md +++ b/doc/domains-reference.md @@ -42,6 +42,9 @@ protocol buffers. Specifically, for the following types: - Protocol buffer types: `MyProtoMessage`, etc. - [Abseil time library types](https://abseil.io/docs/cpp/guides/time): `absl::Duration`, `absl::Time`. +- [Abseil status types](https://abseil.io/docs/cpp/guides/status): + `absl::StatusCode`, `absl::Status`. +- Enum types: `enum class Color { kRed, kGreen, kBlue };` Composite or container types, like `std::optional` or `std::vector`, are supported as long as the inner types are. For example, @@ -59,6 +62,9 @@ cannot have C-style array members (e.g., `int[5]`). TIP: If your struct doesn't satisfy the requirements for `Arbitrary`, you can construct a domain for it using `Map`, `ReversibleMap`, or `FlatMap`. +Enum types only supported for `clang` and `gcc` +with enums that have fewer than 256 fields. + Recall that `Arbitrary` is the default input domain, which means that you can fuzz a function like below without a `.WithDomains()` clause: diff --git a/domain_tests/BUILD b/domain_tests/BUILD index 8d8d25194..9068e66ee 100644 --- a/domain_tests/BUILD +++ b/domain_tests/BUILD @@ -164,6 +164,16 @@ cc_library( ], ) +cc_test( + name = "enum_reflection_test", + srcs = ["enum_reflection_test.cc"], + deps = [ + "@abseil-cpp//absl/strings:string_view", + "@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl", + "@googletest//:gtest_main", + ], +) + cc_test( name = "in_grammar_domain_test", srcs = ["in_grammar_domain_test.cc"], diff --git a/domain_tests/arbitrary_domains_test.cc b/domain_tests/arbitrary_domains_test.cc index ce611704e..afb05d038 100644 --- a/domain_tests/arbitrary_domains_test.cc +++ b/domain_tests/arbitrary_domains_test.cc @@ -626,5 +626,88 @@ TEST(ArbitraryTimeTest, ArbitraryVectorHasAllTypesOfValues) { EXPECT_THAT(to_find, IsEmpty()); } +TEST(ArbitraryStatusCodeTest, GeneratesAllValues) { + absl::flat_hash_set to_find = { + absl::StatusCode::kOk, + absl::StatusCode::kCancelled, + absl::StatusCode::kUnknown, + absl::StatusCode::kInvalidArgument, + absl::StatusCode::kDeadlineExceeded, + absl::StatusCode::kNotFound, + absl::StatusCode::kAlreadyExists, + absl::StatusCode::kPermissionDenied, + absl::StatusCode::kResourceExhausted, + absl::StatusCode::kFailedPrecondition, + absl::StatusCode::kAborted, + absl::StatusCode::kOutOfRange, + absl::StatusCode::kUnimplemented, + absl::StatusCode::kInternal, + absl::StatusCode::kUnavailable, + absl::StatusCode::kDataLoss, + absl::StatusCode::kUnauthenticated, + }; + auto domain = Arbitrary(); + absl::BitGen prng; + + const int max_iterations = IterationsToHitAll(17, 1.0 / 17); + for (int i = 0; i < max_iterations && !to_find.empty(); ++i) { + to_find.erase(domain.GetRandomValue(prng)); + } + + EXPECT_THAT(to_find, IsEmpty()); +} + +TEST(ArbitraryStatusCodeTest, InitGeneratesSeeds) { + Domain domain = Arbitrary().WithSeeds( + {absl::StatusCode::kInvalidArgument}); + + EXPECT_THAT(GenerateInitialValues(domain, 1000), + Contains(Value(domain, absl::StatusCode::kInvalidArgument))); +} + +TEST(ArbitraryStatusTest, GeneratesOkAndError) { + auto domain = Arbitrary(); + absl::BitGen prng; + bool found_ok = false; + bool found_error = false; + + for (int i = 0; i < 100 && (!found_ok || !found_error); ++i) { + absl::Status s = domain.GetRandomValue(prng); + if (s.ok()) { + found_ok = true; + } else { + found_error = true; + } + } + + EXPECT_TRUE(found_ok); + EXPECT_TRUE(found_error); +} + +TEST(ArbitraryStatusTest, InitGeneratesSeeds) { + absl::Status seed = absl::InvalidArgumentError("seed message"); + Domain domain = Arbitrary().WithSeeds({seed}); + + EXPECT_THAT(GenerateInitialValues(domain, 1000), + Contains(Value(domain, seed))); +} + +enum class MyTestEnum { kValue0, kValue1, kValue2 }; + +TEST(ArbitraryEnumTest, GeneratesAllValues) { + auto domain = Arbitrary(); + absl::BitGen prng; + absl::flat_hash_set found; + + const int max_iterations = IterationsToHitAll(3, 1.0 / 3); + for (int i = 0; i < max_iterations && found.size() < 3; ++i) { + found.insert(domain.GetRandomValue(prng)); + } + + EXPECT_THAT(found, testing::UnorderedElementsAre(MyTestEnum::kValue0, + MyTestEnum::kValue1, + MyTestEnum::kValue2)); +} + } // namespace } // namespace fuzztest diff --git a/domain_tests/enum_reflection_test.cc b/domain_tests/enum_reflection_test.cc new file mode 100644 index 000000000..1c9cd204e --- /dev/null +++ b/domain_tests/enum_reflection_test.cc @@ -0,0 +1,53 @@ +// Copyright 2026 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 +// +// 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. + +#include "./fuzztest/internal/domains/enum_reflection.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" + +namespace fuzztest::internal::enum_reflection { +namespace { + +TEST(EnumReflectionTest, IsValidEnumValueSuffix_ValidSuffixes) { + EXPECT_TRUE(IsValidEnumValueSuffix("... V = kGreen]")); + EXPECT_TRUE(IsValidEnumValueSuffix("... V = Green]")); + EXPECT_TRUE(IsValidEnumValueSuffix("... V = _Value_123]")); + EXPECT_TRUE(IsValidEnumValueSuffix("... V = fuzztest::Color::kGreen]")); +} + +TEST(EnumReflectionTest, IsValidEnumValueSuffix_InvalidSuffixes) { + EXPECT_FALSE(IsValidEnumValueSuffix("... V = kGreen")); // Missing ] + EXPECT_FALSE(IsValidEnumValueSuffix("")); // Empty + EXPECT_FALSE(IsValidEnumValueSuffix("]")); // Only ] + EXPECT_FALSE(IsValidEnumValueSuffix("... V = 42]")); // Ends with number + EXPECT_FALSE( + IsValidEnumValueSuffix("... V = (Color)2]")); // Ends with number + EXPECT_FALSE(IsValidEnumValueSuffix("... V = ]")); // Empty suffix +} + +enum class MyTestEnum { kVal0 = 0, kVal_1 = 2, ABC = 3 }; + +TEST(EnumReflectionTest, GetEnumValues_ReturnsValidValues) { + std::vector values = GetEnumValues(); + EXPECT_THAT(values, + testing::UnorderedElementsAre( + MyTestEnum::kVal0, MyTestEnum::kVal_1, MyTestEnum::ABC)); +} + +} // namespace +} // namespace fuzztest::internal::enum_reflection diff --git a/fuzztest/internal/domains/BUILD b/fuzztest/internal/domains/BUILD index 732d67573..ef257948d 100644 --- a/fuzztest/internal/domains/BUILD +++ b/fuzztest/internal/domains/BUILD @@ -41,6 +41,7 @@ cc_library( "domain_base.h", "domain_type_erasure.h", "element_of_impl.h", + "enum_reflection.h", "filter_impl.h", "flat_map_impl.h", "in_range_impl.h", diff --git a/fuzztest/internal/domains/arbitrary_impl.h b/fuzztest/internal/domains/arbitrary_impl.h index b4d53484e..28a602004 100644 --- a/fuzztest/internal/domains/arbitrary_impl.h +++ b/fuzztest/internal/domains/arbitrary_impl.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include "absl/random/bit_gen_ref.h" #include "absl/random/distributions.h" +#include "absl/status/status.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "./fuzztest/internal/domains/absl_helpers.h" @@ -40,6 +42,7 @@ #include "./fuzztest/internal/domains/domain.h" #include "./fuzztest/internal/domains/domain_base.h" #include "./fuzztest/internal/domains/element_of_impl.h" +#include "./fuzztest/internal/domains/enum_reflection.h" #include "./fuzztest/internal/domains/in_range_impl.h" #include "./fuzztest/internal/domains/map_impl.h" #include "./fuzztest/internal/domains/one_of_impl.h" @@ -50,7 +53,6 @@ #include "./fuzztest/internal/domains/variant_of_impl.h" #include "./fuzztest/internal/meta.h" #include "./fuzztest/internal/serialization.h" -#include "./fuzztest/internal/status.h" #include "./fuzztest/internal/table_of_recent_compares.h" #include "./fuzztest/internal/type_support.h" @@ -64,6 +66,16 @@ class ArbitraryImpl { ); }; +// Arbitrary for enums. +// See limitations in fuzztest::internal::enum_reflection::GetEnumValues +template +class ArbitraryImpl< + T, std::enable_if_t && !is_protocol_buffer_enum_v>> + : public ElementOfImpl { + public: + ArbitraryImpl() : ElementOfImpl(enum_reflection::GetEnumValues()) {} +}; + // Arbitrary for monostate. // // For monostate types with a default constructor, just give the single value. @@ -583,6 +595,51 @@ class ArbitraryImpl ArbitraryImpl()) {} }; +// Arbitrary for absl::StatusCode. +template <> +class ArbitraryImpl + : public ReversibleMapImpl> (*)( + absl::StatusCode), + InRangeImpl> { + public: + ArbitraryImpl() + : ReversibleMapImpl> (*)(absl::StatusCode), + InRangeImpl>( + [](int code) { return static_cast(code); }, + [](absl::StatusCode code) { + return std::optional{std::tuple{static_cast(code)}}; + }, + InRangeImpl(0, 16)) {} +}; + +// Arbitrary for absl::Status. +// Note: This does not generate payloads. +template <> +class ArbitraryImpl + : public ReversibleMapImpl< + absl::Status (*)(absl::StatusCode, std::string), + std::optional> (*)( + absl::Status), + ArbitraryImpl, ArbitraryImpl> { + public: + ArbitraryImpl() + : ReversibleMapImpl< + absl::Status (*)(absl::StatusCode, std::string), + std::optional> (*)( + absl::Status), + ArbitraryImpl, ArbitraryImpl>( + [](absl::StatusCode code, std::string msg) { + return absl::Status(code, msg); + }, + [](absl::Status status) { + return std::optional{ + std::tuple{status.code(), std::string(status.message())}}; + }, + ArbitraryImpl(), ArbitraryImpl()) {} +}; + // Arbitrary for absl::BitGenRef. template <> class ArbitraryImpl diff --git a/fuzztest/internal/domains/enum_reflection.h b/fuzztest/internal/domains/enum_reflection.h new file mode 100644 index 000000000..709ab921c --- /dev/null +++ b/fuzztest/internal/domains/enum_reflection.h @@ -0,0 +1,88 @@ +// Copyright 2026 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 +// +// 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. + +#ifndef FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ENUM_REFLECTION_H_ +#define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ENUM_REFLECTION_H_ + +#include +#include +#include +#include + +#include "absl/strings/string_view.h" + +namespace fuzztest::internal { + +namespace enum_reflection { + +constexpr bool IsValidCharacter(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || c == '_'; +} + +constexpr bool IsNumber(char c) { return c >= '0' && c <= '9'; } + +// When the template parameter V is equal to a valid enum value, +// clang replaces __PRETTY_FUNCTION__ with a string ending in something like: +// "[E = my_namespace::MyEnum, V = my_namespace::MyEnum::kRed]" +// If V is not a valid value, the suffix looks like: +// "[E = my_namespace::MyEnum, V = (my_namespace::MyEnum)5]". +constexpr bool IsValidEnumValueSuffix(absl::string_view name) { + if (name.empty() || name.back() != ']') return false; + name.remove_suffix(1); + + size_t i = name.size(); + while (i > 0 && IsValidCharacter(name[i - 1])) { + --i; + } + return i < name.size() && !IsNumber(name[i]); +} + +template +constexpr bool IsValidEnumValue() { +#if defined(__clang__) || defined(__GNUC__) + return IsValidEnumValueSuffix( + {__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}); +#else +#error "Enum reflection is only supported on Clang and GCC" +#endif +} + +template +std::vector GetValidEnumValues(std::integer_sequence) { + constexpr auto is_valid = + std::array{IsValidEnumValue(Is - 128)>()...}; + constexpr auto values_arr = std::array{static_cast(Is - 128)...}; + + std::vector values; + for (size_t i = 0; i < sizeof...(Is); ++i) { + if (is_valid[i]) { + values.push_back(values_arr[i]); + } + } + return values; +} + +// Currently only available with Clang and GCC. +// Assumes that the enums values are within the range [-128, 127]. +template +std::vector GetEnumValues() { + return GetValidEnumValues(std::make_integer_sequence{}); +} + +} // namespace enum_reflection + +} // namespace fuzztest::internal + +#endif // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ENUM_REFLECTION_H_