Skip to content

Commit a4eafb7

Browse files
lowdaniecopybara-github
authored andcommitted
Arbitrary Enum Domain
PiperOrigin-RevId: 918473334
1 parent 50b9c99 commit a4eafb7

8 files changed

Lines changed: 253 additions & 1 deletion

File tree

doc/domains-reference.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Specifically, for the following types:
4545
`absl::Duration`, `absl::Time`.
4646
- [Abseil status types](https://abseil.io/docs/cpp/guides/status):
4747
`absl::StatusCode`, `absl::Status` (without support for payloads).
48+
- Enum types: `enum class Color { kRed, kGreen, kBlue };`
4849

4950
Composite or container types, like `std::optional<T>` or `std::vector<T>`, are
5051
supported as long as the inner types are. For example,
@@ -62,8 +63,13 @@ cannot have C-style array members (e.g., `int[5]`).
6263
TIP: If your struct doesn't satisfy the requirements for `Arbitrary`, you can
6364
construct a domain for it using `Map`, `ReversibleMap`, or `FlatMap`.
6465

66+
Enum types are only supported for `clang`, `gcc`, and `msvc`.
67+
Automatic reflection only detects values in the range `[-128, 127]`.
68+
Values outside this range will not be generated by `Arbitrary<T>()`.
69+
At least one value must be in this range to avoid a compile-time error.
70+
6571
Recall that `Arbitrary` is the default input domain, which means that you can
66-
fuzz a function like below without a `.WithDomains()` clause:
72+
fuzz a function like below without a `.WithDomains()` clause.
6773

6874
```c++
6975
void MyProperty(const absl::flat_hash_map<uint32, MyProtoMessage>& m,

domain_tests/arbitrary_domains_test.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ using ::testing::Each;
5454
using ::testing::Ge;
5555
using ::testing::IsEmpty;
5656
using ::testing::SizeIs;
57+
using ::testing::UnorderedElementsAre;
5758

5859
TEST(BoolTest, Arbitrary) {
5960
absl::BitGen bitgen;
@@ -704,5 +705,22 @@ TEST(ArbitraryStatusTest, FromValueAndGetValuePreserveCodeAndMessage) {
704705
EXPECT_EQ(mapped_status.message(), status.message());
705706
}
706707

708+
TEST(ArbitraryEnumTest, GeneratesAllValues) {
709+
enum class MyTestEnum { kValue0, kValue1, kValue2 };
710+
711+
auto domain = Arbitrary<MyTestEnum>();
712+
absl::BitGen prng;
713+
absl::flat_hash_set<MyTestEnum> found;
714+
715+
const int max_iterations = IterationsToHitAll(3, 1.0 / 3);
716+
for (int i = 0; i < max_iterations && found.size() < 3; ++i) {
717+
found.insert(domain.GetRandomValue(prng));
718+
}
719+
720+
EXPECT_THAT(found,
721+
UnorderedElementsAre(MyTestEnum::kValue0, MyTestEnum::kValue1,
722+
MyTestEnum::kValue2));
723+
}
724+
707725
} // namespace
708726
} // namespace fuzztest

fuzztest/internal/BUILD

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,26 @@ cc_test(
4141
],
4242
)
4343

44+
cc_library(
45+
name = "enum_reflection",
46+
hdrs = ["enum_reflection.h"],
47+
deps = [
48+
":meta",
49+
"@abseil-cpp//absl/strings",
50+
"@abseil-cpp//absl/strings:string_view",
51+
],
52+
)
53+
54+
cc_test(
55+
name = "enum_reflection_test",
56+
srcs = ["enum_reflection_test.cc"],
57+
deps = [
58+
":enum_reflection",
59+
"@abseil-cpp//absl/strings:string_view",
60+
"@googletest//:gtest_main",
61+
],
62+
)
63+
4464
cc_library(
4565
name = "centipede_adaptor",
4666
srcs = ["centipede_adaptor.cc"],

fuzztest/internal/CMakeLists.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,28 @@ fuzztest_cc_test(
3737
GTest::gmock_main
3838
)
3939

40+
fuzztest_cc_library(
41+
NAME
42+
enum_reflection
43+
HDRS
44+
"enum_reflection.h"
45+
DEPS
46+
absl::strings
47+
absl::string_view
48+
fuzztest::meta
49+
)
50+
51+
fuzztest_cc_test(
52+
NAME
53+
enum_reflection_test
54+
SRCS
55+
"enum_reflection_test.cc"
56+
DEPS
57+
fuzztest::enum_reflection
58+
GTest::gmock_main
59+
absl::string_view
60+
)
61+
4062
fuzztest_cc_library(
4163
NAME
4264
compatibility_mode

fuzztest/internal/domains/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ cc_library(
7474
"@com_google_fuzztest//common:logging",
7575
"@com_google_fuzztest//fuzztest:fuzzing_bit_gen",
7676
"@com_google_fuzztest//fuzztest/internal:any",
77+
"@com_google_fuzztest//fuzztest/internal:enum_reflection",
7778
"@com_google_fuzztest//fuzztest/internal:logging",
7879
"@com_google_fuzztest//fuzztest/internal:meta",
7980
"@com_google_fuzztest//fuzztest/internal:printer",

fuzztest/internal/domains/arbitrary_impl.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#include "./fuzztest/internal/domains/special_values.h"
5151
#include "./fuzztest/internal/domains/value_mutation_helpers.h"
5252
#include "./fuzztest/internal/domains/variant_of_impl.h"
53+
#include "./fuzztest/internal/enum_reflection.h"
5354
#include "./fuzztest/internal/meta.h"
5455
#include "./fuzztest/internal/serialization.h"
5556
#include "./fuzztest/internal/status.h"
@@ -66,6 +67,21 @@ class ArbitraryImpl {
6667
);
6768
};
6869

70+
// Arbitrary for enums.
71+
// See limitations in fuzztest::internal::enum_reflection::GetEnumValues
72+
template <typename T>
73+
class ArbitraryImpl<
74+
T, std::enable_if_t<std::is_enum_v<T> && !is_protocol_buffer_enum_v<T>>>
75+
: public ElementOfImpl<T> {
76+
static_assert(enum_reflection::HasEnumValuesInRange<T>(),
77+
"Arbitrary<T>() for enums requires at least one value in the "
78+
"supported range [-128, 127] for automatic reflection. "
79+
"Please use ElementOf directly with explicit values.");
80+
81+
public:
82+
ArbitraryImpl() : ElementOfImpl<T>(enum_reflection::GetEnumValues<T>()) {}
83+
};
84+
6985
// Arbitrary for monostate.
7086
//
7187
// For monostate types with a default constructor, just give the single value.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of 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,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ENUM_REFLECTION_H_
16+
#define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ENUM_REFLECTION_H_
17+
18+
#include <cstddef>
19+
#include <vector>
20+
21+
#include "absl/strings/string_view.h"
22+
#include "absl/strings/strip.h"
23+
#include "./fuzztest/internal/meta.h"
24+
25+
namespace fuzztest::internal::enum_reflection {
26+
27+
constexpr bool IsValidCharacter(char c) {
28+
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
29+
(c >= 'A' && c <= 'Z') || c == '_';
30+
}
31+
32+
constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; }
33+
34+
// Checks if `name` ends with `compiler_suffix`. After removing it, checks if
35+
// the remaining string ends with a valid identifier (valid enum value) rather
36+
// than a numeric literal (invalid value).
37+
constexpr bool IsValidEnumValueSuffix(absl::string_view name,
38+
absl::string_view compiler_suffix) {
39+
if (!absl::ConsumeSuffix(&name, compiler_suffix)) return false;
40+
41+
size_t i = name.size();
42+
while (i > 0 && IsValidCharacter(name[i - 1])) {
43+
--i;
44+
}
45+
return i < name.size() && !IsDigit(name[i]);
46+
}
47+
48+
// When the template parameter V is equal to a valid enum value,
49+
// compilers replace the signature macro with a string containing the enum name.
50+
//
51+
// For Clang/GCC, __PRETTY_FUNCTION__ ends in something like:
52+
// "[E = ns::MyEnum, V = ns::MyEnum::kRed]"
53+
// If V is not a valid value, the suffix looks like:
54+
// "[E = ns::MyEnum, V = (ns::MyEnum)5]".
55+
//
56+
// For MSVC, __FUNCSIG__ ends in something like:
57+
// "IsValidEnumValue<enum ns::MyEnum,ns::MyEnum::kRed>(void)"
58+
// If V is not a valid value, it looks like:
59+
// "IsValidEnumValue<enum ns::MyEnum,5>(void)"
60+
template <typename E, E V>
61+
constexpr bool IsValidEnumValue() {
62+
#if defined(__clang__) || defined(__GNUC__)
63+
constexpr absl::string_view kCompilerSuffix = "]";
64+
return IsValidEnumValueSuffix(
65+
{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}, kCompilerSuffix);
66+
#elif defined(_MSC_VER)
67+
constexpr absl::string_view kCompilerSuffix = ">(void)";
68+
return IsValidEnumValueSuffix({__FUNCSIG__, sizeof(__FUNCSIG__) - 1},
69+
kCompilerSuffix);
70+
#else
71+
#error "Enum reflection is only supported on Clang, GCC, and MSVC"
72+
#endif
73+
}
74+
75+
template <typename E>
76+
constexpr bool HasEnumValuesInRange() {
77+
return ApplyIndex<256>([](auto... I) {
78+
return (IsValidEnumValue<E, static_cast<E>(static_cast<int>(I) - 128)>() ||
79+
...);
80+
});
81+
}
82+
83+
// Currently only available with Clang, GCC and MSVC.
84+
// Assumes that the enums values are within the range [-128, 127].
85+
template <typename E>
86+
std::vector<E> GetEnumValues() {
87+
return ApplyIndex<256>([](auto... I) {
88+
std::vector<E> values;
89+
auto add_if_valid = [&](auto index) {
90+
if constexpr (IsValidEnumValue<E, static_cast<E>(static_cast<int>(index) -
91+
128)>()) {
92+
values.push_back(static_cast<E>(static_cast<int>(index) - 128));
93+
}
94+
};
95+
(add_if_valid(I), ...);
96+
return values;
97+
});
98+
}
99+
100+
} // namespace fuzztest::internal::enum_reflection
101+
102+
#endif // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ENUM_REFLECTION_H_
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of 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,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "./fuzztest/internal/enum_reflection.h"
16+
17+
#include <vector>
18+
19+
#include "gmock/gmock.h"
20+
#include "gtest/gtest.h"
21+
#include "absl/strings/string_view.h"
22+
23+
namespace fuzztest::internal::enum_reflection {
24+
namespace {
25+
26+
using ::testing::UnorderedElementsAre;
27+
28+
TEST(IsValidEnumValue, WithValidSuffixes) {
29+
EXPECT_TRUE(IsValidEnumValueSuffix("... V = kGreen]", "]"));
30+
EXPECT_TRUE(IsValidEnumValueSuffix("... V = Green]", "]"));
31+
EXPECT_TRUE(IsValidEnumValueSuffix("... V = _Value_123]", "]"));
32+
EXPECT_TRUE(IsValidEnumValueSuffix("... V = fuzztest::Color::kGreen]", "]"));
33+
}
34+
35+
TEST(IsValidEnumValue, WithInvalidSuffixes) {
36+
EXPECT_FALSE(IsValidEnumValueSuffix("... V = kGreen", "]")); // Missing ]
37+
EXPECT_FALSE(IsValidEnumValueSuffix("", "]")); // Empty
38+
EXPECT_FALSE(IsValidEnumValueSuffix("]", "]")); // Only ]
39+
EXPECT_FALSE(IsValidEnumValueSuffix("... V = 42]", "]")); // Ends with number
40+
EXPECT_FALSE(
41+
IsValidEnumValueSuffix("... V = (Color)2]", "]")); // Ends with number
42+
EXPECT_FALSE(IsValidEnumValueSuffix("... V = ]", "]")); // Empty suffix
43+
}
44+
45+
enum class MyTestEnum { kVal0 = 0, kVal_1 = 2, ABC = 3 };
46+
47+
TEST(GetEnumValues, ReturnsValidValues) {
48+
std::vector<MyTestEnum> values = GetEnumValues<MyTestEnum>();
49+
EXPECT_THAT(values,
50+
UnorderedElementsAre(MyTestEnum::kVal0, MyTestEnum::kVal_1,
51+
MyTestEnum::ABC));
52+
}
53+
54+
TEST(HasEnumValuesInRange, ReturnsTrueForValidEnum) {
55+
EXPECT_TRUE(HasEnumValuesInRange<MyTestEnum>());
56+
}
57+
58+
TEST(HasEnumValuesInRange, ReturnsFalseForOutOfRangeEnum) {
59+
enum class OutOfRangeEnum { kVal = 128 };
60+
enum class NegOutOfRangeEnum { kVal = -129 };
61+
62+
EXPECT_FALSE(HasEnumValuesInRange<OutOfRangeEnum>());
63+
EXPECT_FALSE(HasEnumValuesInRange<NegOutOfRangeEnum>());
64+
}
65+
66+
} // namespace
67+
} // namespace fuzztest::internal::enum_reflection

0 commit comments

Comments
 (0)