Add struct support to the flatbuffers domain
PiperOrigin-RevId: 743170054
diff --git a/.github/workflows/cmake_test.yml b/.github/workflows/cmake_test.yml
index d23c692..e4a5f44 100644
--- a/.github/workflows/cmake_test.yml
+++ b/.github/workflows/cmake_test.yml
@@ -77,6 +77,7 @@
-D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
-D CMAKE_BUILD_TYPE=RelWithDebug \
-D FUZZTEST_BUILD_TESTING=on \
+ -D FUZZTEST_BUILD_FLATBUFFERS=on \
&& cmake --build build -j $(nproc) \
&& ctest --test-dir build -j $(nproc) --output-on-failure
- name: Run all tests in default mode with gcc
@@ -90,6 +91,7 @@
-D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
-D CMAKE_BUILD_TYPE=RelWithDebug \
-D FUZZTEST_BUILD_TESTING=on \
+ -D FUZZTEST_BUILD_FLATBUFFERS=on \
&& cmake --build build_gcc -j $(nproc) \
&& ctest --test-dir build_gcc -j $(nproc) --output-on-failure
- name: Run end-to-end tests in fuzzing mode
@@ -104,6 +106,7 @@
-D CMAKE_BUILD_TYPE=RelWithDebug \
-D FUZZTEST_FUZZING_MODE=on \
-D FUZZTEST_BUILD_TESTING=on \
+ -D FUZZTEST_BUILD_FLATBUFFERS=on \
&& cmake --build build -j $(nproc) \
&& ctest --test-dir build -j $(nproc) --output-on-failure -R "functional_test"
- name: Save new cache based on main
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1e34de3..e3803d3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,6 +2,7 @@
project(fuzztest)
option(FUZZTEST_BUILD_TESTING "Building the tests." OFF)
+option(FUZZTEST_BUILD_FLATBUFFERS "Building the flatbuffers support." OFF)
option(FUZZTEST_FUZZING_MODE "Building the fuzztest in fuzzing mode." OFF)
set(FUZZTEST_COMPATIBILITY_MODE "" CACHE STRING "Compatibility mode. Available options: <empty>, libfuzzer")
set(CMAKE_CXX_STANDARD 17)
diff --git a/MODULE.bazel b/MODULE.bazel
index 9fbd479..28902ac 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -42,6 +42,10 @@
name = "platforms",
version = "0.0.10",
)
+bazel_dep(
+ name = "flatbuffers",
+ version = "25.2.10"
+)
# GoogleTest is not a dev dependency, because it's needed when FuzzTest is used
# with GoogleTest integration (e.g., googletest_adaptor). Note that the FuzzTest
# framework can be used without GoogleTest integration as well.
@@ -55,8 +59,6 @@
name = "protobuf",
version = "30.2",
)
-# TODO(lszekeres): Make this a dev dependency, as the protobuf library is only
-# required for testing.
bazel_dep(
name = "rules_proto",
version = "7.1.0",
diff --git a/cmake/BuildDependencies.cmake b/cmake/BuildDependencies.cmake
index 5214fce..2966c0b 100644
--- a/cmake/BuildDependencies.cmake
+++ b/cmake/BuildDependencies.cmake
@@ -21,6 +21,9 @@
set(nlohmann_json_URL https://github.com/nlohmann/json.git)
set(nlohmann_json_TAG v3.11.3)
+set(flatbuffers_URL https://github.com/google/flatbuffers.git)
+set(flatbuffers_TAG v25.2.10)
+
if(POLICY CMP0135)
cmake_policy(SET CMP0135 NEW)
set(CMAKE_POLICY_DEFAULT_CMP0135 NEW)
@@ -50,6 +53,14 @@
URL_HASH MD5=${antlr_cpp_MD5}
)
+if (FUZZTEST_BUILD_FLATBUFFERS)
+ FetchContent_Declare(
+ flatbuffers
+ GIT_REPOSITORY ${flatbuffers_URL}
+ GIT_TAG ${flatbuffers_TAG}
+ )
+endif()
+
if (FUZZTEST_BUILD_TESTING)
FetchContent_Declare(
@@ -87,3 +98,9 @@
FetchContent_MakeAvailable(nlohmann_json)
endif ()
+
+if (FUZZTEST_BUILD_FLATBUFFERS)
+ set(FLATBUFFERS_BUILD_TESTS OFF)
+ set(FLATBUFFERS_BUILD_INSTALL OFF)
+ FetchContent_MakeAvailable(flatbuffers)
+endif()
diff --git a/cmake/generate_cmake_from_bazel.py b/cmake/generate_cmake_from_bazel.py
index 83d31d5..0c9079c 100755
--- a/cmake/generate_cmake_from_bazel.py
+++ b/cmake/generate_cmake_from_bazel.py
@@ -52,6 +52,7 @@
"@abseil-cpp//absl/types:optional": "absl::optional",
"@abseil-cpp//absl/types:span": "absl::span",
"@abseil-cpp//absl/types:variant": "absl::variant",
+ "@flatbuffers//:runtime_cc": "flatbuffers",
"@googletest//:gtest": "GTest::gtest",
"@googletest//:gtest_main": "GTest::gmock_main",
"@protobuf//:protobuf": "protobuf::libprotobuf",
diff --git a/domain_tests/BUILD b/domain_tests/BUILD
index d71a93b..82cd903 100644
--- a/domain_tests/BUILD
+++ b/domain_tests/BUILD
@@ -34,6 +34,24 @@
)
cc_test(
+ name = "arbitrary_domains_flatbuffers_test",
+ srcs = ["arbitrary_domains_flatbuffers_test.cc"],
+ deps = [
+ ":domain_testing",
+ "@abseil-cpp//absl/container:flat_hash_map",
+ "@abseil-cpp//absl/log:check",
+ "@abseil-cpp//absl/random",
+ "@com_google_fuzztest//fuzztest:domain",
+ "@com_google_fuzztest//fuzztest:flatbuffers",
+ "@com_google_fuzztest//fuzztest:meta",
+ "@com_google_fuzztest//fuzztest:serialization",
+ "@com_google_fuzztest//fuzztest:test_flatbuffers_cc_fbs",
+ "@flatbuffers//:runtime_cc",
+ "@googletest//:gtest_main",
+ ],
+)
+
+cc_test(
name = "arbitrary_domains_protobuf_test",
srcs = ["arbitrary_domains_protobuf_test.cc"],
deps = [
diff --git a/domain_tests/CMakeLists.txt b/domain_tests/CMakeLists.txt
index 703ee49..092d283 100644
--- a/domain_tests/CMakeLists.txt
+++ b/domain_tests/CMakeLists.txt
@@ -19,6 +19,30 @@
GTest::gmock_main
)
+if (FUZZTEST_BUILD_FLATBUFFERS)
+ fuzztest_cc_test(
+ NAME
+ arbitrary_domains_flatbuffers_test
+ SRCS
+ "arbitrary_domains_flatbuffers_test.cc"
+ DEPS
+ absl::flat_hash_set
+ absl::random_bit_gen_ref
+ absl::random_random
+ absl::strings
+ flatbuffers
+ fuzztest::flatbuffers
+ fuzztest::domain
+ fuzztest::domain_testing
+ fuzztest::flatbuffers
+ GTest::gmock_main
+ test_flatbuffers
+ )
+ add_dependencies(fuzztest_arbitrary_domains_flatbuffers_test
+ GENERATE_test_flatbuffers
+ )
+endif()
+
fuzztest_cc_test(
NAME
arbitrary_domains_protobuf_test
diff --git a/domain_tests/arbitrary_domains_flatbuffers_test.cc b/domain_tests/arbitrary_domains_flatbuffers_test.cc
new file mode 100644
index 0000000..15cbd78
--- /dev/null
+++ b/domain_tests/arbitrary_domains_flatbuffers_test.cc
@@ -0,0 +1,913 @@
+// Copyright 2025 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
+//
+// http://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 <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/log/check.h"
+#include "absl/random/random.h"
+#include "flatbuffers/base.h"
+#include "flatbuffers/buffer.h"
+#include "flatbuffers/flatbuffer_builder.h"
+#include "flatbuffers/string.h"
+#include "flatbuffers/vector.h"
+#include "flatbuffers/verifier.h"
+#include "./fuzztest/domain.h"
+#include "./domain_tests/domain_testing.h"
+#include "./fuzztest/flatbuffers.h"
+#include "./fuzztest/internal/meta.h"
+#include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/test_flatbuffers_generated.h"
+
+namespace fuzztest {
+namespace {
+
+using ::fuzztest::internal::BoolStruct;
+using ::fuzztest::internal::BoolTable;
+using ::fuzztest::internal::DefaultStruct;
+using ::fuzztest::internal::DefaultTable;
+using ::fuzztest::internal::Enum;
+using ::fuzztest::internal::OptionalTable;
+using ::fuzztest::internal::RequiredTable;
+using ::fuzztest::internal::StringTable;
+using ::fuzztest::internal::UnionTable;
+using ::testing::_;
+using ::testing::Contains;
+using ::testing::Each;
+using ::testing::IsTrue;
+using ::testing::NotNull;
+using ::testing::Pair;
+using ::testing::ResultOf;
+
+template <typename T>
+inline bool Eq(T rhs, T lhs) {
+ static_assert(!std::is_pointer_v<T>, "T cannot be a pointer type");
+ return lhs == rhs;
+}
+
+template <>
+inline bool Eq<const flatbuffers::String*>(const flatbuffers::String* rhs,
+ const flatbuffers::String* lhs) {
+ return (rhs == nullptr && lhs == nullptr) ||
+ (rhs != nullptr && lhs != nullptr && rhs->str() == lhs->str());
+};
+
+template <>
+inline bool Eq<BoolStruct>(BoolStruct rhs, BoolStruct lhs) {
+ return Eq(rhs.b(), lhs.b());
+};
+
+template <>
+inline bool Eq<const DefaultStruct*>(const DefaultStruct* rhs,
+ const DefaultStruct* lhs) {
+ if (rhs == nullptr && lhs == nullptr) {
+ return true;
+ } else if (rhs == nullptr || lhs == nullptr) {
+ return false;
+ } else {
+ return Eq(rhs->b(), lhs->b()) && Eq(rhs->i8(), lhs->i8()) &&
+ Eq(rhs->i16(), lhs->i16()) && Eq(rhs->i32(), lhs->i32()) &&
+ Eq(rhs->i64(), lhs->i64()) && Eq(rhs->u8(), lhs->u8()) &&
+ Eq(rhs->u16(), lhs->u16()) && Eq(rhs->u32(), lhs->u32()) &&
+ Eq(rhs->u64(), lhs->u64()) && Eq(rhs->f(), lhs->f()) &&
+ Eq(rhs->d(), lhs->d()) && Eq(rhs->e(), lhs->e()) &&
+ Eq(rhs->s(), lhs->s());
+ }
+}
+
+template <>
+inline bool Eq<const BoolTable*>(const BoolTable* rhs, const BoolTable* lhs) {
+ return (rhs == nullptr && lhs == nullptr) ||
+ (rhs != nullptr && lhs != nullptr && rhs->b() == lhs->b());
+};
+
+template <>
+inline bool Eq<std::pair<uint8_t, const void*>>(
+ std::pair<uint8_t, const void*> rhs, std::pair<uint8_t, const void*> lhs) {
+ if (rhs.first == internal::Union_NONE && lhs.first == internal::Union_NONE) {
+ return true;
+ } else if (rhs.first != lhs.first) {
+ return false;
+ } else {
+ switch (rhs.first) {
+ case internal::Union_BoolTable:
+ return static_cast<const BoolTable*>(rhs.second)->b() ==
+ static_cast<const BoolTable*>(lhs.second)->b();
+ case internal::Union_StringTable:
+ return static_cast<const StringTable*>(rhs.second)->str()->str() ==
+ static_cast<const StringTable*>(lhs.second)->str()->str();
+ case internal::Union_BoolStruct:
+ return static_cast<const BoolStruct*>(rhs.second)->b() ==
+ static_cast<const BoolStruct*>(lhs.second)->b();
+ default:
+ CHECK(false) << "Unsupported union type";
+ }
+ }
+}
+
+template <typename T>
+inline bool VectorEq(const flatbuffers::Vector<T>* rhs,
+ const flatbuffers::Vector<T>* lhs) {
+ if (rhs == nullptr && lhs == nullptr) {
+ return true;
+ } else if (rhs == nullptr || lhs == nullptr) {
+ return false;
+ }
+ if (rhs->size() != lhs->size()) {
+ return false;
+ }
+ for (int i = 0; i < rhs->size(); ++i) {
+ if (!Eq(rhs->Get(i), lhs->Get(i))) {
+ return false;
+ }
+ }
+ return true;
+};
+
+inline bool VectorUnionEq(
+ const flatbuffers::Vector<uint8_t>* rhs_type,
+ const flatbuffers::Vector<::flatbuffers::Offset<void>>* rhs,
+ const flatbuffers::Vector<uint8_t>* lhs_type,
+ const flatbuffers::Vector<::flatbuffers::Offset<void>>* lhs) {
+ if (!VectorEq(rhs_type, lhs_type)) {
+ return false;
+ }
+ if (rhs == nullptr && lhs == nullptr) {
+ return true;
+ } else if (rhs == nullptr || lhs == nullptr) {
+ return false;
+ }
+ if (rhs->size() != lhs->size()) {
+ return false;
+ }
+ for (int i = 0; i < rhs->size(); ++i) {
+ if (!Eq(std::make_pair(rhs_type->Get(i), rhs->Get(i)),
+ std::make_pair(lhs_type->Get(i), lhs->Get(i)))) {
+ return false;
+ }
+ }
+ return true;
+};
+
+const internal::DefaultTable* CreateDefaultTable(
+ flatbuffers::FlatBufferBuilder& fbb) {
+ auto bool_table_offset = internal::CreateBoolTable(fbb, true);
+ auto string_table_offset =
+ internal::CreateStringTableDirect(fbb, "foo bar baz");
+ DefaultStruct s{
+ true, // b
+ 1, // i8
+ 2, // i16
+ 3, // i32
+ 4, // i64
+ 5, // u8
+ 6, // u16
+ 7, // u32
+ 8, // u64
+ 9, // f
+ 10.0, // d
+ internal::Enum_Second, // e
+ BoolStruct{true} // s
+ };
+ std::vector<uint8_t> v_b{true, false};
+ std::vector<int8_t> v_i8{1, 2, 3};
+ std::vector<int16_t> v_i16{1, 2, 3};
+ std::vector<int32_t> v_i32{1, 2, 3};
+ std::vector<int64_t> v_i64{1, 2, 3};
+ std::vector<uint8_t> v_u8{1, 2, 3};
+ std::vector<uint16_t> v_u16{1, 2, 3};
+ std::vector<uint32_t> v_u32{1, 2, 3};
+ std::vector<uint64_t> v_u64{1, 2, 3};
+ std::vector<float> v_f{1, 2, 3};
+ std::vector<double> v_d{1, 2, 3};
+ std::vector<flatbuffers::Offset<flatbuffers::String>> v_str{
+ fbb.CreateString("foo"), fbb.CreateString("bar"),
+ fbb.CreateString("baz")};
+ std::vector<std::underlying_type_t<internal::Enum>> v_e{
+ internal::Enum_First, internal::Enum_Second, internal::Enum_Third};
+ std::vector<flatbuffers::Offset<BoolTable>> v_t{bool_table_offset};
+ std::vector<std::underlying_type_t<internal::Union>> v_u_type{
+ internal::Union_BoolTable,
+ internal::Union_StringTable,
+ };
+ std::vector<flatbuffers::Offset<>> v_u{
+ bool_table_offset.Union(),
+ string_table_offset.Union(),
+ };
+ std::vector<DefaultStruct> v_s{s};
+ auto table_offset =
+ internal::CreateDefaultTableDirect(fbb,
+ true, // b
+ 1, // i8
+ 2, // i16
+ 3, // i32
+ 4, // i64
+ 5, // u8
+ 6, // u16
+ 7, // u32
+ 8, // u64
+ 9.0, // f
+ 10.0, // d
+ "foo bar baz", // str
+ internal::Enum_Second, // e
+ bool_table_offset, // t
+ internal::Union_BoolTable, // u_type
+ bool_table_offset.Union(), // u
+ &s, // s
+ &v_b, // v_b
+ &v_i8, // v_i8
+ &v_i16, // v_i16
+ &v_i32, // v_i32
+ &v_i64, // v_i64
+ &v_u8, // v_u8
+ &v_u16, // v_u16
+ &v_u32, // v_u32
+ &v_u64, // v_u64
+ &v_f, // v_f
+ &v_d, // v_d
+ &v_str, // v_str
+ &v_e, // v_e
+ &v_t, // v_t
+ &v_u_type, // v_u_type
+ &v_u, // v_u
+ &v_s // v_s
+ );
+ fbb.Finish(table_offset);
+ return flatbuffers::GetRoot<DefaultTable>(fbb.GetBufferPointer());
+}
+
+TEST(FlatbuffersMetaTest, IsFlatbuffersTable) {
+ static_assert(internal::is_flatbuffers_table_v<DefaultTable>);
+ static_assert(!internal::is_flatbuffers_table_v<int>);
+ static_assert(!internal::is_flatbuffers_table_v<std::optional<bool>>);
+}
+
+TEST(FlatbuffersTableDomainImplTest, DefaultTableValueRoundTrip) {
+ flatbuffers::FlatBufferBuilder fbb;
+ auto table = CreateDefaultTable(fbb);
+
+ auto domain = Arbitrary<DefaultTable>();
+ auto corpus = domain.FromValue(table);
+ ASSERT_TRUE(corpus.has_value());
+ ASSERT_OK(domain.ValidateCorpusValue(*corpus));
+
+ auto ir = domain.SerializeCorpus(corpus.value());
+
+ auto new_corpus = domain.ParseCorpus(ir);
+ ASSERT_TRUE(new_corpus.has_value());
+ ASSERT_OK(domain.ValidateCorpusValue(*new_corpus));
+
+ auto new_table = domain.GetValue(*new_corpus);
+ EXPECT_EQ(new_table->b(), true);
+ EXPECT_EQ(new_table->i8(), 1);
+ EXPECT_EQ(new_table->i16(), 2);
+ EXPECT_EQ(new_table->i32(), 3);
+ EXPECT_EQ(new_table->i64(), 4);
+ EXPECT_EQ(new_table->u8(), 5);
+ EXPECT_EQ(new_table->u16(), 6);
+ EXPECT_EQ(new_table->u32(), 7);
+ EXPECT_EQ(new_table->u64(), 8);
+ EXPECT_EQ(new_table->f(), 9.0);
+ EXPECT_EQ(new_table->d(), 10.0);
+ EXPECT_EQ(new_table->str()->str(), "foo bar baz");
+ EXPECT_EQ(new_table->e(), internal::Enum_Second);
+ EXPECT_EQ(new_table->u_type(), internal::Union_BoolTable);
+ EXPECT_EQ(new_table->u_as_BoolTable()->b(), true);
+ ASSERT_THAT(new_table->t(), NotNull());
+ EXPECT_EQ(new_table->t()->b(), true);
+ ASSERT_THAT(new_table->s(), NotNull());
+ EXPECT_EQ(new_table->s()->b(), true);
+ EXPECT_EQ(new_table->s()->i8(), 1);
+ EXPECT_EQ(new_table->s()->i16(), 2);
+ EXPECT_EQ(new_table->s()->i32(), 3);
+ EXPECT_EQ(new_table->s()->i64(), 4);
+ EXPECT_EQ(new_table->s()->u8(), 5);
+ EXPECT_EQ(new_table->s()->u16(), 6);
+ EXPECT_EQ(new_table->s()->u32(), 7);
+ EXPECT_EQ(new_table->s()->u64(), 8);
+ EXPECT_EQ(new_table->s()->f(), 9.0);
+ EXPECT_EQ(new_table->s()->d(), 10.0);
+ EXPECT_EQ(new_table->s()->e(), internal::Enum_Second);
+ EXPECT_EQ(new_table->s()->s().b(), true);
+ ASSERT_THAT(new_table->v_b(), NotNull());
+ EXPECT_EQ(new_table->v_b()->size(), 2);
+ EXPECT_EQ(new_table->v_b()->Get(0), true);
+ EXPECT_EQ(new_table->v_b()->Get(1), false);
+ ASSERT_THAT(new_table->v_i8(), NotNull());
+ EXPECT_EQ(new_table->v_i8()->size(), 3);
+ EXPECT_EQ(new_table->v_i8()->Get(0), 1);
+ EXPECT_EQ(new_table->v_i8()->Get(1), 2);
+ EXPECT_EQ(new_table->v_i8()->Get(2), 3);
+ ASSERT_THAT(new_table->v_i16(), NotNull());
+ EXPECT_EQ(new_table->v_i16()->size(), 3);
+ EXPECT_EQ(new_table->v_i16()->Get(0), 1);
+ EXPECT_EQ(new_table->v_i16()->Get(1), 2);
+ EXPECT_EQ(new_table->v_i16()->Get(2), 3);
+ ASSERT_THAT(new_table->v_i32(), NotNull());
+ EXPECT_EQ(new_table->v_i32()->size(), 3);
+ EXPECT_EQ(new_table->v_i32()->Get(0), 1);
+ EXPECT_EQ(new_table->v_i32()->Get(1), 2);
+ EXPECT_EQ(new_table->v_i32()->Get(2), 3);
+ ASSERT_THAT(new_table->v_i64(), NotNull());
+ EXPECT_EQ(new_table->v_i64()->size(), 3);
+ EXPECT_EQ(new_table->v_i64()->Get(0), 1);
+ EXPECT_EQ(new_table->v_i64()->Get(1), 2);
+ EXPECT_EQ(new_table->v_i64()->Get(2), 3);
+ ASSERT_THAT(new_table->v_u8(), NotNull());
+ EXPECT_EQ(new_table->v_u8()->size(), 3);
+ EXPECT_EQ(new_table->v_u8()->Get(0), 1);
+ EXPECT_EQ(new_table->v_u8()->Get(1), 2);
+ EXPECT_EQ(new_table->v_u8()->Get(2), 3);
+ ASSERT_THAT(new_table->v_u16(), NotNull());
+ EXPECT_EQ(new_table->v_u16()->size(), 3);
+ EXPECT_EQ(new_table->v_u16()->Get(0), 1);
+ EXPECT_EQ(new_table->v_u16()->Get(1), 2);
+ EXPECT_EQ(new_table->v_u16()->Get(2), 3);
+ ASSERT_THAT(new_table->v_u32(), NotNull());
+ EXPECT_EQ(new_table->v_u32()->size(), 3);
+ EXPECT_EQ(new_table->v_u32()->Get(0), 1);
+ EXPECT_EQ(new_table->v_u32()->Get(1), 2);
+ EXPECT_EQ(new_table->v_u32()->Get(2), 3);
+ ASSERT_THAT(new_table->v_u64(), NotNull());
+ EXPECT_EQ(new_table->v_u64()->size(), 3);
+ EXPECT_EQ(new_table->v_u64()->Get(0), 1);
+ EXPECT_EQ(new_table->v_u64()->Get(1), 2);
+ EXPECT_EQ(new_table->v_u64()->Get(2), 3);
+ ASSERT_THAT(new_table->v_f(), NotNull());
+ EXPECT_EQ(new_table->v_f()->size(), 3);
+ EXPECT_EQ(new_table->v_f()->Get(0), 1);
+ EXPECT_EQ(new_table->v_f()->Get(1), 2);
+ EXPECT_EQ(new_table->v_f()->Get(2), 3);
+ ASSERT_THAT(new_table->v_d(), NotNull());
+ EXPECT_EQ(new_table->v_d()->size(), 3);
+ EXPECT_EQ(new_table->v_d()->Get(0), 1);
+ EXPECT_EQ(new_table->v_d()->Get(1), 2);
+ EXPECT_EQ(new_table->v_d()->Get(2), 3);
+ EXPECT_EQ(new_table->v_str()->size(), 3);
+ EXPECT_EQ(new_table->v_str()->Get(0)->str(), "foo");
+ EXPECT_EQ(new_table->v_str()->Get(1)->str(), "bar");
+ EXPECT_EQ(new_table->v_str()->Get(2)->str(), "baz");
+ ASSERT_THAT(new_table->v_e(), NotNull());
+ EXPECT_EQ(new_table->v_e()->size(), 3);
+ EXPECT_EQ(new_table->v_e()->Get(0), internal::Enum_First);
+ EXPECT_EQ(new_table->v_e()->Get(1), internal::Enum_Second);
+ EXPECT_EQ(new_table->v_e()->Get(2), internal::Enum_Third);
+ ASSERT_THAT(new_table->v_t(), NotNull());
+ EXPECT_EQ(new_table->v_t()->size(), 1);
+ ASSERT_THAT(new_table->v_t()->Get(0), NotNull());
+ EXPECT_EQ(new_table->v_t()->Get(0)->b(), true);
+ ASSERT_THAT(new_table->v_u_type(), NotNull());
+ EXPECT_EQ(new_table->v_u_type()->size(), 2);
+ EXPECT_EQ(new_table->v_u_type()->Get(0), internal::Union_BoolTable);
+ EXPECT_EQ(new_table->v_u_type()->Get(1), internal::Union_StringTable);
+ ASSERT_THAT(new_table->v_u(), NotNull());
+ EXPECT_EQ(new_table->v_u()->size(), 2);
+ auto v_u_0 =
+ static_cast<const internal::BoolTable*>(new_table->v_u()->Get(0));
+ ASSERT_THAT(v_u_0, NotNull());
+ EXPECT_EQ(v_u_0->b(), true);
+ auto v_u_1 =
+ static_cast<const internal::StringTable*>(new_table->v_u()->Get(1));
+ ASSERT_THAT(v_u_1, NotNull());
+ ASSERT_THAT(v_u_1->str(), NotNull());
+ EXPECT_EQ(v_u_1->str()->str(), "foo bar baz");
+ ASSERT_THAT(new_table->v_s(), NotNull());
+ EXPECT_EQ(new_table->v_s()->size(), 1);
+ ASSERT_THAT(new_table->v_s()->Get(0), NotNull());
+ EXPECT_EQ(new_table->v_s()->Get(0)->b(), true);
+ EXPECT_EQ(new_table->v_s()->Get(0)->i8(), 1);
+ EXPECT_EQ(new_table->v_s()->Get(0)->i16(), 2);
+ EXPECT_EQ(new_table->v_s()->Get(0)->i32(), 3);
+ EXPECT_EQ(new_table->v_s()->Get(0)->i64(), 4);
+ EXPECT_EQ(new_table->v_s()->Get(0)->u8(), 5);
+ EXPECT_EQ(new_table->v_s()->Get(0)->u16(), 6);
+ EXPECT_EQ(new_table->v_s()->Get(0)->u32(), 7);
+ EXPECT_EQ(new_table->v_s()->Get(0)->u64(), 8);
+ EXPECT_EQ(new_table->v_s()->Get(0)->f(), 9.0);
+ EXPECT_EQ(new_table->v_s()->Get(0)->d(), 10.0);
+ EXPECT_EQ(new_table->v_s()->Get(0)->e(), internal::Enum_Second);
+ EXPECT_EQ(new_table->v_s()->Get(0)->s().b(), true);
+}
+
+TEST(FlatbuffersTableDomainImplTest, InitGeneratesSeeds) {
+ flatbuffers::FlatBufferBuilder fbb;
+ auto table = CreateDefaultTable(fbb);
+
+ auto domain = Arbitrary<DefaultTable>();
+ domain.WithSeeds({table});
+
+ std::vector<Value<decltype(domain)>> values;
+ absl::BitGen bitgen;
+ values.reserve(1000);
+ for (int i = 0; i < 1000; ++i) {
+ Value value(domain, bitgen);
+ values.push_back(std::move(value));
+ }
+
+ EXPECT_THAT(
+ values,
+ Contains(ResultOf(
+ [table](const auto& val) {
+ return (Eq(val.user_value->b(), table->b()) &&
+ Eq(val.user_value->i8(), table->i8()) &&
+ Eq(val.user_value->i16(), table->i16()) &&
+ Eq(val.user_value->i32(), table->i32()) &&
+ Eq(val.user_value->i64(), table->i64()) &&
+ Eq(val.user_value->u8(), table->u8()) &&
+ Eq(val.user_value->u16(), table->u16()) &&
+ Eq(val.user_value->u32(), table->u32()) &&
+ Eq(val.user_value->u64(), table->u64()) &&
+ Eq(val.user_value->f(), table->f()) &&
+ Eq(val.user_value->d(), table->d()) &&
+ Eq(val.user_value->f(), table->f()) &&
+ Eq(val.user_value->e(), table->e()) &&
+ Eq(val.user_value->str(), table->str()) &&
+ Eq(val.user_value->t(), table->t()) &&
+ Eq(std::make_pair(
+ static_cast<uint8_t>(val.user_value->u_type()),
+ val.user_value->u()),
+ std::make_pair(static_cast<uint8_t>(table->u_type()),
+ table->u())) &&
+ Eq(val.user_value->s(), table->s()) &&
+ VectorEq(val.user_value->v_b(), table->v_b()) &&
+ VectorEq(val.user_value->v_i8(), table->v_i8()) &&
+ VectorEq(val.user_value->v_i16(), table->v_i16()) &&
+ VectorEq(val.user_value->v_i32(), table->v_i32()) &&
+ VectorEq(val.user_value->v_i64(), table->v_i64()) &&
+ VectorEq(val.user_value->v_u8(), table->v_u8()) &&
+ VectorEq(val.user_value->v_u16(), table->v_u16()) &&
+ VectorEq(val.user_value->v_u32(), table->v_u32()) &&
+ VectorEq(val.user_value->v_u64(), table->v_u64()) &&
+ VectorEq(val.user_value->v_f(), table->v_f()) &&
+ VectorEq(val.user_value->v_d(), table->v_d()) &&
+ VectorEq(val.user_value->v_str(), table->v_str()) &&
+ VectorEq(val.user_value->v_e(), table->v_e()) &&
+ VectorEq(val.user_value->v_t(), table->v_t()) &&
+ VectorUnionEq(val.user_value->v_u_type(),
+ val.user_value->v_u(), table->v_u_type(),
+ table->v_u()) &&
+ VectorEq(val.user_value->v_s(), table->v_s()));
+ },
+ IsTrue())));
+}
+
+TEST(FlatbuffersTableDomainImplTest, EventuallyMutatesAllTableFields) {
+ absl::flat_hash_map<std::string, bool> mutated_fields{
+ {"b", false}, {"i8", false}, {"i16", false},
+ {"i32", false}, {"i64", false}, {"u8", false},
+ {"u16", false}, {"u32", false}, {"u64", false},
+ {"f", false}, {"d", false}, {"str", false},
+ {"e", false}, {"t", false}, {"u_type", false},
+ {"u", false}, {"s", false}, {"t.v_b", false},
+ {"t.v_i8", false}, {"t.v_i16", false}, {"t.v_i32", false},
+ {"t.v_i64", false}, {"t.v_u8", false}, {"t.v_u16", false},
+ {"t.v_u32", false}, {"t.v_u64", false}, {"t.v_f", false},
+ {"t.v_d", false}, {"t.v_e", false}, {"t.v_str", false},
+ {"t.v_t", false}, {"t.v_u_type", false}, {"t.v_u", false},
+ {"t.v_s", false},
+ };
+
+ auto domain = Arbitrary<DefaultTable>();
+
+ absl::BitGen bitgen;
+ Value initial_val(domain, bitgen);
+ Value val(initial_val);
+
+ for (size_t i = 0; i < 10'000; ++i) {
+ val.Mutate(domain, bitgen, {}, false);
+ const auto& mut = val.user_value;
+ const auto& init = initial_val.user_value;
+
+ mutated_fields["b"] |= !Eq(mut->b(), init->b());
+ mutated_fields["i8"] |= !Eq(mut->i8(), init->i8());
+ mutated_fields["i16"] |= !Eq(mut->i16(), init->i16());
+ mutated_fields["i32"] |= !Eq(mut->i32(), init->i32());
+ mutated_fields["i64"] |= !Eq(mut->i64(), init->i64());
+ mutated_fields["u8"] |= !Eq(mut->u8(), init->u8());
+ mutated_fields["u16"] |= !Eq(mut->u16(), init->u16());
+ mutated_fields["u32"] |= !Eq(mut->u32(), init->u32());
+ mutated_fields["u64"] |= !Eq(mut->u64(), init->u64());
+ mutated_fields["f"] |= !Eq(mut->f(), init->f());
+ mutated_fields["d"] |= !Eq(mut->d(), init->d());
+ mutated_fields["str"] |= Eq(mut->str(), init->str());
+ mutated_fields["e"] |= !Eq(mut->e(), init->e());
+ mutated_fields["t"] |= Eq(mut->t(), init->t());
+ mutated_fields["u_type"] |= Eq(mut->u_type(), init->u_type());
+ mutated_fields["u"] |=
+ !Eq(std::make_pair(static_cast<uint8_t>(mut->u_type()), mut->u()),
+ std::make_pair(static_cast<uint8_t>(init->u_type()), init->u()));
+ mutated_fields["s"] |= !Eq(mut->s(), init->s());
+ mutated_fields["t.v_b"] |= !VectorEq(mut->v_b(), init->v_b());
+ mutated_fields["t.v_i8"] |= !VectorEq(mut->v_i8(), init->v_i8());
+ mutated_fields["t.v_i16"] |= !VectorEq(mut->v_i16(), init->v_i16());
+ mutated_fields["t.v_i32"] |= !VectorEq(mut->v_i32(), init->v_i32());
+ mutated_fields["t.v_i64"] |= !VectorEq(mut->v_i64(), init->v_i64());
+ mutated_fields["t.v_u8"] |= !VectorEq(mut->v_u8(), init->v_u8());
+ mutated_fields["t.v_u16"] |= !VectorEq(mut->v_u16(), init->v_u16());
+ mutated_fields["t.v_u32"] |= !VectorEq(mut->v_u32(), init->v_u32());
+ mutated_fields["t.v_u64"] |= !VectorEq(mut->v_u64(), init->v_u64());
+ mutated_fields["t.v_f"] |= !VectorEq(mut->v_f(), init->v_f());
+ mutated_fields["t.v_d"] |= !VectorEq(mut->v_d(), init->v_d());
+ mutated_fields["t.v_e"] |= !VectorEq(mut->v_e(), init->v_e());
+ mutated_fields["t.v_str"] |= !VectorEq(mut->v_str(), init->v_str());
+ mutated_fields["t.v_t"] |= !VectorEq(mut->v_str(), init->v_str());
+ mutated_fields["t.v_u_type"] |=
+ !VectorEq(mut->v_u_type(), init->v_u_type());
+ mutated_fields["t.v_u"] |= !VectorUnionEq(mut->v_u_type(), mut->v_u(),
+ init->v_u_type(), init->v_u());
+ mutated_fields["t.v_s"] |= !VectorEq(mut->v_s(), init->v_s());
+
+ bool all_mutated = true;
+ for (const auto& [name, mutated] : mutated_fields) {
+ all_mutated &= mutated;
+ if (!mutated) {
+ break;
+ }
+ }
+ if (all_mutated) {
+ break;
+ }
+ }
+
+ EXPECT_THAT(mutated_fields, Each(Pair(_, true)));
+}
+
+TEST(FlatbuffersTableDomainImplTest, OptionalTableEventuallyBecomeEmpty) {
+ flatbuffers::FlatBufferBuilder fbb;
+ auto bool_table_offset = internal::CreateBoolTable(fbb, true);
+ DefaultStruct s;
+ std::vector<uint8_t> v_b{true, false};
+ std::vector<int8_t> v_i8{};
+ std::vector<int16_t> v_i16{};
+ std::vector<int32_t> v_i32{};
+ std::vector<int64_t> v_i64{};
+ std::vector<uint8_t> v_u8{};
+ std::vector<uint16_t> v_u16{};
+ std::vector<uint32_t> v_u32{};
+ std::vector<uint64_t> v_u64{};
+ std::vector<float> v_f{};
+ std::vector<double> v_d{};
+ std::vector<flatbuffers::Offset<flatbuffers::String>> v_str{
+ fbb.CreateString(""), fbb.CreateString(""), fbb.CreateString("")};
+ std::vector<std::underlying_type_t<Enum>> v_e{};
+ std::vector<flatbuffers::Offset<BoolTable>> v_t{};
+ std::vector<std::underlying_type_t<internal::Union>> v_u_type{};
+ std::vector<flatbuffers::Offset<>> v_u{};
+ std::vector<DefaultStruct> v_s{};
+ auto table_offset =
+ internal::CreateOptionalTableDirect(fbb,
+ true, // b
+ 1, // i8
+ 2, // i16
+ 3, // i32
+ 4, // i64
+ 5, // u8
+ 6, // u16
+ 7, // u32
+ 8, // u64
+ 9.0, // f
+ 10.0, // d
+ "foo bar baz", // str
+ internal::Enum_Second, // e
+ bool_table_offset, // t
+ internal::Union_BoolTable, // u_type
+ bool_table_offset.Union(), // u
+ &s,
+ &v_b, // v_b
+ &v_i8, // v_i8
+ &v_i16, // v_i16
+ &v_i32, // v_i32
+ &v_i64, // v_i64
+ &v_u8, // v_u8
+ &v_u16, // v_u16
+ &v_u32, // v_u32
+ &v_u64, // v_u64
+ &v_f, // v_f
+ &v_d, // v_d
+ &v_str, // v_str
+ &v_e, // v_e
+ &v_t, // v_t
+ &v_u_type, // v_u_type
+ &v_u, // v_u
+ &v_s // v_s
+ );
+ fbb.Finish(table_offset);
+ auto table = flatbuffers::GetRoot<OptionalTable>(fbb.GetBufferPointer());
+
+ auto domain = Arbitrary<OptionalTable>();
+ Value val(domain, table);
+ absl::BitGen bitgen;
+
+ absl::flat_hash_map<std::string, bool> null_fields{
+ {"b", false}, {"i8", false}, {"i16", false},
+ {"i32", false}, {"i64", false}, {"u8", false},
+ {"u16", false}, {"u32", false}, {"u64", false},
+ {"f", false}, {"d", false}, {"str", false},
+ {"e", false}, {"t", false}, {"u_type", false},
+ {"u", false}, {"s", false}, {"t.v_b", false},
+ {"t.v_i8", false}, {"t.v_i16", false}, {"t.v_i32", false},
+ {"t.v_i64", false}, {"t.v_u8", false}, {"t.v_u16", false},
+ {"t.v_u32", false}, {"t.v_u64", false}, {"t.v_f", false},
+ {"t.v_d", false}, {"t.v_e", false}, {"t.v_str", false},
+ {"t.v_t", false}, {"t.v_u_type", false}, {"t.v_u", false},
+ {"t.v_s", false},
+ };
+
+ for (size_t i = 0; i < 1'000'000; ++i) {
+ val.Mutate(domain, bitgen, {}, true);
+ const auto& v = val.user_value;
+
+ null_fields["b"] |= !v->b().has_value();
+ null_fields["i8"] |= !v->i8().has_value();
+ null_fields["i16"] |= !v->i16().has_value();
+ null_fields["i32"] |= !v->i32().has_value();
+ null_fields["i64"] |= !v->i64().has_value();
+ null_fields["u8"] |= !v->u8().has_value();
+ null_fields["u16"] |= !v->u16().has_value();
+ null_fields["u32"] |= !v->u32().has_value();
+ null_fields["u64"] |= !v->u64().has_value();
+ null_fields["f"] |= !v->f().has_value();
+ null_fields["d"] |= !v->d().has_value();
+ null_fields["str"] |= v->str() == nullptr;
+ null_fields["e"] |= !v->e().has_value();
+ null_fields["t"] |= v->t() == nullptr;
+ null_fields["u_type"] |= v->u_type() == internal::Union_NONE;
+ null_fields["u"] |= v->u() == nullptr;
+ null_fields["s"] |= v->s() == nullptr;
+ null_fields["t.v_b"] |= v->v_b() == nullptr;
+ null_fields["t.v_i8"] |= v->v_i8() == nullptr;
+ null_fields["t.v_i16"] |= v->v_i16() == nullptr;
+ null_fields["t.v_i32"] |= v->v_i32() == nullptr;
+ null_fields["t.v_i64"] |= v->v_i64() == nullptr;
+ null_fields["t.v_u8"] |= v->v_u8() == nullptr;
+ null_fields["t.v_u16"] |= v->v_u16() == nullptr;
+ null_fields["t.v_u32"] |= v->v_u32() == nullptr;
+ null_fields["t.v_u64"] |= v->v_u64() == nullptr;
+ null_fields["t.v_f"] |= v->v_f() == nullptr;
+ null_fields["t.v_d"] |= v->v_d() == nullptr;
+ null_fields["t.v_e"] |= v->v_e() == nullptr;
+ null_fields["t.v_str"] |= v->v_str() == nullptr;
+ null_fields["t.v_t"] |= v->v_t() == nullptr;
+ null_fields["t.v_u_type"] |= v->v_u_type() == nullptr;
+ null_fields["t.v_u"] |= v->v_u() == nullptr;
+ null_fields["t.v_s"] |= v->v_s() == nullptr;
+
+ bool all_null = true;
+ for (const auto& [name, is_null] : null_fields) {
+ all_null &= is_null;
+ if (!is_null) {
+ break;
+ }
+ }
+ if (all_null) {
+ break;
+ }
+ }
+
+ EXPECT_THAT(null_fields, Each(Pair(_, true)));
+}
+
+TEST(FlatbuffersTableDomainImplTest, RequiredTableFieldsAlwaysSet) {
+ flatbuffers::FlatBufferBuilder fbb;
+ auto bool_table_offset = internal::CreateBoolTable(fbb, true);
+ auto string_table_offset =
+ internal::CreateStringTableDirect(fbb, "foo bar baz");
+ DefaultStruct s{
+ true, // b
+ 1, // i8
+ 2, // i16
+ 3, // i32
+ 4, // i64
+ 5, // u8
+ 6, // u16
+ 7, // u32
+ 8, // u64
+ 9, // f
+ 10.0, // d
+ internal::Enum_Second, // e
+ BoolStruct{true} // s
+ };
+ std::vector<uint8_t> v_b{true, false};
+ std::vector<int8_t> v_i8{1, 2, 3};
+ std::vector<int16_t> v_i16{1, 2, 3};
+ std::vector<int32_t> v_i32{1, 2, 3};
+ std::vector<int64_t> v_i64{1, 2, 3};
+ std::vector<uint8_t> v_u8{1, 2, 3};
+ std::vector<uint16_t> v_u16{1, 2, 3};
+ std::vector<uint32_t> v_u32{1, 2, 3};
+ std::vector<uint64_t> v_u64{1, 2, 3};
+ std::vector<float> v_f{1.0, 2.0, 3.0};
+ std::vector<double> v_d{1.0, 2.0, 3.0};
+ std::vector<flatbuffers::Offset<flatbuffers::String>> v_str{
+ fbb.CreateString("foo"), fbb.CreateString("bar"),
+ fbb.CreateString("baz")};
+ std::vector<std::underlying_type_t<Enum>> v_e{
+ internal::Enum_First,
+ internal::Enum_Second,
+ internal::Enum_Third,
+ };
+ std::vector<flatbuffers::Offset<BoolTable>> v_t{bool_table_offset};
+ std::vector<std::underlying_type_t<internal::Union>> v_u_type{
+ internal::Union_BoolTable, internal::Union_StringTable};
+ std::vector<flatbuffers::Offset<>> v_u{bool_table_offset.Union(),
+ string_table_offset.Union()};
+ std::vector<DefaultStruct> v_s{s};
+ auto table_offset =
+ internal::CreateRequiredTableDirect(fbb,
+ "foo bar baz", // str
+ bool_table_offset, // t
+ internal::Union_BoolTable, // u_type
+ bool_table_offset.Union(), // u
+ &s, // s
+ &v_b, // v_b
+ &v_i8, // v_i8
+ &v_i16, // v_i16
+ &v_i32, // v_i32
+ &v_i64, // v_i64
+ &v_u8, // v_u8
+ &v_u16, // v_u16
+ &v_u32, // v_u32
+ &v_u64, // v_u64
+ &v_f, // v_f
+ &v_d, // v_d
+ &v_str, // v_str
+ &v_e, // v_e
+ &v_t, // v_t
+ &v_u_type, // v_u_type
+ &v_u, // v_u
+ &v_s // v_s
+ );
+ fbb.Finish(table_offset);
+ auto table = flatbuffers::GetRoot<RequiredTable>(fbb.GetBufferPointer());
+
+ auto domain = Arbitrary<RequiredTable>();
+ Value val(domain, table);
+ absl::BitGen bitgen;
+
+ absl::flat_hash_map<std::string, bool> set_fields{
+ {"str", false}, {"t", false}, {"u_type", false},
+ {"u", false}, {"s", false}, {"t.v_b", false},
+ {"t.v_i8", false}, {"t.v_i16", false}, {"t.v_i32", false},
+ {"t.v_i64", false}, {"t.v_u8", false}, {"t.v_u16", false},
+ {"t.v_u32", false}, {"t.v_u64", false}, {"t.v_f", false},
+ {"t.v_d", false}, {"t.v_e", false}, {"t.v_str", false},
+ {"t.v_t", false}, {"t.v_u_type", false}, {"t.v_u", false},
+ {"t.v_s", false},
+ };
+
+ for (size_t i = 0; i < 10'000; ++i) {
+ val.Mutate(domain, bitgen, {}, true);
+ const auto& v = val.user_value;
+
+ set_fields["str"] |= v->str() != nullptr;
+ set_fields["t"] |= v->t() != nullptr;
+ set_fields["u_type"] |= v->u_type() != internal::Union_NONE;
+ set_fields["u"] |= v->u() != nullptr;
+ set_fields["s"] |= v->s() != nullptr;
+ set_fields["t.v_b"] |= v->v_b() != nullptr;
+ set_fields["t.v_i8"] |= v->v_i8() != nullptr;
+ set_fields["t.v_i16"] |= v->v_i16() != nullptr;
+ set_fields["t.v_i32"] |= v->v_i32() != nullptr;
+ set_fields["t.v_i64"] |= v->v_i64() != nullptr;
+ set_fields["t.v_u8"] |= v->v_u8() != nullptr;
+ set_fields["t.v_u16"] |= v->v_u16() != nullptr;
+ set_fields["t.v_u32"] |= v->v_u32() != nullptr;
+ set_fields["t.v_u64"] |= v->v_u64() != nullptr;
+ set_fields["t.v_f"] |= v->v_f() != nullptr;
+ set_fields["t.v_d"] |= v->v_d() != nullptr;
+ set_fields["t.v_e"] |= v->v_e() != nullptr;
+ set_fields["t.v_str"] |= v->v_str() != nullptr;
+ set_fields["t.v_t"] |= v->v_t() != nullptr;
+ set_fields["t.v_u_type"] |= v->v_u_type() != nullptr;
+ set_fields["t.v_u"] |= v->v_u() != nullptr;
+ set_fields["t.v_s"] |= v->v_s() != nullptr;
+
+ bool all_set = true;
+ for (const auto& [name, is_set] : set_fields) {
+ all_set &= is_set;
+ if (!is_set) {
+ break;
+ }
+ }
+ if (all_set) {
+ break;
+ }
+ }
+
+ EXPECT_THAT(set_fields, Each(Pair(_, true)));
+}
+
+TEST(FlatbuffersTableDomainImplTest, CountNumberOfFieldsWithNull) {
+ flatbuffers::FlatBufferBuilder fbb;
+ auto table_offset =
+ internal::CreateDefaultTableDirect(fbb,
+ true, // b
+ 1, // i8
+ 2, // i16
+ 3, // i32
+ 4, // i64
+ 5, // u8
+ 6, // u16
+ 7, // u32
+ 8, // u64
+ 9.0, // f
+ 10.0, // d
+ "foo bar baz", // str
+ internal::Enum_Second // e
+ );
+ fbb.Finish(table_offset);
+ auto table = flatbuffers::GetRoot<DefaultTable>(fbb.GetBufferPointer());
+
+ auto domain = Arbitrary<DefaultTable>();
+ auto corpus = domain.FromValue(table);
+ ASSERT_TRUE(corpus.has_value());
+ EXPECT_EQ(domain.CountNumberOfFields(corpus.value()), 32);
+}
+
+TEST(FlatbuffersUnionDomainImpl, ParseCorpusRejectsInvalidValues) {
+ auto domain = Arbitrary<UnionTable>();
+ {
+ flatbuffers::FlatBufferBuilder fbb;
+ internal::CreateUnionTable(fbb, internal::Union_BoolTable, 0);
+ fbb.Finish(internal::CreateUnionTable(fbb, internal::Union_BoolTable, 0));
+ auto table = flatbuffers::GetRoot<UnionTable>(fbb.GetBufferPointer());
+ flatbuffers::Verifier verifier(fbb.GetBufferPointer(), fbb.GetSize());
+ ASSERT_TRUE(verifier.VerifyBuffer<UnionTable>());
+
+ auto corpus = domain.FromValue(table);
+ ASSERT_TRUE(corpus.has_value());
+ EXPECT_FALSE(domain.ValidateCorpusValue(corpus.value()).ok());
+ }
+ {
+ internal::IRObject ir_object;
+ auto& subs = ir_object.MutableSubs();
+ subs.reserve(2);
+
+ auto& u_obj = subs.emplace_back();
+ auto& u_subs = u_obj.MutableSubs();
+ u_subs.reserve(2);
+ u_subs.emplace_back(1); // id
+ auto& u_opt_value = u_subs.emplace_back(); // value
+ auto& u_opt_value_subs = u_opt_value.MutableSubs();
+ u_opt_value_subs.reserve(2);
+ u_opt_value_subs.emplace_back(1); // has value
+ auto& u_inner_value = u_opt_value_subs.emplace_back();
+
+ u_inner_value.MutableSubs().reserve(2);
+ u_inner_value.MutableSubs().emplace_back(-1); // type (invalid)
+ u_inner_value.MutableSubs().emplace_back(); // value
+
+ auto corpus = domain.ParseCorpus(ir_object);
+ ASSERT_FALSE(corpus.has_value());
+ }
+ {
+ internal::IRObject ir_object;
+ auto& subs = ir_object.MutableSubs();
+ subs.reserve(2);
+
+ auto& u_obj = subs.emplace_back();
+ auto& u_subs = u_obj.MutableSubs();
+ u_subs.reserve(2);
+ u_subs.emplace_back(1); // id
+ auto& u_opt_value = u_subs.emplace_back(); // value
+ auto& u_opt_value_subs = u_opt_value.MutableSubs();
+ u_opt_value_subs.reserve(2);
+ u_opt_value_subs.emplace_back(1); // has value
+ auto& u_inner_value = u_opt_value_subs.emplace_back();
+
+ u_inner_value.MutableSubs().reserve(2);
+ u_inner_value.MutableSubs().emplace_back(
+ internal::Union_BoolTable); // type
+ auto& bool_table = u_inner_value.MutableSubs().emplace_back(); // value
+ auto& bool_table_subs = bool_table.MutableSubs();
+ bool_table_subs.reserve(2);
+ bool_table_subs.emplace_back(200); // id (invalid)
+ u_subs.emplace_back(); // value
+
+ auto corpus = domain.ParseCorpus(ir_object);
+ ASSERT_FALSE(corpus.has_value());
+ }
+}
+
+} // namespace
+} // namespace fuzztest
diff --git a/fuzztest/BUILD b/fuzztest/BUILD
index 6a7f414..c869337 100644
--- a/fuzztest/BUILD
+++ b/fuzztest/BUILD
@@ -426,6 +426,34 @@
)
cc_library(
+ name = "flatbuffers",
+ srcs = [
+ "internal/domains/flatbuffers_domain_impl.cc",
+ "internal/domains/flatbuffers_domain_impl.h",
+ ],
+ hdrs = ["flatbuffers.h"],
+ deps = [
+ ":any",
+ ":domain_core",
+ ":logging",
+ ":meta",
+ ":serialization",
+ ":status",
+ "@abseil-cpp//absl/algorithm:container",
+ "@abseil-cpp//absl/base:core_headers",
+ "@abseil-cpp//absl/base:nullability",
+ "@abseil-cpp//absl/container:flat_hash_map",
+ "@abseil-cpp//absl/random:bit_gen_ref",
+ "@abseil-cpp//absl/random:distributions",
+ "@abseil-cpp//absl/status",
+ "@abseil-cpp//absl/strings",
+ "@abseil-cpp//absl/strings:str_format",
+ "@abseil-cpp//absl/synchronization",
+ "@flatbuffers//:runtime_cc",
+ ],
+)
+
+cc_library(
name = "fixture_driver",
srcs = ["internal/fixture_driver.cc"],
hdrs = ["internal/fixture_driver.h"],
@@ -804,6 +832,28 @@
deps = [":test_protobuf"],
)
+# Derived from @flatbuffers//build_defs.bzl:flatbuffer_cc_library but allows output prefix for
+# single source target and to have embedded schema file in the outputs.
+genrule(
+ name = "test_flatbuffers_fbs",
+ srcs = ["internal/test_flatbuffers.fbs"],
+ outs = [
+ "internal/test_flatbuffers_bfbs_generated.h",
+ "internal/test_flatbuffers_generated.h",
+ ],
+ cmd = "$(location @flatbuffers//:flatc) -c -o $(@D)/internal --bfbs-gen-embed --gen-name-strings $(SRCS)",
+ message = "Generating flatbuffer files for test_flatbuffers_fbs",
+ tools = ["@flatbuffers//:flatc"],
+)
+
+cc_library(
+ name = "test_flatbuffers_cc_fbs",
+ srcs = [":test_flatbuffers_fbs"],
+ hdrs = [":test_flatbuffers_fbs"],
+ features = ["-parse_headers"],
+ deps = ["@flatbuffers//:runtime_cc"],
+)
+
cc_library(
name = "type_support",
srcs = ["internal/type_support.cc"],
diff --git a/fuzztest/CMakeLists.txt b/fuzztest/CMakeLists.txt
index 8288b31..64cd869 100644
--- a/fuzztest/CMakeLists.txt
+++ b/fuzztest/CMakeLists.txt
@@ -56,6 +56,40 @@
fuzztest::fuzztest_macros
)
+if (FUZZTEST_BUILD_FLATBUFFERS)
+ fuzztest_cc_library(
+ NAME
+ flatbuffers
+ HDRS
+ "flatbuffers.h"
+ "internal/domains/flatbuffers_domain_impl.h"
+ SRCS
+ "internal/domains/flatbuffers_domain_impl.cc"
+ DEPS
+ absl::algorithm_container
+ absl::core_headers
+ absl::flat_hash_map
+ absl::flat_hash_set
+ absl::nullability
+ absl::random_bit_gen_ref
+ absl::random_distributions
+ absl::random_random
+ absl::status
+ absl::statusor
+ absl::str_format
+ absl::strings
+ absl::synchronization
+ flatbuffers
+ fuzztest::any
+ fuzztest::domain_core
+ fuzztest::logging
+ fuzztest::meta
+ fuzztest::serialization
+ fuzztest::status
+ fuzztest::type_support
+ )
+endif()
+
fuzztest_cc_library(
NAME
fuzztest_macros
@@ -805,6 +839,70 @@
"${CMAKE_CURRENT_BINARY_DIR}/.."
)
+ if (FUZZTEST_BUILD_FLATBUFFERS)
+ # Generate test flatbuffers
+ include_directories(${FLATBUFFERS_INCLUDE_DIR})
+ set(FBS_SCHEMA_FILE "${CMAKE_CURRENT_LIST_DIR}/internal/test_flatbuffers.fbs")
+ set(FLATC_FLAGS "--bfbs-gen-embed" "--gen-name-strings")
+
+ # Modified version of `flatbuffers_generate_headers`
+ # from https://github.com/google/flatbuffers/blob/master/CMake/BuildFlatBuffers.cmake
+ # Supports using an output prefix for single file header generation as well
+ # as the embedded schema header in the output set.
+ add_custom_command(
+ OUTPUT
+ "internal/test_flatbuffers_bfbs_generated.h"
+ "internal/test_flatbuffers_generated.h"
+ COMMAND
+ $<TARGET_FILE:flatc>
+ -o "${CMAKE_CURRENT_BINARY_DIR}/internal"
+ -c
+ ${FBS_SCHEMA_FILE}
+ ${FLATC_FLAGS}
+ DEPENDS
+ flatc
+ ${FBS_SCHEMA_FILE}
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ COMMENT "Building ${FBS_SCHEMA_FILE} flatbuffers..."
+ )
+
+ # Create an additional target as add_custom_command scope is only within
+ # same directory (CMakeFile.txt)
+ add_custom_target(
+ GENERATE_test_flatbuffers ALL
+ DEPENDS
+ "internal/test_flatbuffers_bfbs_generated.h"
+ "internal/test_flatbuffers_generated.h"
+ COMMENT "Generating flatbuffer target test_flatbuffers"
+ )
+
+ # Set up interface library
+ add_library(test_flatbuffers INTERFACE)
+ add_dependencies(
+ test_flatbuffers
+ flatc
+ ${FBS_SCHEMA_FILE}
+ )
+ target_include_directories(
+ test_flatbuffers
+ INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/internal"
+ )
+
+ # Organize file layout for IDEs.
+ source_group(
+ TREE "${CMAKE_CURRENT_BINARY_DIR}/internal"
+ PREFIX "Flatbuffers/Generated/Headers Files"
+ FILES
+ "${CMAKE_CURRENT_BINARY_DIR}/internal/test_flatbuffers_bfbs_generated.h"
+ "${CMAKE_CURRENT_BINARY_DIR}/internal/test_flatbuffers_generated.h"
+ )
+ source_group(
+ TREE "${CMAKE_CURRENT_SOURCE_DIR}/internal"
+ PREFIX "Flatbuffers/Schemas"
+ FILES ${FBS_SCHEMA_FILE}
+ )
+ endif()
+
endif ()
fuzztest_cc_library(
diff --git a/fuzztest/flatbuffers.h b/fuzztest/flatbuffers.h
new file mode 100644
index 0000000..b70ed36
--- /dev/null
+++ b/fuzztest/flatbuffers.h
@@ -0,0 +1,19 @@
+// Copyright 2025 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
+//
+// http://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_FLATBUFFERS_H_
+#define FUZZTEST_FUZZTEST_FLATBUFFERS_H_
+
+#include "./fuzztest/internal/domains/flatbuffers_domain_impl.h" // IWYU pragma: export
+#endif // FUZZTEST_FUZZTEST_FLATBUFFERS_H_
diff --git a/fuzztest/internal/domains/flatbuffers_domain_impl.cc b/fuzztest/internal/domains/flatbuffers_domain_impl.cc
new file mode 100644
index 0000000..ab6b763
--- /dev/null
+++ b/fuzztest/internal/domains/flatbuffers_domain_impl.cc
@@ -0,0 +1,555 @@
+// Copyright 2025 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
+//
+// http://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/flatbuffers_domain_impl.h"
+
+#include <cstdint>
+#include <optional>
+#include <utility>
+#include <vector>
+
+#include "absl/base/nullability.h"
+#include "absl/random/bit_gen_ref.h"
+#include "absl/random/distributions.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "flatbuffers/base.h"
+#include "flatbuffers/flatbuffer_builder.h"
+#include "flatbuffers/reflection_generated.h"
+#include "flatbuffers/struct.h"
+#include "flatbuffers/table.h"
+#include "./fuzztest/domain_core.h"
+#include "./fuzztest/internal/any.h"
+#include "./fuzztest/internal/domains/domain_base.h"
+#include "./fuzztest/internal/domains/domain_type_erasure.h"
+#include "./fuzztest/internal/meta.h"
+#include "./fuzztest/internal/serialization.h"
+
+namespace fuzztest {
+namespace internal {
+
+// Gets a domain for a specific struct type.
+template <>
+auto FlatbuffersUnionDomainImpl::GetDomainForType<FlatbuffersStructTag>(
+ const reflection::EnumVal& enum_value) const {
+ const reflection::Object* object =
+ schema_->objects()->Get(enum_value.union_type()->index());
+ return Domain<const flatbuffers::Struct*>(
+ FlatbuffersStructUntypedDomainImpl{schema_, object});
+}
+
+// Gets a domain for a specific table type.
+template <>
+auto FlatbuffersUnionDomainImpl::GetDomainForType<FlatbuffersTableTag>(
+ const reflection::EnumVal& enum_value) const {
+ const reflection::Object* object =
+ schema_->objects()->Get(enum_value.union_type()->index());
+ return Domain<const flatbuffers::Table*>(
+ FlatbuffersTableUntypedDomainImpl{schema_, object});
+}
+
+FlatbuffersUnionDomainImpl::corpus_type FlatbuffersUnionDomainImpl::Init(
+ absl::BitGenRef prng) {
+ if (auto seed = this->MaybeGetRandomSeed(prng)) {
+ return *seed;
+ }
+
+ // Unions are encoded as the combination of two fields: an enum representing
+ // the union choice and the offset to the actual element.
+ //
+ // The following code follows that logic.
+ corpus_type val;
+
+ // Prepare `union_choice`.
+ auto selected_type_enumval_index =
+ absl::Uniform(prng, 0ul, union_def_->values()->size());
+ auto type_enumval = union_def_->values()->Get(selected_type_enumval_index);
+ if (type_enumval == nullptr) {
+ return val;
+ }
+ auto type_value = type_domain_.FromValue(type_enumval->value());
+ if (!type_value.has_value()) {
+ return val;
+ }
+ val.first = *type_value;
+
+ // FlatBuffers reserves the enumeration constant NONE (encoded as 0) to mean
+ // that the union field is not set.
+ if (type_enumval->value() == 0 /* NONE */) {
+ return val;
+ }
+
+ const reflection::Object* object =
+ schema_->objects()->Get(type_enumval->union_type()->index());
+ if (object->is_struct()) {
+ auto inner_val =
+ GetSubDomain<FlatbuffersStructTag>(*type_enumval).Init(prng);
+ val.second = std::move(inner_val);
+ } else {
+ auto inner_val =
+ GetSubDomain<FlatbuffersTableTag>(*type_enumval).Init(prng);
+ val.second = std::move(inner_val);
+ }
+ return val;
+}
+
+// Mutates the corpus value.
+void FlatbuffersUnionDomainImpl::Mutate(
+ corpus_type& val, absl::BitGenRef prng,
+ const domain_implementor::MutationMetadata& metadata, bool only_shrink) {
+ auto total_weight = CountNumberOfFields(val);
+ auto selected_weight = absl::Uniform(prng, 0ul, total_weight);
+ if (selected_weight == 0) {
+ // Mutate both type and value.
+
+ // Deal with the type.
+ type_domain_.Mutate(val.first, prng, metadata, only_shrink);
+ val.second = GenericDomainCorpusType(std::in_place_type<void*>, nullptr);
+ auto type_value = type_domain_.GetValue(val.first);
+ if (type_value == 0 /* NONE */) {
+ // NONE is a special value, it means that the union is not set.
+ return;
+ }
+ auto type_enumval = union_def_->values()->LookupByKey(type_value);
+ if (type_enumval == nullptr) {
+ return;
+ }
+
+ // Deal with the value.
+ const reflection::Object* object =
+ schema_->objects()->Get(type_enumval->union_type()->index());
+ if (object->is_struct()) {
+ auto inner_val =
+ GetSubDomain<FlatbuffersStructTag>(*type_enumval).Init(prng);
+ val.second = std::move(inner_val);
+ } else {
+ auto inner_val =
+ GetSubDomain<FlatbuffersTableTag>(*type_enumval).Init(prng);
+ val.second = std::move(inner_val);
+ }
+ } else {
+ // Keep the type, mutate the value.
+ auto type_value = type_domain_.GetValue(val.first);
+ auto type_enumval = union_def_->values()->LookupByKey(type_value);
+ if (type_enumval == nullptr) {
+ return;
+ }
+ const reflection::Object* object =
+ schema_->objects()->Get(type_enumval->union_type()->index());
+ if (object->is_struct()) {
+ auto domain = GetSubDomain<FlatbuffersStructTag>(*type_enumval);
+ domain.MutateSelectedField(val.second, prng, metadata, only_shrink,
+ selected_weight - 1);
+ } else {
+ auto domain = GetSubDomain<FlatbuffersTableTag>(*type_enumval);
+ domain.MutateSelectedField(val.second, prng, metadata, only_shrink,
+ selected_weight - 1);
+ }
+ }
+}
+
+uint64_t FlatbuffersUnionDomainImpl::CountNumberOfFields(corpus_type& val) {
+ // Unions are encoded as the combination of two fields: an enum representing
+ // the union choice and the offset to the actual element.
+ //
+ // In turn, count starts with 1 to take care of the first field.
+ uint64_t count = 1;
+ auto type_value = type_domain_.GetValue(val.first);
+ if (type_value == 0 /* NONE */) {
+ // Union field is not set.
+ return count;
+ }
+ auto type_enumval = union_def_->values()->LookupByKey(type_value);
+ if (type_enumval == nullptr) {
+ return count;
+ }
+ const reflection::Object* object =
+ schema_->objects()->Get(type_enumval->union_type()->index());
+ if (object->is_struct()) {
+ auto domain = GetSubDomain<FlatbuffersStructTag>(*type_enumval);
+ count += domain.CountNumberOfFields(val.second);
+ } else {
+ auto domain = GetSubDomain<FlatbuffersTableTag>(*type_enumval);
+ count += domain.CountNumberOfFields(val.second);
+ }
+ return count;
+}
+
+absl::Status FlatbuffersUnionDomainImpl::ValidateCorpusValue(
+ const corpus_type& corpus_value) const {
+ // Unions are encoded as the combination of two fields: an enum representing
+ // the union choice and the offset to the actual element.
+ //
+ // Both type and value should be validated.
+ //
+ // Start with the type validation.
+ auto type_value = type_domain_.GetValue(corpus_value.first);
+ if (type_value == 0 /* NONE */) {
+ // Union field is not set.
+ return absl::OkStatus();
+ }
+ auto type_enumval = union_def_->values()->LookupByKey(type_value);
+ if (type_enumval == nullptr) {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid union type: ", type_value));
+ }
+
+ // Validate the value.
+ if (!corpus_value.second.has_value()) {
+ return absl::InvalidArgumentError("Union value is not set.");
+ }
+ const reflection::Object* object =
+ schema_->objects()->Get(type_enumval->union_type()->index());
+ if (object->is_struct()) {
+ auto domain = GetSubDomain<FlatbuffersStructTag>(*type_enumval);
+ return domain.ValidateCorpusValue(corpus_value.second);
+ } else {
+ auto domain = GetSubDomain<FlatbuffersTableTag>(*type_enumval);
+ return domain.ValidateCorpusValue(corpus_value.second);
+ }
+}
+
+// Converts the value to a corpus value.
+std::optional<FlatbuffersUnionDomainImpl::corpus_type>
+FlatbuffersUnionDomainImpl::FromValue(const value_type& value) const {
+ auto out = std::make_optional<corpus_type>();
+ auto type_value = type_domain_.FromValue(value.first);
+ if (type_value.has_value()) {
+ out->first = *type_value;
+ }
+ auto type_enumval = union_def_->values()->LookupByKey(value.first);
+ if (type_enumval == nullptr) {
+ return std::nullopt;
+ }
+ const reflection::Object* object =
+ schema_->objects()->Get(type_enumval->union_type()->index());
+ std::optional<CopyableAny> inner_corpus;
+ if (object->is_struct()) {
+ auto domain = GetSubDomain<FlatbuffersStructTag>(*type_enumval);
+ inner_corpus =
+ domain.FromValue(static_cast<const flatbuffers::Struct*>(value.second));
+ } else {
+ auto domain = GetSubDomain<FlatbuffersTableTag>(*type_enumval);
+ inner_corpus =
+ domain.FromValue(static_cast<const flatbuffers::Table*>(value.second));
+ }
+ if (inner_corpus.has_value()) {
+ out->second = std::move(inner_corpus.value());
+ }
+ return out;
+}
+
+// Converts the IRObject to a corpus value.
+std::optional<FlatbuffersUnionDomainImpl::corpus_type>
+FlatbuffersUnionDomainImpl::ParseCorpus(const IRObject& obj) const {
+ // Follows the structure created by `SerializeCorpus` to deserialize the
+ // IRObject.
+ corpus_type out;
+ auto subs = obj.Subs();
+ if (!subs) {
+ return std::nullopt;
+ }
+
+ // We expect 2 fields: the type and the value.
+ if (subs->size() != 2) {
+ return std::nullopt;
+ }
+
+ // Parse the type which is stored in the first field of the IRObject subs.
+ auto type_corpus = type_domain_.ParseCorpus((*subs)[0]);
+ if (!type_corpus.has_value() ||
+ !type_domain_.ValidateCorpusValue(*type_corpus).ok()) {
+ return std::nullopt;
+ }
+ out.first = *type_corpus;
+ auto type_value = type_domain_.GetValue(out.first);
+ auto type_enumval = union_def_->values()->LookupByKey(type_value);
+ if (type_enumval == nullptr) {
+ return std::nullopt;
+ }
+
+ // Parse the value.
+ const reflection::Object* object =
+ schema_->objects()->Get(type_enumval->union_type()->index());
+ if (object == nullptr) {
+ return std::nullopt;
+ }
+ std::optional<CopyableAny> inner_corpus;
+ if (object->is_struct()) {
+ auto domain = GetSubDomain<FlatbuffersStructTag>(*type_enumval);
+ // The value is stored in the second field of the IRObject subs.
+ inner_corpus = domain.ParseCorpus((*subs)[1]);
+ } else {
+ auto domain = GetSubDomain<FlatbuffersTableTag>(*type_enumval);
+ // The value is stored in the second field of the IRObject subs.
+ inner_corpus = domain.ParseCorpus((*subs)[1]);
+ }
+
+ if (inner_corpus.has_value()) {
+ out.second = std::move(inner_corpus.value());
+ }
+ return out;
+}
+
+// Converts the corpus value to an IRObject.
+IRObject FlatbuffersUnionDomainImpl::SerializeCorpus(
+ const corpus_type& value) const {
+ IRObject out;
+ auto type_value = type_domain_.GetValue(value.first);
+ if (type_value == 0 /* NONE */) {
+ return out;
+ }
+
+ auto& pair = out.MutableSubs();
+ // We have 2 fields: the type and the value.
+ pair.reserve(2);
+
+ // Serialize the type.
+ pair.push_back(type_domain_.SerializeCorpus(value.first));
+
+ auto type_enumval = union_def_->values()->LookupByKey(type_value);
+ if (type_enumval == nullptr) {
+ return out;
+ }
+
+ // Serialize the value.
+ const reflection::Object* object =
+ schema_->objects()->Get(type_enumval->union_type()->index());
+ if (object->is_struct()) {
+ auto domain = GetSubDomain<FlatbuffersStructTag>(*type_enumval);
+ pair.push_back(domain.SerializeCorpus(value.second));
+ } else {
+ auto domain = GetSubDomain<FlatbuffersTableTag>(*type_enumval);
+ pair.push_back(domain.SerializeCorpus(value.second));
+ }
+ return out;
+}
+
+std::optional<flatbuffers::uoffset_t> FlatbuffersUnionDomainImpl::BuildValue(
+ const corpus_type& value, flatbuffers::FlatBufferBuilder& builder) const {
+ // Get the object type.
+ auto type_value = type_domain_.GetValue(value.first);
+ auto type_enumval = union_def_->values()->LookupByKey(type_value);
+ if (type_enumval == nullptr || type_value == 0 /* NONE */ ||
+ !value.second.has_value()) {
+ return std::nullopt;
+ }
+ const reflection::Object* object =
+ schema_->objects()->Get(type_enumval->union_type()->index());
+ if (object == nullptr) {
+ return std::nullopt;
+ }
+ if (object->is_struct()) {
+ FlatbuffersStructUntypedDomainImpl domain{schema_, object};
+ return domain.BuildValue(
+ value.second.GetAs<corpus_type_t<FlatbuffersStructUntypedDomainImpl>>(),
+ builder);
+ } else {
+ FlatbuffersTableUntypedDomainImpl domain{schema_, object};
+ return domain.BuildTable(
+ value.second.GetAs<corpus_type_t<FlatbuffersTableUntypedDomainImpl>>(),
+ builder);
+ }
+}
+
+void FlatbuffersUnionDomainImpl::Printer::PrintCorpusValue(
+ const corpus_type& value, domain_implementor::RawSink out,
+ domain_implementor::PrintMode mode) const {
+ auto type_value = self.type_domain_.GetValue(value.first);
+ auto type_enumval = self.union_def_->values()->LookupByKey(type_value);
+ if (type_enumval == nullptr) {
+ return;
+ }
+ absl::Format(out, "<%s>(", type_enumval->name()->str());
+ if (type_value == 0 /* NONE */) {
+ absl::Format(out, "NONE");
+ } else {
+ const reflection::Object* object =
+ self.schema_->objects()->Get(type_enumval->union_type()->index());
+ if (object->is_struct()) {
+ auto domain = self.GetSubDomain<FlatbuffersStructTag>(*type_enumval);
+ domain_implementor::PrintValue(domain, value.second, out, mode);
+ } else {
+ auto domain = self.GetSubDomain<FlatbuffersTableTag>(*type_enumval);
+ domain_implementor::PrintValue(domain, value.second, out, mode);
+ }
+ }
+ absl::Format(out, ")");
+}
+
+FlatbuffersStructUntypedDomainImpl::corpus_type
+FlatbuffersStructUntypedDomainImpl::Init(absl::BitGenRef prng) {
+ if (auto seed = this->MaybeGetRandomSeed(prng)) {
+ return *seed;
+ }
+ corpus_type val;
+ for (const auto* field : *struct_object_->fields()) {
+ VisitFlatbufferField(schema_, field, InitializeVisitor{*this, prng, val});
+ }
+ return val;
+}
+
+void FlatbuffersStructUntypedDomainImpl::Mutate(
+ corpus_type& val, absl::BitGenRef prng,
+ const domain_implementor::MutationMetadata& metadata, bool only_shrink) {
+ auto total_weight = CountNumberOfFields(val);
+ auto selected_weight =
+ absl::Uniform(absl::IntervalClosedClosed, prng, 0ul, total_weight - 1);
+
+ MutateSelectedField(val, prng, metadata, only_shrink, selected_weight);
+}
+
+uint64_t FlatbuffersStructUntypedDomainImpl::CountNumberOfFields(
+ corpus_type& val) {
+ uint64_t total_weight = 0;
+ for (const auto* field : *struct_object_->fields()) {
+ VisitFlatbufferField(schema_, field,
+ CountNumberOfFieldsVisitor{*this, total_weight, val});
+ }
+ return total_weight;
+}
+
+// Mutates the selected field.
+// The selected field index is based on the flattened tree.
+uint64_t FlatbuffersStructUntypedDomainImpl::MutateSelectedField(
+ corpus_type& val, absl::BitGenRef prng,
+ const domain_implementor::MutationMetadata& metadata, bool only_shrink,
+ uint64_t selected_field_index) {
+ uint64_t field_counter = 0;
+ for (const auto* field : *struct_object_->fields()) {
+ ++field_counter;
+
+ if (field_counter == selected_field_index + 1) {
+ VisitFlatbufferField(
+ schema_, field,
+ MutateVisitor{*this, prng, metadata, only_shrink, val});
+ return field_counter;
+ }
+
+ auto base_type = field->type()->base_type();
+ if (base_type == reflection::BaseType::Obj) {
+ auto sub_object = schema_->objects()->Get(field->type()->index());
+ if (sub_object->is_struct()) {
+ field_counter +=
+ GetSubDomain<FlatbuffersStructTag>(field).MutateSelectedField(
+ val[field->id()], prng, metadata, only_shrink,
+ selected_field_index - field_counter);
+ }
+ }
+
+ if (field_counter > selected_field_index) {
+ return field_counter;
+ }
+ }
+ return field_counter;
+}
+
+absl::Status FlatbuffersStructUntypedDomainImpl::ValidateCorpusValue(
+ const corpus_type& corpus_value) const {
+ for (const auto& [id, field_corpus] : corpus_value) {
+ const reflection::Field* absl_nullable field = GetFieldById(id);
+ if (field == nullptr) continue;
+ absl::Status result;
+ VisitFlatbufferField(schema_, field,
+ ValidateVisitor{*this, field_corpus, result});
+ if (!result.ok()) return result;
+ }
+ return absl::OkStatus();
+}
+
+std::optional<FlatbuffersStructUntypedDomainImpl::corpus_type>
+FlatbuffersStructUntypedDomainImpl::FromValue(const value_type& value) const {
+ if (value == nullptr) {
+ return std::nullopt;
+ }
+ corpus_type ret;
+ for (const auto* field : *struct_object_->fields()) {
+ VisitFlatbufferField(schema_, field, FromValueVisitor{*this, value, ret});
+ }
+ return ret;
+}
+
+std::optional<flatbuffers::uoffset_t>
+FlatbuffersStructUntypedDomainImpl::BuildValue(
+ const corpus_type& value, flatbuffers::FlatBufferBuilder& builder) const {
+ std::vector<uint8_t> buf(struct_object_->bytesize());
+ BuildValue(value, buf.data());
+ builder.StartStruct(struct_object_->minalign());
+ builder.PushBytes(buf.data(), buf.size());
+ return builder.EndStruct();
+}
+
+void FlatbuffersStructUntypedDomainImpl::BuildValue(const corpus_type& value,
+ uint8_t* buf) const {
+ for (const auto* field : *struct_object_->fields()) {
+ VisitFlatbufferField(schema_, field, BuildValueVisitor{*this, value, buf});
+ }
+}
+
+std::optional<FlatbuffersStructUntypedDomainImpl::corpus_type>
+FlatbuffersStructUntypedDomainImpl::ParseCorpus(const IRObject& obj) const {
+ corpus_type out;
+ auto subs = obj.Subs();
+ if (!subs) {
+ return std::nullopt;
+ }
+ out.reserve(subs->size());
+ for (const auto& sub : *subs) {
+ auto pair_subs = sub.Subs();
+ if (!pair_subs || pair_subs->size() != 2) {
+ return std::nullopt;
+ }
+ auto id = (*pair_subs)[0].GetScalar<typename corpus_type::key_type>();
+ if (!id.has_value()) {
+ return std::nullopt;
+ }
+ const reflection::Field* absl_nullable field = GetFieldById(id.value());
+ if (field == nullptr) {
+ return std::nullopt;
+ }
+ std::optional<GenericDomainCorpusType> inner_parsed;
+ VisitFlatbufferField(schema_, field,
+ ParseVisitor{*this, (*pair_subs)[1], inner_parsed});
+ if (!inner_parsed) {
+ return std::nullopt;
+ }
+ out[id.value()] = *std::move(inner_parsed);
+ }
+ return out;
+}
+
+// Converts the corpus value to an IRObject.
+IRObject FlatbuffersStructUntypedDomainImpl::SerializeCorpus(
+ const corpus_type& value) const {
+ IRObject out;
+ auto& subs = out.MutableSubs();
+ subs.reserve(value.size());
+ for (const auto& [id, field_corpus] : value) {
+ const reflection::Field* absl_nullable field = GetFieldById(id);
+ if (field == nullptr) {
+ continue;
+ }
+ IRObject& pair = subs.emplace_back();
+ auto& pair_subs = pair.MutableSubs();
+ pair_subs.reserve(2);
+ pair_subs.emplace_back(field->id());
+ VisitFlatbufferField(
+ schema_, field,
+ SerializeVisitor{*this, field_corpus, pair_subs.emplace_back()});
+ }
+ return out;
+}
+} // namespace internal
+} // namespace fuzztest
diff --git a/fuzztest/internal/domains/flatbuffers_domain_impl.h b/fuzztest/internal/domains/flatbuffers_domain_impl.h
new file mode 100644
index 0000000..ce711ab
--- /dev/null
+++ b/fuzztest/internal/domains/flatbuffers_domain_impl.h
@@ -0,0 +1,2106 @@
+// Copyright 2025 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
+//
+// http://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_FLATBUFFERS_DOMAIN_IMPL_H_
+#define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_FLATBUFFERS_DOMAIN_IMPL_H_
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <list>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "absl/algorithm/container.h"
+#include "absl/base/nullability.h"
+#include "absl/base/thread_annotations.h"
+#include "absl/container/flat_hash_map.h"
+#include "absl/random/bit_gen_ref.h"
+#include "absl/random/distributions.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_format.h"
+#include "absl/synchronization/mutex.h"
+#include "flatbuffers/base.h"
+#include "flatbuffers/flatbuffer_builder.h"
+#include "flatbuffers/reflection.h"
+#include "flatbuffers/reflection_generated.h"
+#include "flatbuffers/string.h"
+#include "flatbuffers/struct.h"
+#include "flatbuffers/table.h"
+#include "flatbuffers/vector.h"
+#include "flatbuffers/verifier.h"
+#include "./fuzztest/domain_core.h"
+#include "./fuzztest/internal/any.h"
+#include "./fuzztest/internal/domains/arbitrary_impl.h"
+#include "./fuzztest/internal/domains/domain_base.h"
+#include "./fuzztest/internal/domains/domain_type_erasure.h"
+#include "./fuzztest/internal/domains/element_of_impl.h"
+#include "./fuzztest/internal/logging.h"
+#include "./fuzztest/internal/meta.h"
+#include "./fuzztest/internal/serialization.h"
+#include "./fuzztest/internal/status.h"
+
+namespace fuzztest::internal {
+
+template <typename Underlying,
+ typename = std::enable_if_t<std::is_integral_v<Underlying> &&
+ !std::is_same_v<Underlying, bool>>>
+
+//
+// Flatbuffers enum detection.
+//
+struct FlatbuffersEnumTag {
+ using type = Underlying;
+};
+
+template <typename T>
+struct is_flatbuffers_enum_tag : std::false_type {};
+
+template <typename Underlying>
+struct is_flatbuffers_enum_tag<FlatbuffersEnumTag<Underlying>>
+ : std::true_type {};
+
+template <typename T>
+inline constexpr bool is_flatbuffers_enum_tag_v =
+ is_flatbuffers_enum_tag<T>::value;
+
+//
+// Flatbuffers vector detection.
+//
+template <typename T>
+struct FlatbuffersVectorTag {
+ using value_type = T;
+};
+
+template <typename T>
+struct is_flatbuffers_vector_tag : std::false_type {};
+
+template <typename T>
+struct is_flatbuffers_vector_tag<FlatbuffersVectorTag<T>> : std::true_type {};
+
+template <typename T>
+inline constexpr bool is_flatbuffers_vector_tag_v =
+ is_flatbuffers_vector_tag<T>::value;
+
+struct FlatbuffersArrayTag;
+struct FlatbuffersTableTag;
+struct FlatbuffersStructTag;
+struct FlatbuffersUnionTag;
+
+// Dynamic to static dispatch visitor pattern for flatbuffers vector elements.
+template <typename Visitor>
+auto VisitFlatbufferVectorElementField(const reflection::Schema* schema,
+ const reflection::Field* field,
+ Visitor visitor) {
+ auto field_index = field->type()->index();
+ auto element_type = field->type()->element();
+ switch (element_type) {
+ case reflection::BaseType::Bool:
+ visitor.template Visit<FlatbuffersVectorTag<bool>>(field);
+ break;
+ case reflection::BaseType::Byte:
+ if (field_index >= 0) {
+ visitor
+ .template Visit<FlatbuffersVectorTag<FlatbuffersEnumTag<int8_t>>>(
+ field);
+ } else {
+ visitor.template Visit<FlatbuffersVectorTag<int8_t>>(field);
+ }
+ break;
+ case reflection::BaseType::Short:
+ if (field_index >= 0) {
+ visitor
+ .template Visit<FlatbuffersVectorTag<FlatbuffersEnumTag<int16_t>>>(
+ field);
+ } else {
+ visitor.template Visit<FlatbuffersVectorTag<int16_t>>(field);
+ }
+ break;
+ case reflection::BaseType::Int:
+ if (field_index >= 0) {
+ visitor
+ .template Visit<FlatbuffersVectorTag<FlatbuffersEnumTag<int32_t>>>(
+ field);
+ } else {
+ visitor.template Visit<FlatbuffersVectorTag<int32_t>>(field);
+ }
+ break;
+ case reflection::BaseType::Long:
+ if (field_index >= 0) {
+ visitor
+ .template Visit<FlatbuffersVectorTag<FlatbuffersEnumTag<int64_t>>>(
+ field);
+ } else {
+ visitor.template Visit<FlatbuffersVectorTag<int64_t>>(field);
+ }
+ break;
+ case reflection::BaseType::UByte:
+ if (field_index >= 0) {
+ visitor
+ .template Visit<FlatbuffersVectorTag<FlatbuffersEnumTag<uint8_t>>>(
+ field);
+ } else {
+ visitor.template Visit<FlatbuffersVectorTag<uint8_t>>(field);
+ }
+ break;
+ case reflection::BaseType::UShort:
+ if (field_index >= 0) {
+ visitor
+ .template Visit<FlatbuffersVectorTag<FlatbuffersEnumTag<uint16_t>>>(
+ field);
+ } else {
+ visitor.template Visit<FlatbuffersVectorTag<uint16_t>>(field);
+ }
+ break;
+ case reflection::BaseType::UInt:
+ if (field_index >= 0) {
+ visitor
+ .template Visit<FlatbuffersVectorTag<FlatbuffersEnumTag<uint32_t>>>(
+ field);
+ } else {
+ visitor.template Visit<FlatbuffersVectorTag<uint32_t>>(field);
+ }
+ break;
+ case reflection::BaseType::ULong:
+ if (field_index >= 0) {
+ visitor
+ .template Visit<FlatbuffersVectorTag<FlatbuffersEnumTag<uint64_t>>>(
+ field);
+ } else {
+ visitor.template Visit<FlatbuffersVectorTag<uint64_t>>(field);
+ }
+ break;
+ case reflection::BaseType::Float:
+ visitor.template Visit<FlatbuffersVectorTag<float>>(field);
+ break;
+ case reflection::BaseType::Double:
+ visitor.template Visit<FlatbuffersVectorTag<double>>(field);
+ break;
+ case reflection::BaseType::String:
+ visitor.template Visit<FlatbuffersVectorTag<std::string>>(field);
+ break;
+ case reflection::BaseType::Obj: {
+ auto sub_object = schema->objects()->Get(field_index);
+ if (sub_object->is_struct()) {
+ visitor.template Visit<FlatbuffersVectorTag<FlatbuffersStructTag>>(
+ field);
+ } else {
+ visitor.template Visit<FlatbuffersVectorTag<FlatbuffersTableTag>>(
+ field);
+ }
+ break;
+ }
+ case reflection::BaseType::Union:
+ visitor.template Visit<FlatbuffersVectorTag<FlatbuffersUnionTag>>(field);
+ break;
+ case reflection::BaseType::UType:
+ // Noop: Union types are visited at the same time as their corresponding
+ // union values.
+ break;
+ default: // Vector of vectors and vector of arrays are not supported.
+ FUZZTEST_INTERNAL_CHECK(false, "Unsupported vector base type");
+ }
+}
+
+// Dynamic to static dispatch visitor pattern.
+template <typename Visitor>
+auto VisitFlatbufferField(const reflection::Schema* absl_nonnull schema,
+ const reflection::Field* absl_nonnull field,
+ Visitor visitor) {
+ auto field_index = field->type()->index();
+ switch (field->type()->base_type()) {
+ case reflection::BaseType::Bool:
+ visitor.template Visit<bool>(field);
+ break;
+ case reflection::BaseType::Byte:
+ if (field_index >= 0) {
+ visitor.template Visit<FlatbuffersEnumTag<int8_t>>(field);
+ } else {
+ visitor.template Visit<int8_t>(field);
+ }
+ break;
+ case reflection::BaseType::Short:
+ if (field_index >= 0) {
+ visitor.template Visit<FlatbuffersEnumTag<int16_t>>(field);
+ } else {
+ visitor.template Visit<int16_t>(field);
+ }
+ break;
+ case reflection::BaseType::Int:
+ if (field_index >= 0) {
+ visitor.template Visit<FlatbuffersEnumTag<int32_t>>(field);
+ } else {
+ visitor.template Visit<int32_t>(field);
+ }
+ break;
+ case reflection::BaseType::Long:
+ if (field_index >= 0) {
+ visitor.template Visit<FlatbuffersEnumTag<int64_t>>(field);
+ } else {
+ visitor.template Visit<int64_t>(field);
+ }
+ break;
+ case reflection::BaseType::UByte:
+ if (field_index >= 0) {
+ visitor.template Visit<FlatbuffersEnumTag<uint8_t>>(field);
+ } else {
+ visitor.template Visit<uint8_t>(field);
+ }
+ break;
+ case reflection::BaseType::UShort:
+ if (field_index >= 0) {
+ visitor.template Visit<FlatbuffersEnumTag<uint16_t>>(field);
+ } else {
+ visitor.template Visit<uint16_t>(field);
+ }
+ break;
+ case reflection::BaseType::UInt:
+ if (field_index >= 0) {
+ visitor.template Visit<FlatbuffersEnumTag<uint32_t>>(field);
+ } else {
+ visitor.template Visit<uint32_t>(field);
+ }
+ break;
+ case reflection::BaseType::ULong:
+ if (field_index >= 0) {
+ visitor.template Visit<FlatbuffersEnumTag<uint64_t>>(field);
+ } else {
+ visitor.template Visit<uint64_t>(field);
+ }
+ break;
+ case reflection::BaseType::Float:
+ visitor.template Visit<float>(field);
+ break;
+ case reflection::BaseType::Double:
+ visitor.template Visit<double>(field);
+ break;
+ case reflection::BaseType::String:
+ visitor.template Visit<std::string>(field);
+ break;
+ case reflection::BaseType::Vector:
+ case reflection::BaseType::Vector64:
+ VisitFlatbufferVectorElementField<Visitor>(schema, field, visitor);
+ break;
+ case reflection::BaseType::Array:
+ visitor.template Visit<FlatbuffersArrayTag>(field);
+ break;
+ case reflection::BaseType::Obj: {
+ auto sub_object = schema->objects()->Get(field->type()->index());
+ if (sub_object->is_struct()) {
+ visitor.template Visit<FlatbuffersStructTag>(field);
+ } else {
+ visitor.template Visit<FlatbuffersTableTag>(field);
+ }
+ break;
+ }
+ case reflection::BaseType::Union:
+ visitor.template Visit<FlatbuffersUnionTag>(field);
+ break;
+ case reflection::BaseType::UType:
+ // Noop: Union types are visited at the same time as their corresponding
+ // union values.
+ break;
+ default:
+ FUZZTEST_INTERNAL_CHECK(false, "Unsupported base type");
+ }
+}
+
+// Flatbuffers enum domain implementation.
+template <typename Underlaying>
+class FlatbuffersEnumDomainImpl
+ // Value type: Underlaying
+ // Corpus type: ElementOfImplCorpusType
+ // See ElementOfImpl for more details.
+ : public ElementOfImpl<Underlaying> {
+ public:
+ using typename ElementOfImpl<Underlaying>::DomainBase::corpus_type;
+ using typename ElementOfImpl<Underlaying>::DomainBase::value_type;
+
+ explicit FlatbuffersEnumDomainImpl(const reflection::Enum* enum_def)
+ : ElementOfImpl<Underlaying>(GetEnumValues(enum_def)),
+ enum_def_(enum_def) {}
+
+ auto GetPrinter() const { return Printer{*this}; }
+
+ private:
+ const reflection::Enum* enum_def_;
+
+ static std::vector<value_type> GetEnumValues(
+ const reflection::Enum* enum_def) {
+ std::vector<value_type> values;
+ values.reserve(enum_def->values()->size());
+ for (const auto* value : *enum_def->values()) {
+ FUZZTEST_INTERNAL_CHECK(
+ value->value() >= std::numeric_limits<value_type>::min() &&
+ value->value() <= std::numeric_limits<value_type>::max(),
+ "Enum value from reflection is out of range for the target type.");
+ values.push_back(static_cast<value_type>(value->value()));
+ }
+ return values;
+ }
+
+ struct Printer {
+ const FlatbuffersEnumDomainImpl& self;
+ void PrintCorpusValue(const corpus_type& value,
+ domain_implementor::RawSink out,
+ domain_implementor::PrintMode mode) const {
+ if (mode == domain_implementor::PrintMode::kHumanReadable) {
+ auto user_value = self.GetValue(value);
+ absl::Format(
+ out, "%s",
+ self.enum_def_->values()->LookupByKey(user_value)->name()->str());
+ } else {
+ absl::Format(out, "%d", value);
+ }
+ }
+ };
+};
+
+// From flatbuffers documentation:
+// Unions are encoded as the combination of two fields: an enum representing the
+// union choice and the offset to the actual element.
+// The type of the enum is always uint8_t as generated by the flatbuffers
+// compiler.
+using FlatbuffersUnionTypeDomainImpl = FlatbuffersEnumDomainImpl<uint8_t>;
+
+// Domain implementation for flatbuffers struct types.
+// The corpus type is a map of field ids to field values.
+class FlatbuffersStructUntypedDomainImpl
+ : public domain_implementor::DomainBase<
+ // Derived, for CRTP needs. See DomainBase for more details.
+ FlatbuffersStructUntypedDomainImpl,
+ // ValueType - user facing type
+ const flatbuffers::Struct* absl_nonnull,
+ // CorpusType - internal representation of ValueType,
+ // a map of field ids to field values.
+ absl::flat_hash_map<
+ // a.k.a. uint16_t
+ decltype(static_cast<reflection::Field*>(nullptr)->id()),
+ // Fancy wrapper around `void*`: knows about the exact type of
+ // stored value and can copy it using exact type copy constructor
+ // via `CopyFrom` method.
+ GenericDomainCorpusType>> {
+ public:
+ using typename FlatbuffersStructUntypedDomainImpl::DomainBase::corpus_type;
+ using typename FlatbuffersStructUntypedDomainImpl::DomainBase::value_type;
+
+ FlatbuffersStructUntypedDomainImpl(const reflection::Schema* schema,
+ const reflection::Object* struct_object)
+ : schema_(schema), struct_object_(struct_object) {}
+
+ FlatbuffersStructUntypedDomainImpl(
+ const FlatbuffersStructUntypedDomainImpl& other)
+ : schema_(other.schema_), struct_object_(other.struct_object_) {
+ absl::MutexLock l(&other.mutex_);
+ domains_ = other.domains_;
+ }
+
+ FlatbuffersStructUntypedDomainImpl& operator=(
+ const FlatbuffersStructUntypedDomainImpl& other) {
+ schema_ = other.schema_;
+ struct_object_ = other.struct_object_;
+ absl::MutexLock l(&other.mutex_);
+ domains_ = other.domains_;
+ return *this;
+ }
+
+ FlatbuffersStructUntypedDomainImpl(FlatbuffersStructUntypedDomainImpl&& other)
+ : schema_(other.schema_), struct_object_(other.struct_object_) {
+ absl::MutexLock l(&other.mutex_);
+ domains_ = std::move(other.domains_);
+ }
+
+ FlatbuffersStructUntypedDomainImpl& operator=(
+ FlatbuffersStructUntypedDomainImpl&& other) {
+ schema_ = other.schema_;
+ struct_object_ = other.struct_object_;
+ absl::MutexLock l(&other.mutex_);
+ domains_ = std::move(other.domains_);
+ return *this;
+ }
+
+ // Returns the domain for the given field.
+ // The domain is cached, and the same instance is returned for the same
+ // field.
+ template <typename T>
+ auto& GetSubDomain(const reflection::Field* absl_nonnull field) const {
+ using DomainT = decltype(GetDomainForField<T>(field));
+ // Do the operation under a lock to prevent race conditions in `const`
+ // methods.
+ absl::MutexLock l(&mutex_);
+ auto it = domains_.find(field->id());
+ if (it == domains_.end()) {
+ it = domains_
+ .try_emplace(field->id(), std::in_place_type<DomainT>,
+ GetDomainForField<T>(field))
+ .first;
+ }
+ return it->second.template GetAs<DomainT>();
+ }
+
+ // Initializes the corpus value.
+ corpus_type Init(absl::BitGenRef prng);
+
+ // Mutates the corpus value.
+ void Mutate(corpus_type& val, absl::BitGenRef prng,
+ const domain_implementor::MutationMetadata& metadata,
+ bool only_shrink);
+
+ // Counts the number of fields that can be mutated.
+ // Returns the number of fields in the flattened tree for supported field
+ // types.
+ uint64_t CountNumberOfFields(corpus_type& val);
+
+ // Mutates the selected field.
+ // The selected field index is based on the flattened tree.
+ uint64_t MutateSelectedField(
+ corpus_type& val, absl::BitGenRef prng,
+ const domain_implementor::MutationMetadata& metadata, bool only_shrink,
+ uint64_t selected_field_index);
+
+ auto GetPrinter() const { return Printer{*this}; }
+
+ absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const;
+
+ value_type GetValue(const corpus_type& value) const {
+ // Untyped domain does not support GetValue since if it is a nested table
+ // it would need the top level table corpus value to be able to build it.
+ return nullptr;
+ }
+
+ // Converts the struct pointer to a corpus value.
+ std::optional<corpus_type> FromValue(const value_type& value) const;
+
+ // Builds the struct in a builder.
+ std::optional<flatbuffers::uoffset_t> BuildValue(
+ const corpus_type& value, flatbuffers::FlatBufferBuilder& builder) const;
+
+ // Builds the struct in a buffer.
+ void BuildValue(const corpus_type& value, uint8_t* buf) const;
+
+ // Converts the IRObject to a corpus value.
+ std::optional<corpus_type> ParseCorpus(const IRObject& obj) const;
+
+ // Converts the corpus value to an IRObject.
+ IRObject SerializeCorpus(const corpus_type& value) const;
+
+ private:
+ const reflection::Schema* schema_;
+ const reflection::Object* struct_object_;
+ mutable absl::Mutex mutex_;
+ mutable absl::flat_hash_map<
+ // a.k.a. uint16_t
+ decltype(static_cast<reflection::Field*>(nullptr)->id()),
+ // Fancy wrapper around `void*`: knows about the exact type of
+ // stored value and can copy it using exact type copy constructor
+ // via `CopyFrom` method.
+ GenericDomainCorpusType>
+ domains_ ABSL_GUARDED_BY(mutex_);
+
+ const reflection::Field* absl_nullable GetFieldById(
+ typename corpus_type::key_type id) const {
+ const auto it =
+ absl::c_find_if(*struct_object_->fields(),
+ [id](const auto* field) { return field->id() == id; });
+ return it != struct_object_->fields()->end() ? *it : nullptr;
+ }
+
+ // Returns the domain for the given field.
+ template <typename T>
+ auto GetDomainForField(const reflection::Field* absl_nonnull field) const {
+ if constexpr (std::is_same_v<T, FlatbuffersArrayTag>) {
+ // TODO(b/405938558): Implement this.
+ return Domain<bool>{Arbitrary<bool>()};
+ } else if constexpr (is_flatbuffers_enum_tag_v<T>) {
+ auto enum_object = schema_->enums()->Get(field->type()->index());
+ return Domain<typename T::type>{
+ FlatbuffersEnumDomainImpl<typename T::type>(enum_object)};
+ } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+ const reflection::Object* sub_object =
+ schema_->objects()->Get(field->type()->index());
+ FUZZTEST_INTERNAL_CHECK(sub_object->is_struct(),
+ "Field must be a struct type.");
+ return Domain<const flatbuffers::Struct*>(
+ FlatbuffersStructUntypedDomainImpl{schema_, sub_object});
+ } else if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>) {
+ return Domain<T>{Arbitrary<T>()};
+ } else {
+ FUZZTEST_INTERNAL_CHECK(false, "Unsupported type");
+ // Return a no-op domain for the static checks to pass.
+ return Domain<bool>{Arbitrary<bool>()};
+ }
+ }
+
+ struct InitializeVisitor {
+ FlatbuffersStructUntypedDomainImpl& self;
+ absl::BitGenRef prng;
+ corpus_type& val;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) {
+ auto& domain = self.GetSubDomain<T>(field);
+ val[field->id()] = domain.Init(prng);
+ }
+ };
+
+ struct CountNumberOfFieldsVisitor {
+ const FlatbuffersStructUntypedDomainImpl& self;
+ uint64_t& total_weight;
+ corpus_type& corpus;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) const {
+ if constexpr (std::is_same_v<T, FlatbuffersArrayTag>) {
+ // TODO(b/405938558): Implement this.
+ return;
+ } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+ auto sub_object = self.schema_->objects()->Get(field->type()->index());
+ FUZZTEST_INTERNAL_CHECK(sub_object->is_struct(),
+ "Field must be a struct type.");
+ auto sub_domain = self.GetSubDomain<T>(field);
+ total_weight += sub_domain.CountNumberOfFields(corpus.at(field->id()));
+ } else {
+ total_weight++;
+ }
+ }
+ };
+
+ struct MutateVisitor {
+ FlatbuffersStructUntypedDomainImpl& self;
+ absl::BitGenRef prng;
+ const domain_implementor::MutationMetadata& metadata;
+ bool only_shrink;
+ corpus_type& val;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) {
+ auto& domain = self.GetSubDomain<T>(field);
+ if (auto it = val.find(field->id()); it != val.end()) {
+ domain.Mutate(it->second, prng, metadata, only_shrink);
+ } else if (!only_shrink) {
+ val[field->id()] = domain.Init(prng);
+ }
+ }
+ };
+
+ struct ParseVisitor {
+ const FlatbuffersStructUntypedDomainImpl& self;
+ const IRObject& obj;
+ std::optional<GenericDomainCorpusType>& out;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) {
+ out = self.GetSubDomain<T>(field).ParseCorpus(obj);
+ }
+ };
+
+ struct SerializeVisitor {
+ const FlatbuffersStructUntypedDomainImpl& self;
+ const GenericDomainCorpusType& corpus_value;
+ IRObject& out;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) {
+ out = self.GetSubDomain<T>(field).SerializeCorpus(corpus_value);
+ }
+ };
+
+ struct Printer {
+ const FlatbuffersStructUntypedDomainImpl& self;
+
+ void PrintCorpusValue(const corpus_type& value,
+ domain_implementor::RawSink out,
+ domain_implementor::PrintMode mode) const {
+ absl::Format(out, "{");
+ bool first = true;
+ for (const auto& [id, field_corpus] : value) {
+ if (!first) {
+ absl::Format(out, ", ");
+ }
+ const reflection::Field* absl_nullable field = self.GetFieldById(id);
+ if (field == nullptr) {
+ absl::Format(out, "<unknown field: %d>", id);
+ } else {
+ VisitFlatbufferField(self.schema_, field,
+ PrinterVisitor{self, field_corpus, out, mode});
+ }
+ first = false;
+ }
+ absl::Format(out, "}");
+ }
+ };
+
+ struct PrinterVisitor {
+ const FlatbuffersStructUntypedDomainImpl& self;
+ const GenericDomainCorpusType& val;
+ domain_implementor::RawSink out;
+ domain_implementor::PrintMode mode;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) const {
+ auto& domain = self.GetSubDomain<T>(field);
+ absl::Format(out, "%s: ", field->name()->str());
+ domain_implementor::PrintValue(domain, val, out, mode);
+ }
+ };
+
+ struct ValidateVisitor {
+ const FlatbuffersStructUntypedDomainImpl& self;
+ const GenericDomainCorpusType& corpus_value;
+ absl::Status& out;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) {
+ auto& domain = self.GetSubDomain<T>(field);
+ out = domain.ValidateCorpusValue(corpus_value);
+ if (!out.ok()) {
+ out = Prefix(out, absl::StrCat("Invalid value for field ",
+ field->name()->str()));
+ }
+ }
+ };
+
+ struct FromValueVisitor {
+ const FlatbuffersStructUntypedDomainImpl& self;
+ value_type value;
+ corpus_type& out;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) const {
+ [[maybe_unused]]
+ reflection::BaseType base_type = field->type()->base_type();
+ auto& domain = self.GetSubDomain<T>(field);
+ std::optional<corpus_type_t<std::decay_t<decltype(domain)>>> inner_corpus;
+
+ if constexpr (is_flatbuffers_enum_tag_v<T>) {
+ FUZZTEST_INTERNAL_CHECK(base_type >= reflection::BaseType::Byte &&
+ base_type <= reflection::BaseType::ULong &&
+ field->type()->index() >= 0,
+ "Field must be an enum type.");
+ auto inner_value = value->GetField<typename T::type>(field->offset());
+ inner_corpus = domain.FromValue(inner_value);
+ } else if constexpr (std::is_integral_v<T> ||
+ std::is_floating_point_v<T>) {
+ FUZZTEST_INTERNAL_CHECK(flatbuffers::IsScalar(base_type),
+ "Field must be an scalar type.");
+ auto inner_value = value->GetField<T>(field->offset());
+ inner_corpus = domain.FromValue(inner_value);
+ } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+ auto sub_object = self.schema_->objects()->Get(field->type()->index());
+ FUZZTEST_INTERNAL_CHECK(
+ base_type == reflection::BaseType::Obj && sub_object->is_struct(),
+ "Field must be a struct type.");
+ auto inner_value =
+ value->GetStruct<const flatbuffers::Struct*>(field->offset());
+ inner_corpus = domain.FromValue(inner_value);
+ }
+
+ if (inner_corpus.has_value()) {
+ out[field->id()] = std::move(*inner_corpus);
+ }
+ };
+ };
+
+ struct BuildValueVisitor {
+ const FlatbuffersStructUntypedDomainImpl& self;
+ const corpus_type& corpus_value;
+ uint8_t* struct_ptr;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) const {
+ [[maybe_unused]]
+ reflection::BaseType base_type = field->type()->base_type();
+ auto& domain = self.GetSubDomain<T>(field);
+ if constexpr (is_flatbuffers_enum_tag_v<T> || std::is_integral_v<T> ||
+ std::is_floating_point_v<T>) {
+ FUZZTEST_INTERNAL_CHECK(flatbuffers::IsScalar(base_type),
+ "Field must be an scalar type.");
+ auto inner_value = domain.GetValue(corpus_value.at(field->id()));
+ flatbuffers::WriteScalar(struct_ptr + field->offset(), inner_value);
+ } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+ auto sub_object = self.schema_->objects()->Get(field->type()->index());
+ FUZZTEST_INTERNAL_CHECK(
+ base_type == reflection::BaseType::Obj && sub_object->is_struct(),
+ "Field must be a struct type.");
+ auto inner_corpus_value =
+ corpus_value.at(field->id())
+ .GetAs<FlatbuffersStructUntypedDomainImpl::corpus_type>();
+ FlatbuffersStructUntypedDomainImpl sub_domain(self.schema_, sub_object);
+ for (const auto* nested_field : *sub_object->fields()) {
+ VisitFlatbufferField(sub_domain.schema_, nested_field,
+ BuildValueVisitor{sub_domain, inner_corpus_value,
+ struct_ptr + field->offset()});
+ }
+ } else if constexpr (std::is_same_v<T, FlatbuffersArrayTag>) {
+ // TODO (b/405938558): Implement array support.
+ }
+ }
+ };
+};
+
+class FlatbuffersTableUntypedDomainImpl;
+
+// Flatbuffers union domain implementation.
+class FlatbuffersUnionDomainImpl
+ : public domain_implementor::DomainBase<
+ // Derived, for CRTP needs. See DomainBase for more details.
+ FlatbuffersUnionDomainImpl,
+ // ValueType - user facing type
+ std::pair<typename FlatbuffersUnionTypeDomainImpl::value_type,
+ const void*>,
+ // CorpusType - internal representation of ValueType
+ std::pair<
+ // `Union choice` type representation
+ typename FlatbuffersUnionTypeDomainImpl::corpus_type,
+ // Fancy wrapper around `void*`: knows about the exact type
+ // of stored value and can copy it using exact type copy
+ // constructor via `AnyBase::CopyFrom` method.
+ GenericDomainCorpusType>> {
+ public:
+ using typename FlatbuffersUnionDomainImpl::DomainBase::corpus_type;
+ using typename FlatbuffersUnionDomainImpl::DomainBase::value_type;
+
+ FlatbuffersUnionDomainImpl(const reflection::Schema* schema,
+ const reflection::Enum* union_def)
+ : schema_(schema), union_def_(union_def), type_domain_(union_def) {}
+
+ FlatbuffersUnionDomainImpl(const FlatbuffersUnionDomainImpl& other)
+ : schema_(other.schema_),
+ union_def_(other.union_def_),
+ type_domain_(other.type_domain_) {
+ absl::MutexLock l(&other.mutex_);
+ domains_ = other.domains_;
+ }
+
+ FlatbuffersUnionDomainImpl(FlatbuffersUnionDomainImpl&& other)
+ : schema_(other.schema_),
+ union_def_(other.union_def_),
+ type_domain_(std::move(other.type_domain_)) {
+ absl::MutexLock l(&other.mutex_);
+ domains_ = std::move(other.domains_);
+ }
+
+ FlatbuffersUnionDomainImpl& operator=(
+ const FlatbuffersUnionDomainImpl& other) {
+ schema_ = other.schema_;
+ union_def_ = other.union_def_;
+ type_domain_ = other.type_domain_;
+ absl::MutexLock l(&other.mutex_);
+ domains_ = other.domains_;
+ return *this;
+ }
+
+ FlatbuffersUnionDomainImpl& operator=(FlatbuffersUnionDomainImpl&& other) {
+ schema_ = other.schema_;
+ union_def_ = other.union_def_;
+ type_domain_ = std::move(other.type_domain_);
+ absl::MutexLock l(&other.mutex_);
+ domains_ = std::move(other.domains_);
+ return *this;
+ }
+
+ // Initializes the corpus value.
+ corpus_type Init(absl::BitGenRef prng);
+
+ // Mutates the corpus value.
+ void Mutate(corpus_type& val, absl::BitGenRef prng,
+ const domain_implementor::MutationMetadata& metadata,
+ bool only_shrink);
+
+ uint64_t CountNumberOfFields(corpus_type& val);
+
+ auto GetPrinter() const { return Printer{*this}; }
+
+ absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const;
+
+ // UNSUPPORTED: Flatbuffers unions user values are not supported.
+ value_type GetValue(const corpus_type& value) const {
+ FUZZTEST_INTERNAL_CHECK(false, "GetValue is not supported for unions.");
+ }
+
+ // Gets the type of the union field.
+ auto GetType(const corpus_type& value) const {
+ return type_domain_.GetValue(value.first);
+ }
+
+ // Creates flatbuffer from the corpus value.
+ std::optional<flatbuffers::uoffset_t> BuildValue(
+ const corpus_type& value, flatbuffers::FlatBufferBuilder& builder) const;
+
+ // Converts the value to a corpus value.
+ std::optional<corpus_type> FromValue(const value_type& value) const;
+
+ // Converts the IRObject to a corpus value.
+ std::optional<corpus_type> ParseCorpus(const IRObject& obj) const;
+
+ // Converts the corpus value to an IRObject.
+ IRObject SerializeCorpus(const corpus_type& value) const;
+
+ // Returns the domain for the given enum value.
+ template <typename T>
+ auto& GetSubDomain(const reflection::EnumVal& enum_value) const {
+ using DomainT = decltype(GetDomainForType<T>(enum_value));
+ absl::MutexLock l(&mutex_);
+ auto it = domains_.find(enum_value.value());
+ if (it == domains_.end()) {
+ it = domains_
+ .try_emplace(enum_value.value(), std::in_place_type<DomainT>,
+ GetDomainForType<T>(enum_value))
+ .first;
+ }
+ return it->second.GetAs<DomainT>();
+ }
+
+ private:
+ const reflection::Schema* schema_;
+ const reflection::Enum* union_def_;
+ FlatbuffersEnumDomainImpl<typename value_type::first_type> type_domain_;
+ mutable absl::Mutex mutex_;
+ mutable absl::flat_hash_map<typename value_type::first_type, CopyableAny>
+ domains_ ABSL_GUARDED_BY(mutex_);
+
+ // Creates new or returns existing domain for the given enum value.
+ template <typename T>
+ auto GetDomainForType(const reflection::EnumVal& enum_value) const;
+
+ struct Printer {
+ const FlatbuffersUnionDomainImpl& self;
+
+ void PrintCorpusValue(const corpus_type& value,
+ domain_implementor::RawSink out,
+ domain_implementor::PrintMode mode) const;
+ };
+};
+
+// Domain implementation for flatbuffers untyped tables.
+// The corpus type is a map of field ids to field values.
+class FlatbuffersTableUntypedDomainImpl
+ : public domain_implementor::DomainBase<
+ // Derived, for CRTP needs. See DomainBase for more details.
+ FlatbuffersTableUntypedDomainImpl,
+ // ValueType - user facing type
+ const flatbuffers::Table* absl_nonnull,
+ // CorpusType - internal representation of ValueType,
+ // a map of field ids to field values.
+ absl::flat_hash_map<
+ // a.k.a. uint16_t
+ decltype(static_cast<reflection::Field*>(nullptr)->id()),
+ // Fancy wrapper around `void*`: knows about the exact type of
+ // stored value and can copy it using exact type copy constructor
+ // via `CopyFrom` method.
+ GenericDomainCorpusType>> {
+ public:
+ using typename FlatbuffersTableUntypedDomainImpl::DomainBase::corpus_type;
+ using typename FlatbuffersTableUntypedDomainImpl::DomainBase::value_type;
+
+ explicit FlatbuffersTableUntypedDomainImpl(
+ const reflection::Schema* schema, const reflection::Object* table_object)
+ : schema_(schema), table_object_(table_object) {}
+
+ FlatbuffersTableUntypedDomainImpl(
+ const FlatbuffersTableUntypedDomainImpl& other)
+ : schema_(other.schema_), table_object_(other.table_object_) {
+ absl::MutexLock l_other(&other.mutex_);
+ absl::MutexLock l_this(&mutex_);
+ domains_ = other.domains_;
+ }
+
+ FlatbuffersTableUntypedDomainImpl& operator=(
+ const FlatbuffersTableUntypedDomainImpl& other) {
+ schema_ = other.schema_;
+ table_object_ = other.table_object_;
+ absl::MutexLock l_other(&other.mutex_);
+ absl::MutexLock l_this(&mutex_);
+ domains_ = other.domains_;
+ return *this;
+ }
+
+ FlatbuffersTableUntypedDomainImpl(FlatbuffersTableUntypedDomainImpl&& other)
+ : schema_(other.schema_), table_object_(other.table_object_) {
+ absl::MutexLock l_other(&other.mutex_);
+ absl::MutexLock l_this(&mutex_);
+ domains_ = std::move(other.domains_);
+ }
+
+ FlatbuffersTableUntypedDomainImpl& operator=(
+ FlatbuffersTableUntypedDomainImpl&& other) {
+ schema_ = other.schema_;
+ table_object_ = other.table_object_;
+ absl::MutexLock l_other(&other.mutex_);
+ absl::MutexLock l_this(&mutex_);
+ domains_ = std::move(other.domains_);
+ return *this;
+ }
+
+ // Initializes the corpus value.
+ corpus_type Init(absl::BitGenRef prng) {
+ if (auto seed = this->MaybeGetRandomSeed(prng)) {
+ return *seed;
+ }
+ corpus_type val;
+ for (const auto* field : *table_object_->fields()) {
+ VisitFlatbufferField(schema_, field, InitializeVisitor{*this, prng, val});
+ }
+ return val;
+ }
+
+ // Mutates the corpus value.
+ void Mutate(corpus_type& val, absl::BitGenRef prng,
+ const domain_implementor::MutationMetadata& metadata,
+ bool only_shrink) {
+ auto total_weight = CountNumberOfFields(val);
+ auto selected_weight =
+ absl::Uniform(absl::IntervalClosedClosed, prng, 0ul, total_weight - 1);
+
+ MutateSelectedField(val, prng, metadata, only_shrink, selected_weight);
+ }
+
+ // Returns the domain for the given vector field.
+ template <typename Element>
+ auto GetDomainForVectorField(const reflection::Field* field) const {
+ if constexpr (is_flatbuffers_enum_tag_v<Element>) {
+ auto enum_object = schema_->enums()->Get(field->type()->index());
+ auto inner = OptionalOf(
+ VectorOf(
+ FlatbuffersEnumDomainImpl<typename Element::type>(enum_object))
+ .WithMaxSize(std::numeric_limits<flatbuffers::uoffset_t>::max()));
+ if (!field->optional()) {
+ inner.SetWithoutNull();
+ }
+ return Domain<value_type_t<decltype(inner)>>{inner};
+ } else if constexpr (std::is_same_v<Element, FlatbuffersTableTag>) {
+ auto table_object = schema_->objects()->Get(field->type()->index());
+ auto inner = OptionalOf(
+ VectorOf(FlatbuffersTableUntypedDomainImpl{schema_, table_object})
+ .WithMaxSize(std::numeric_limits<flatbuffers::uoffset_t>::max()));
+ if (!field->optional()) {
+ inner.SetWithoutNull();
+ }
+ return Domain<std::optional<std::vector<const flatbuffers::Table*>>>{
+ inner};
+ } else if constexpr (std::is_same_v<Element, FlatbuffersStructTag>) {
+ auto struct_object = schema_->objects()->Get(field->type()->index());
+ auto inner = OptionalOf(
+ VectorOf(FlatbuffersStructUntypedDomainImpl{schema_, struct_object})
+ .WithMaxSize(std::numeric_limits<flatbuffers::uoffset_t>::max()));
+ if (!field->optional()) {
+ inner.SetWithoutNull();
+ }
+ return Domain<std::optional<std::vector<const flatbuffers::Struct*>>>{
+ inner};
+ } else if constexpr (std::is_same_v<Element, FlatbuffersUnionTag>) {
+ auto union_type = schema_->enums()->Get(field->type()->index());
+ auto inner = OptionalOf(
+ VectorOf(FlatbuffersUnionDomainImpl{schema_, union_type})
+ .WithMaxSize(std::numeric_limits<flatbuffers::uoffset_t>::max()));
+ if (!field->optional()) {
+ inner.SetWithoutNull();
+ }
+ return Domain<value_type_t<decltype(inner)>>{inner};
+ } else {
+ auto inner = OptionalOf(
+ VectorOf(ArbitraryImpl<Element>())
+ .WithMaxSize(std::numeric_limits<flatbuffers::uoffset_t>::max()));
+ if (!field->optional()) {
+ inner.SetWithoutNull();
+ }
+ return Domain<std::optional<std::vector<Element>>>{inner};
+ }
+ }
+
+ // Returns the domain for the given field.
+ template <typename T>
+ auto GetDomainForField(const reflection::Field* field) const {
+ if constexpr (std::is_same_v<T, FlatbuffersArrayTag>) {
+ FUZZTEST_INTERNAL_CHECK(
+ false, "Arrays in tables are not supported in flatbuffers.");
+ // Return a placeholder domain to make the compiler happy.
+ return Domain<std::optional<bool>>{Arbitrary<std::optional<bool>>()};
+ } else if constexpr (is_flatbuffers_enum_tag_v<T>) {
+ auto enum_object = schema_->enums()->Get(field->type()->index());
+ auto domain =
+ OptionalOf(FlatbuffersEnumDomainImpl<typename T::type>(enum_object));
+ if (!field->optional()) {
+ domain.SetWithoutNull();
+ }
+ return Domain<value_type_t<decltype(domain)>>{domain};
+ } else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
+ auto table_object = schema_->objects()->Get(field->type()->index());
+ auto inner =
+ OptionalOf(FlatbuffersTableUntypedDomainImpl{schema_, table_object});
+ if (!field->optional()) {
+ inner.SetWithoutNull();
+ }
+ return Domain<std::optional<const flatbuffers::Table*>>{inner};
+ } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+ auto struct_object = schema_->objects()->Get(field->type()->index());
+ auto inner = OptionalOf(
+ FlatbuffersStructUntypedDomainImpl{schema_, struct_object});
+ if (!field->optional()) {
+ inner.SetWithoutNull();
+ }
+ return Domain<std::optional<const flatbuffers::Struct*>>(inner);
+ } else if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) {
+ auto union_type = schema_->enums()->Get(field->type()->index());
+ auto inner = OptionalOf(FlatbuffersUnionDomainImpl{schema_, union_type});
+ return Domain<value_type_t<decltype(inner)>>{inner};
+ } else if constexpr (is_flatbuffers_vector_tag_v<T>) {
+ return GetDomainForVectorField<typename T::value_type>(field);
+ } else {
+ auto inner = OptionalOf(ArbitraryImpl<T>());
+ if (!field->optional()) {
+ inner.SetWithoutNull();
+ }
+ return Domain<std::optional<T>>{inner};
+ }
+ }
+
+ // Returns the domain for the given field.
+ // The domain is cached, and the same instance is returned for the same
+ // field.
+ template <typename T>
+ auto& GetSubDomain(const reflection::Field* absl_nonnull field) const {
+ using DomainT = decltype(GetDomainForField<T>(field));
+ // Do the operation under a lock to prevent race conditions in `const`
+ // methods.
+ absl::MutexLock l(&mutex_);
+ auto it = domains_.find(field->id());
+ if (it == domains_.end()) {
+ it = domains_
+ .try_emplace(field->id(), std::in_place_type<DomainT>,
+ GetDomainForField<T>(field))
+ .first;
+ }
+ return it->second.template GetAs<DomainT>();
+ }
+
+ // Counts the number of fields that can be mutated.
+ // Returns the number of fields in the flattened tree for supported field
+ // types.
+ uint64_t CountNumberOfFields(corpus_type& val) {
+ uint64_t total_weight = 0;
+ for (const auto* field : *table_object_->fields()) {
+ VisitFlatbufferField(
+ schema_, field, CountNumberOfFieldsVisitor{*this, total_weight, val});
+ }
+ return total_weight;
+ }
+
+ // Mutates the selected field.
+ // The selected field index is based on the flattened tree.
+ uint64_t MutateSelectedField(
+ corpus_type& val, absl::BitGenRef prng,
+ const domain_implementor::MutationMetadata& metadata, bool only_shrink,
+ uint64_t selected_field_index) {
+ uint64_t field_counter = 0;
+ for (const auto* field : *table_object_->fields()) {
+ ++field_counter;
+
+ if (field_counter == selected_field_index + 1) {
+ VisitFlatbufferField(
+ schema_, field,
+ MutateVisitor{*this, prng, metadata, only_shrink, val});
+ return field_counter;
+ }
+
+ auto base_type = field->type()->base_type();
+ if (base_type == reflection::BaseType::Obj) {
+ auto sub_object = schema_->objects()->Get(field->type()->index());
+ if (sub_object->is_struct()) {
+ field_counter +=
+ GetSubDomain<FlatbuffersStructTag>(field).MutateSelectedField(
+ val[field->id()], prng, metadata, only_shrink,
+ selected_field_index - field_counter);
+ } else {
+ field_counter +=
+ GetSubDomain<FlatbuffersTableTag>(field).MutateSelectedField(
+ val[field->id()], prng, metadata, only_shrink,
+ selected_field_index - field_counter);
+ }
+ }
+
+ if (base_type == reflection::BaseType::Vector ||
+ base_type == reflection::BaseType::Vector64) {
+ auto elem_type = field->type()->element();
+ if (elem_type == reflection::BaseType::Obj) {
+ auto sub_object = schema_->objects()->Get(field->type()->index());
+ if (!sub_object->is_struct()) {
+ field_counter +=
+ GetSubDomain<FlatbuffersVectorTag<FlatbuffersTableTag>>(field)
+ .MutateSelectedField(val[field->id()], prng, metadata,
+ only_shrink,
+ selected_field_index - field_counter);
+ } else {
+ field_counter +=
+ GetSubDomain<FlatbuffersVectorTag<FlatbuffersStructTag>>(field)
+ .MutateSelectedField(val[field->id()], prng, metadata,
+ only_shrink,
+ selected_field_index - field_counter);
+ }
+ } else if (elem_type == reflection::BaseType::Union) {
+ field_counter +=
+ GetSubDomain<FlatbuffersVectorTag<FlatbuffersUnionTag>>(field)
+ .MutateSelectedField(val[field->id()], prng, metadata,
+ only_shrink,
+ selected_field_index - field_counter);
+ }
+ }
+
+ if (base_type == reflection::BaseType::Union) {
+ field_counter +=
+ GetSubDomain<FlatbuffersUnionTag>(field).MutateSelectedField(
+ val[field->id()], prng, metadata, only_shrink,
+ selected_field_index - field_counter);
+ }
+
+ if (field_counter > selected_field_index) {
+ return field_counter;
+ }
+ }
+ return field_counter;
+ }
+
+ auto GetPrinter() const { return Printer{*this}; }
+
+ absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+ for (const auto& [id, field_corpus] : corpus_value) {
+ const reflection::Field* absl_nullable field = GetFieldById(id);
+ if (field == nullptr) continue;
+ absl::Status result;
+ VisitFlatbufferField(schema_, field,
+ ValidateVisitor{*this, field_corpus, result});
+ if (!result.ok()) return result;
+ }
+ return absl::OkStatus();
+ }
+
+ value_type GetValue(const corpus_type& value) const {
+ // Untyped domain does not support GetValue since if it is a nested table
+ // it would need the top level table corpus value to be able to build it.
+ return nullptr;
+ }
+
+ // Converts the table pointer to a corpus value.
+ std::optional<corpus_type> FromValue(const value_type& value) const {
+ if (value == nullptr) {
+ return std::nullopt;
+ }
+ corpus_type ret;
+ for (const auto* field : *table_object_->fields()) {
+ VisitFlatbufferField(schema_, field, FromValueVisitor{*this, value, ret});
+ }
+ return ret;
+ }
+
+ // Converts the IRObject to a corpus value.
+ std::optional<corpus_type> ParseCorpus(const IRObject& obj) const {
+ corpus_type out;
+ auto subs = obj.Subs();
+ if (!subs) {
+ return std::nullopt;
+ }
+ // Follows the structure created by `SerializeCorpus` to deserialize the
+ // IRObject.
+
+ // subs->size() represents the number of fields in the table.
+ out.reserve(subs->size());
+ for (const auto& sub : *subs) {
+ auto pair_subs = sub.Subs();
+ // Each field is represented by a pair of field id and the serialized
+ // corpus value.
+ if (!pair_subs || pair_subs->size() != 2) {
+ return std::nullopt;
+ }
+
+ // Deserialize the field id.
+ auto id = (*pair_subs)[0].GetScalar<typename corpus_type::key_type>();
+ if (!id.has_value()) {
+ return std::nullopt;
+ }
+
+ // Get information about the field from reflection.
+ const reflection::Field* absl_nullable field = GetFieldById(id.value());
+ if (field == nullptr) {
+ return std::nullopt;
+ }
+
+ if (field->type()->base_type() == reflection::BaseType::UType) {
+ // Union types are handled as part of the union field.
+ continue;
+ }
+
+ // Deserialize the field corpus value.
+ std::optional<GenericDomainCorpusType> inner_parsed;
+ VisitFlatbufferField(schema_, field,
+ ParseVisitor{*this, (*pair_subs)[1], inner_parsed});
+ if (!inner_parsed) {
+ return std::nullopt;
+ }
+ out[id.value()] = *std::move(inner_parsed);
+ }
+ return out;
+ }
+
+ // Converts the corpus value to an IRObject.
+ IRObject SerializeCorpus(const corpus_type& value) const {
+ IRObject out;
+ auto& subs = out.MutableSubs();
+ subs.reserve(value.size());
+
+ // Each field is represented by a pair of field id and the serialized
+ // corpus value.
+ for (const auto& [id, field_corpus] : value) {
+ // Get information about the field from reflection.
+ const reflection::Field* absl_nullable field = GetFieldById(id);
+ if (field == nullptr) {
+ continue;
+ }
+ IRObject& pair = subs.emplace_back();
+ auto& pair_subs = pair.MutableSubs();
+ pair_subs.reserve(2);
+
+ // Serialize the field id.
+ pair_subs.emplace_back(field->id());
+
+ // Serialize the field corpus value.
+ VisitFlatbufferField(
+ schema_, field,
+ SerializeVisitor{*this, field_corpus, pair_subs.emplace_back()});
+ }
+ return out;
+ }
+
+ uint32_t BuildTable(const corpus_type& value,
+ flatbuffers::FlatBufferBuilder& builder) const {
+ // Add all the fields to the builder.
+
+ // Offsets is the map of field id to its offset in the table.
+ absl::flat_hash_map<typename corpus_type::key_type, flatbuffers::uoffset_t>
+ offsets;
+
+ // Some fields are stored inline in the flatbuffer table itself (a.k.a
+ // "inline fields") and some are referenced by their offsets (a.k.a. "out of
+ // line fields").
+ //
+ // "Out of line fields" shall be added to the builder first, so that we can
+ // refer to them in the final table.
+ for (const auto& [id, field_corpus] : value) {
+ const reflection::Field* absl_nullable field = GetFieldById(id);
+ if (field == nullptr) {
+ continue;
+ }
+ // Take care of strings, and tables.
+ VisitFlatbufferField(
+ schema_, field,
+ TableFieldBuilderVisitor{*this, builder, offsets, field_corpus});
+ }
+
+ // Now it is time to build the final table.
+ uint32_t table_start = builder.StartTable();
+ for (const auto& [id, field_corpus] : value) {
+ const reflection::Field* absl_nullable field = GetFieldById(id);
+ if (field == nullptr) {
+ continue;
+ }
+
+ // Visit all fields.
+ //
+ // Inline fields will be stored in the table itself, out of line fields
+ // will be referenced by their offsets.
+ VisitFlatbufferField(
+ schema_, field,
+ TableBuilderVisitor{*this, builder, offsets, field_corpus});
+ }
+ return builder.EndTable(table_start);
+ }
+
+ private:
+ const reflection::Schema* absl_nonnull schema_;
+ const reflection::Object* absl_nonnull table_object_;
+ mutable absl::Mutex mutex_;
+ mutable absl::flat_hash_map<typename corpus_type::key_type, CopyableAny>
+ domains_ ABSL_GUARDED_BY(mutex_);
+
+ const reflection::Field* absl_nullable GetFieldById(
+ typename corpus_type::key_type id) const {
+ const auto it =
+ absl::c_find_if(*table_object_->fields(),
+ [id](const auto* field) { return field->id() == id; });
+ return it != table_object_->fields()->end() ? *it : nullptr;
+ }
+
+ struct SerializeVisitor {
+ const FlatbuffersTableUntypedDomainImpl& self;
+ const GenericDomainCorpusType& corpus_value;
+ IRObject& out;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) {
+ out = self.GetSubDomain<T>(field).SerializeCorpus(corpus_value);
+ }
+ };
+
+ struct FromValueVisitor {
+ const FlatbuffersTableUntypedDomainImpl& self;
+ value_type value;
+ corpus_type& out;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) const {
+ [[maybe_unused]]
+ reflection::BaseType base_type = field->type()->base_type();
+ auto& domain = self.GetSubDomain<T>(field);
+ value_type_t<std::decay_t<decltype(domain)>> inner_value;
+
+ if constexpr (is_flatbuffers_enum_tag_v<T>) {
+ FUZZTEST_INTERNAL_CHECK(base_type >= reflection::BaseType::Byte &&
+ base_type <= reflection::BaseType::ULong,
+ "Field must be an enum type.");
+ if (field->optional() && !value->CheckField(field->offset())) {
+ inner_value = std::nullopt;
+ } else {
+ inner_value = std::make_optional(value->GetField<typename T::type>(
+ field->offset(), field->default_integer()));
+ }
+ } else if constexpr (std::is_integral_v<T>) {
+ FUZZTEST_INTERNAL_CHECK(base_type >= reflection::BaseType::Bool &&
+ base_type <= reflection::BaseType::ULong,
+ "Field must be an integer type.");
+ if (field->optional() && !value->CheckField(field->offset())) {
+ inner_value = std::nullopt;
+ } else {
+ inner_value = std::make_optional(
+ value->GetField<T>(field->offset(), field->default_integer()));
+ }
+ } else if constexpr (std::is_floating_point_v<T>) {
+ FUZZTEST_INTERNAL_CHECK(base_type >= reflection::BaseType::Float &&
+ base_type <= reflection::BaseType::Double,
+ "Field must be a floating point type.");
+ if (field->optional() && !value->CheckField(field->offset())) {
+ inner_value = std::nullopt;
+ } else {
+ inner_value = std::make_optional(
+ value->GetField<T>(field->offset(), field->default_real()));
+ }
+ } else if constexpr (std::is_same_v<T, std::string>) {
+ FUZZTEST_INTERNAL_CHECK(base_type == reflection::BaseType::String,
+ "Field must be a string type.");
+ if (!value->CheckField(field->offset())) {
+ inner_value = std::nullopt;
+ } else {
+ inner_value = std::make_optional(
+ value->GetPointer<flatbuffers::String*>(field->offset())->str());
+ }
+ } else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
+ auto sub_object = self.schema_->objects()->Get(field->type()->index());
+ FUZZTEST_INTERNAL_CHECK(
+ base_type == reflection::BaseType::Obj && !sub_object->is_struct(),
+ "Field must be a table type.");
+ inner_value =
+ value->GetPointer<const flatbuffers::Table*>(field->offset());
+ } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+ auto sub_object = self.schema_->objects()->Get(field->type()->index());
+ FUZZTEST_INTERNAL_CHECK(
+ base_type == reflection::BaseType::Obj && sub_object->is_struct(),
+ "Field must be a struct type.");
+ inner_value =
+ value->GetStruct<const flatbuffers::Struct*>(field->offset());
+ } else if constexpr (is_flatbuffers_vector_tag_v<T>) {
+ FUZZTEST_INTERNAL_CHECK(base_type == reflection::BaseType::Vector ||
+ base_type == reflection::BaseType::Vector64,
+ "Field must be a vector type.");
+ if (!value->CheckField(field->offset())) {
+ inner_value = std::nullopt;
+ } else {
+ VisitVector<typename T::value_type, std::decay_t<decltype(domain)>>(
+ field, inner_value);
+ }
+ } else if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) {
+ constexpr char kUnionTypeFieldSuffix[] = "_type";
+ auto enumdef = self.schema_->enums()->Get(field->type()->index());
+ auto type_field = self.table_object_->fields()->LookupByKey(
+ (field->name()->str() + kUnionTypeFieldSuffix).c_str());
+ if (type_field == nullptr) {
+ return;
+ }
+ auto union_type = value->GetField<uint8_t>(type_field->offset(), 0);
+ if (union_type > 0 /* NONE */) {
+ auto enumval = enumdef->values()->LookupByKey(union_type);
+ auto union_object =
+ self.schema_->objects()->Get(enumval->union_type()->index());
+ if (union_object->is_struct()) {
+ auto union_value = value->template GetPointer<flatbuffers::Struct*>(
+ field->offset());
+ inner_value = std::make_pair(union_type, union_value);
+ } else {
+ auto union_value =
+ value->GetPointer<flatbuffers::Table*>(field->offset());
+ inner_value = std::make_pair(union_type, union_value);
+ }
+ }
+ }
+
+ auto inner = domain.FromValue(inner_value);
+ if (inner) {
+ out[field->id()] = std::move(inner.value());
+ }
+ };
+
+ template <typename ElementType, typename Domain>
+ void VisitVector(const reflection::Field* field,
+ value_type_t<Domain>& inner_value) const {
+ if constexpr (std::is_integral_v<ElementType> ||
+ std::is_floating_point_v<ElementType>) {
+ auto vec = value->GetPointer<flatbuffers::Vector<ElementType>*>(
+ field->offset());
+ inner_value = std::make_optional(std::vector<ElementType>());
+ inner_value->reserve(vec->size());
+ for (auto i = 0; i < vec->size(); ++i) {
+ inner_value->push_back(vec->Get(i));
+ }
+ } else if constexpr (is_flatbuffers_enum_tag_v<ElementType>) {
+ using Underlaying = typename ElementType::type;
+ auto vec = value->GetPointer<flatbuffers::Vector<Underlaying>*>(
+ field->offset());
+ inner_value = std::make_optional(std::vector<Underlaying>());
+ inner_value->reserve(vec->size());
+ for (auto i = 0; i < vec->size(); ++i) {
+ inner_value->push_back(vec->Get(i));
+ }
+ } else if constexpr (std::is_same_v<ElementType, std::string>) {
+ auto vec = value->GetPointer<
+ flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>*>(
+ field->offset());
+ inner_value = std::make_optional(std::vector<std::string>());
+ inner_value->reserve(vec->size());
+ for (auto i = 0; i < vec->size(); ++i) {
+ inner_value->push_back(vec->Get(i)->str());
+ }
+ } else if constexpr (std::is_same_v<ElementType, FlatbuffersTableTag>) {
+ auto vec = value->GetPointer<
+ flatbuffers::Vector<flatbuffers::Offset<flatbuffers::Table>>*>(
+ field->offset());
+ inner_value =
+ std::make_optional(std::vector<const flatbuffers::Table*>());
+ inner_value->reserve(vec->size());
+ for (auto i = 0; i < vec->size(); ++i) {
+ inner_value->push_back(vec->Get(i));
+ }
+ } else if constexpr (std::is_same_v<ElementType, FlatbuffersStructTag>) {
+ const reflection::Object* struct_object =
+ self.schema_->objects()->Get(field->type()->index());
+ auto vec =
+ value->template GetPointer<const flatbuffers::Vector<uint8_t>*>(
+ field->offset());
+ if (vec == nullptr) {
+ return;
+ }
+ inner_value =
+ std::make_optional(std::vector<const flatbuffers::Struct*>());
+ inner_value->reserve(vec->size());
+ for (std::remove_pointer_t<decltype(vec)>::size_type i = 0;
+ i < vec->size(); ++i) {
+ const uint8_t* struct_data_ptr =
+ vec->Data() + i * struct_object->bytesize();
+ inner_value->push_back(
+ reinterpret_cast<const flatbuffers::Struct*>(struct_data_ptr));
+ }
+ } else if constexpr (std::is_same_v<ElementType, FlatbuffersUnionTag>) {
+ constexpr char kUnionTypeFieldSuffix[] = "_type";
+ auto type_field = self.table_object_->fields()->LookupByKey(
+ (field->name()->str() + kUnionTypeFieldSuffix).c_str());
+ if (type_field == nullptr) {
+ return;
+ }
+ auto type_vec = value->GetPointer<flatbuffers::Vector<uint8_t>*>(
+ type_field->offset());
+ auto value_vec =
+ value->GetPointer<flatbuffers::Vector<flatbuffers::Offset<void>>*>(
+ field->offset());
+ inner_value = std::make_optional(
+ typename std::decay_t<decltype(inner_value)>::value_type{});
+ inner_value->reserve(value_vec->size());
+ for (auto i = 0; i < value_vec->size(); ++i) {
+ inner_value->push_back(
+ std::make_pair(type_vec->Get(i), value_vec->Get(i)));
+ }
+ }
+ }
+ };
+
+ // Create out-of-line table fields, see `BuildTable` for details.
+ struct TableFieldBuilderVisitor {
+ const FlatbuffersTableUntypedDomainImpl& self;
+ flatbuffers::FlatBufferBuilder& builder;
+ absl::flat_hash_map<typename corpus_type::key_type, flatbuffers::uoffset_t>&
+ offsets;
+ const typename corpus_type::value_type::second_type& corpus_value;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) {
+ if constexpr (std::is_same_v<T, std::string>) {
+ auto& domain = self.GetSubDomain<T>(field);
+ auto user_value = domain.GetValue(corpus_value);
+ if (user_value.has_value()) {
+ auto offset =
+ builder.CreateString(user_value->data(), user_value->size()).o;
+ offsets.insert({field->id(), offset});
+ }
+ } else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
+ FlatbuffersTableUntypedDomainImpl inner_domain(
+ self.schema_, self.schema_->objects()->Get(field->type()->index()));
+ auto opt_corpus =
+ corpus_value
+ .GetAs<std::variant<std::monostate, GenericDomainCorpusType>>();
+ if (std::holds_alternative<GenericDomainCorpusType>(opt_corpus)) {
+ auto inner_corpus = std::get<GenericDomainCorpusType>(opt_corpus)
+ .GetAs<corpus_type>();
+ auto offset = inner_domain.BuildTable(inner_corpus, builder);
+ offsets.insert({field->id(), offset});
+ }
+ } else if constexpr (is_flatbuffers_vector_tag_v<T>) {
+ VisitVector<typename T::value_type>(field, self.GetSubDomain<T>(field));
+ } else if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) {
+ const reflection::Enum* union_type =
+ self.schema_->enums()->Get(field->type()->index());
+ FlatbuffersUnionDomainImpl inner_domain{self.schema_, union_type};
+ auto opt_corpus =
+ corpus_value
+ .GetAs<std::variant<std::monostate, GenericDomainCorpusType>>();
+ if (std::holds_alternative<GenericDomainCorpusType>(opt_corpus)) {
+ auto inner_corpus =
+ std::get<GenericDomainCorpusType>(opt_corpus)
+ .GetAs<corpus_type_t<decltype(inner_domain)>>();
+ auto offset = inner_domain.BuildValue(inner_corpus, builder);
+ if (offset.has_value()) {
+ offsets.insert({field->id(), *offset});
+ }
+ }
+ }
+ }
+
+ private:
+ template <typename Element, typename Domain,
+ std::enable_if_t<std::is_integral_v<Element> ||
+ std::is_floating_point_v<Element> ||
+ is_flatbuffers_enum_tag_v<Element>,
+ int> = 0>
+ void VisitVector(const reflection::Field* field, const Domain& domain) {
+ auto value = domain.GetValue(corpus_value);
+ if (value && (!value->empty() || !field->optional())) {
+ offsets.insert({field->id(), builder.CreateVector(*value).o});
+ } else if (!value && !field->optional()) {
+ // Handle case where value is std::nullopt but field is not optional
+ // Create an empty vector of the appropriate type.
+ if constexpr (is_flatbuffers_enum_tag_v<Element>) {
+ offsets.insert(
+ {field->id(),
+ builder.CreateVector(std::vector<typename Element::type>()).o});
+ } else {
+ offsets.insert(
+ {field->id(), builder.CreateVector(std::vector<Element>()).o});
+ }
+ }
+ }
+
+ template <
+ typename Element, typename Domain,
+ std::enable_if_t<std::is_same_v<Element, FlatbuffersTableTag>, int> = 0>
+ void VisitVector(const reflection::Field* field, const Domain& domain) {
+ auto opt_corpus =
+ corpus_value
+ .GetAs<std::variant<std::monostate, GenericDomainCorpusType>>();
+ if (std::holds_alternative<std::monostate>(opt_corpus)) {
+ return;
+ }
+ auto container_corpus = std::get<GenericDomainCorpusType>(opt_corpus)
+ .GetAs<std::list<corpus_type>>();
+ if (field->optional() && container_corpus.empty()) {
+ return;
+ }
+
+ FlatbuffersTableUntypedDomainImpl inner_domain(
+ self.schema_, self.schema_->objects()->Get(field->type()->index()));
+ std::vector<flatbuffers::Offset<flatbuffers::Table>> vec_offsets;
+ vec_offsets.reserve(container_corpus.size());
+ for (auto& inner_corpus : container_corpus) {
+ auto offset = inner_domain.BuildTable(inner_corpus, builder);
+ vec_offsets.push_back(offset);
+ }
+ offsets.insert({field->id(), builder.CreateVector(vec_offsets).o});
+ }
+
+ template <typename Element, typename Domain,
+ std::enable_if_t<std::is_same_v<Element, FlatbuffersStructTag>,
+ int> = 0>
+ void VisitVector(const reflection::Field* field, const Domain& domain) {
+ auto struct_object = self.schema_->objects()->Get(field->type()->index());
+ auto opt_corpus = corpus_value.template GetAs<
+ std::variant<std::monostate, GenericDomainCorpusType>>();
+ if (std::holds_alternative<std::monostate>(opt_corpus)) {
+ return;
+ }
+ auto container_corpus = std::get<GenericDomainCorpusType>(opt_corpus)
+ .template GetAs<std::list<corpus_type>>();
+ uint8_t* vec_ptr = nullptr;
+ FlatbuffersStructUntypedDomainImpl inner_domain(self.schema_,
+ struct_object);
+ auto vec_offset = builder.CreateUninitializedVector(
+ container_corpus.size(), struct_object->bytesize(),
+ struct_object->minalign(), &vec_ptr);
+ size_t i = 0;
+ for (const auto& inner_corpus : container_corpus) {
+ uint8_t* current_struct_ptr = vec_ptr + i * struct_object->bytesize();
+ inner_domain.BuildValue(inner_corpus, current_struct_ptr);
+ ++i;
+ }
+ offsets.insert({field->id(), vec_offset});
+ }
+
+ template <typename Element, typename Domain,
+ std::enable_if_t<std::is_same_v<Element, std::string>, int> = 0>
+ void VisitVector(const reflection::Field* field, const Domain& domain) {
+ auto value = domain.GetValue(corpus_value);
+ if (!value) {
+ return;
+ }
+ std::vector<flatbuffers::Offset<flatbuffers::String>> vec_offsets;
+ vec_offsets.reserve(value->size());
+ for (const auto& str : *value) {
+ auto offset = builder.CreateString(str);
+ vec_offsets.push_back(offset);
+ }
+ offsets.insert({field->id(), builder.CreateVector(vec_offsets).o});
+ }
+
+ template <
+ typename Element, typename Domain,
+ std::enable_if_t<std::is_same_v<Element, FlatbuffersUnionTag>, int> = 0>
+ void VisitVector(const reflection::Field* field, const Domain& domain) {
+ const reflection::Enum* union_type =
+ self.schema_->enums()->Get(field->type()->index());
+ constexpr char kUnionTypeFieldSuffix[] = "_type";
+ const reflection::Field* type_field =
+ self.table_object_->fields()->LookupByKey(
+ (field->name()->str() + kUnionTypeFieldSuffix).c_str());
+
+ auto opt_corpus =
+ corpus_value
+ .GetAs<std::variant<std::monostate, GenericDomainCorpusType>>();
+ if (std::holds_alternative<std::monostate>(opt_corpus)) {
+ return;
+ }
+ FlatbuffersUnionDomainImpl inner_domain{self.schema_, union_type};
+ auto container_corpus =
+ std::get<GenericDomainCorpusType>(opt_corpus)
+ .GetAs<std::list<corpus_type_t<decltype(inner_domain)>>>();
+
+ std::vector<typename value_type_t<
+ std::decay_t<decltype(inner_domain)>>::first_type>
+ vec_types;
+ vec_types.reserve(container_corpus.size());
+ vec_types.reserve(container_corpus.size());
+ std::vector<flatbuffers::Offset<flatbuffers::Table>> vec_offsets;
+ vec_offsets.reserve(container_corpus.size());
+ vec_offsets.reserve(container_corpus.size());
+ for (auto& inner_corpus : container_corpus) {
+ auto offset = inner_domain.BuildValue(inner_corpus, builder);
+ if (offset.has_value()) {
+ vec_offsets.push_back(*offset);
+ vec_types.push_back(inner_domain.GetType(inner_corpus));
+ }
+ }
+ offsets.insert({field->id(), builder.CreateVector(vec_offsets).o});
+ offsets.insert({type_field->id(), builder.CreateVector(vec_types).o});
+ }
+ };
+
+ // Create complete table: store "inline fields" values inline, and store
+ // just offsets for "out-of-line fields". See `BuildTable` for details.
+ struct TableBuilderVisitor {
+ const FlatbuffersTableUntypedDomainImpl& self;
+ flatbuffers::FlatBufferBuilder& builder;
+ absl::flat_hash_map<typename corpus_type::key_type, flatbuffers::uoffset_t>&
+ offsets;
+ const typename corpus_type::value_type::second_type& corpus_value;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) const {
+ auto size = flatbuffers::GetTypeSize(field->type()->base_type());
+ if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T> ||
+ is_flatbuffers_enum_tag_v<T>) {
+ auto& domain = self.GetSubDomain<T>(field);
+ auto v = domain.GetValue(corpus_value);
+ if (!v) {
+ return;
+ }
+ // Store "inline field" value inline.
+ builder.Align(size);
+ builder.PushBytes(reinterpret_cast<const uint8_t*>(&v), size);
+ builder.TrackField(field->offset(), builder.GetSize());
+ } else if constexpr (std::is_same_v<T, std::string> ||
+ is_flatbuffers_vector_tag_v<T>) {
+ // "Out-of-line field". Store just offset.
+ if constexpr (is_flatbuffers_vector_tag_v<T>) {
+ if constexpr (std::is_same_v<typename T::value_type,
+ FlatbuffersUnionTag>) {
+ constexpr char kUnionTypeFieldSuffix[] = "_type";
+ const reflection::Field* type_field =
+ self.table_object_->fields()->LookupByKey(
+ (field->name()->str() + kUnionTypeFieldSuffix).c_str());
+ if (auto it = offsets.find(type_field->id()); it != offsets.end()) {
+ builder.AddOffset(type_field->offset(),
+ flatbuffers::Offset<>(it->second));
+ }
+ }
+ }
+ if (auto it = offsets.find(field->id()); it != offsets.end()) {
+ builder.AddOffset(
+ field->offset(),
+ flatbuffers::Offset<flatbuffers::String>(it->second));
+ }
+ } else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) {
+ // "Out-of-line field". Store just offset.
+ if (auto it = offsets.find(field->id()); it != offsets.end()) {
+ builder.AddOffset(
+ field->offset(),
+ flatbuffers::Offset<flatbuffers::Table>(it->second));
+ }
+ } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) {
+ FlatbuffersStructUntypedDomainImpl domain(
+ self.schema_, self.schema_->objects()->Get(field->type()->index()));
+ auto opt_corpus = corpus_value.template GetAs<
+ std::variant<std::monostate, GenericDomainCorpusType>>();
+ if (std::holds_alternative<std::monostate>(opt_corpus)) {
+ return;
+ }
+ auto inner_corpus =
+ std::get<GenericDomainCorpusType>(opt_corpus)
+ .template GetAs<corpus_type_t<decltype(domain)>>();
+ auto offset = domain.BuildValue(inner_corpus, builder);
+ if (offset.has_value()) {
+ builder.AddStructOffset(field->offset(), offset.value());
+ }
+ } else if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) {
+ // From flatbuffers documentation:
+ // Unions are encoded as the combination of two fields: an enum
+ // representing the union choice and the offset to the actual element
+ const reflection::Enum* union_type =
+ self.schema_->enums()->Get(field->type()->index());
+ FlatbuffersUnionDomainImpl domain(self.schema_, union_type);
+ if (auto it = offsets.find(field->id()); it != offsets.end()) {
+ // Store just an offset to the actual union element.
+ builder.AddOffset(field->offset(),
+ flatbuffers::Offset<void>(it->second));
+
+ constexpr char kUnionTypeFieldSuffix[] = "_type";
+ const reflection::Field* type_field =
+ self.table_object_->fields()->LookupByKey(
+ (field->name()->str() + kUnionTypeFieldSuffix).c_str());
+ auto opt_corpus = corpus_value.GetAs<
+ std::variant<std::monostate, GenericDomainCorpusType>>();
+ if (std::holds_alternative<std::monostate>(opt_corpus)) {
+ return;
+ }
+ auto inner_corpus = std::get<GenericDomainCorpusType>(opt_corpus)
+ .GetAs<corpus_type_t<decltype(domain)>>();
+ auto type_value = domain.GetType(inner_corpus);
+ auto size = flatbuffers::GetTypeSize(type_field->type()->base_type());
+ // Store the type value inline.
+ builder.Align(size);
+ builder.PushBytes(reinterpret_cast<const uint8_t*>(&type_value),
+ size);
+ builder.TrackField(type_field->offset(), builder.GetSize());
+ }
+ }
+ }
+ };
+
+ struct ParseVisitor {
+ const FlatbuffersTableUntypedDomainImpl& self;
+ const IRObject& obj;
+ std::optional<GenericDomainCorpusType>& out;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) {
+ out = self.GetSubDomain<T>(field).ParseCorpus(obj);
+ }
+ };
+
+ struct ValidateVisitor {
+ const FlatbuffersTableUntypedDomainImpl& self;
+ const GenericDomainCorpusType& corpus_value;
+ absl::Status& out;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) {
+ auto& domain = self.GetSubDomain<T>(field);
+ out = domain.ValidateCorpusValue(corpus_value);
+ if (!out.ok()) {
+ out = Prefix(out, absl::StrCat("Invalid value for field ",
+ field->name()->str()));
+ }
+ }
+ };
+
+ struct InitializeVisitor {
+ FlatbuffersTableUntypedDomainImpl& self;
+ absl::BitGenRef prng;
+ corpus_type& val;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) {
+ auto& domain = self.GetSubDomain<T>(field);
+ val[field->id()] = domain.Init(prng);
+ }
+ };
+
+ struct CountNumberOfFieldsVisitor {
+ const FlatbuffersTableUntypedDomainImpl& self;
+ uint64_t& total_weight;
+ corpus_type& corpus;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) const {
+ // Add the weight of the field itself.
+ total_weight += 1;
+
+ auto domain = self.GetSubDomain<T>(field);
+ if (auto it = corpus.find(field->id()); it != corpus.end()) {
+ // Add the weight of the field corpus.
+ total_weight += domain.CountNumberOfFields(it->second);
+ }
+ }
+ };
+
+ struct MutateVisitor {
+ FlatbuffersTableUntypedDomainImpl& self;
+ absl::BitGenRef prng;
+ const domain_implementor::MutationMetadata& metadata;
+ bool only_shrink;
+ corpus_type& val;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) {
+ auto& domain = self.GetSubDomain<T>(field);
+ if (auto it = val.find(field->id()); it != val.end()) {
+ domain.Mutate(it->second, prng, metadata, only_shrink);
+ } else if (!only_shrink) {
+ val[field->id()] = domain.Init(prng);
+ }
+ }
+ };
+
+ struct Printer {
+ const FlatbuffersTableUntypedDomainImpl& self;
+
+ void PrintCorpusValue(const corpus_type& value,
+ domain_implementor::RawSink out,
+ domain_implementor::PrintMode mode) const {
+ absl::Format(out, "{");
+ bool first = true;
+ for (const auto& [id, field_corpus] : value) {
+ if (!first) {
+ absl::Format(out, ", ");
+ }
+ const reflection::Field* absl_nullable field = self.GetFieldById(id);
+ if (field == nullptr) {
+ absl::Format(out, "<unknown field: %d>", id);
+ } else {
+ VisitFlatbufferField(self.schema_, field,
+ PrinterVisitor{self, field_corpus, out, mode});
+ }
+ first = false;
+ }
+ absl::Format(out, "}");
+ }
+ };
+
+ struct PrinterVisitor {
+ const FlatbuffersTableUntypedDomainImpl& self;
+ const GenericDomainCorpusType& val;
+ domain_implementor::RawSink out;
+ domain_implementor::PrintMode mode;
+
+ template <typename T>
+ void Visit(const reflection::Field* absl_nonnull field) const {
+ auto& domain = self.GetSubDomain<T>(field);
+ absl::Format(out, "%s: ", field->name()->str());
+ domain_implementor::PrintValue(domain, val, out, mode);
+ }
+ };
+};
+
+// Domain implementation for flatbuffers generated table classes.
+// The corpus type is a pair of:
+// - A map of field ids to field values.
+// - The serialized buffer of the table.
+template <typename T>
+class FlatbuffersTableDomainImpl
+ : public domain_implementor::DomainBase<
+ // Derived, for CRTP needs. See DomainBase for more details.
+ FlatbuffersTableDomainImpl<T>,
+ // ValueType - user facing type, exact flatbuffer
+ const T* absl_nonnull,
+ // CorpusType - internal representation of ValueType
+ std::pair<
+ // Map of field ids to field values (note *Untyped*).
+ typename FlatbuffersTableUntypedDomainImpl::corpus_type,
+ // ^^^^^^^
+ // Serialized flatbuffer.
+ std::vector<uint8_t>>> {
+ public:
+ using typename FlatbuffersTableDomainImpl::DomainBase::corpus_type;
+ using typename FlatbuffersTableDomainImpl::DomainBase::value_type;
+
+ FlatbuffersTableDomainImpl() {
+ flatbuffers::Verifier verifier(T::BinarySchema::data(),
+ T::BinarySchema::size());
+ FUZZTEST_INTERNAL_CHECK(reflection::VerifySchemaBuffer(verifier),
+ "Invalid schema for flatbuffers table.");
+ auto schema = reflection::GetSchema(T::BinarySchema::data());
+ auto table_object =
+ schema->objects()->LookupByKey(T::GetFullyQualifiedName());
+ inner_ = FlatbuffersTableUntypedDomainImpl{schema, table_object};
+ }
+
+ FlatbuffersTableDomainImpl(const FlatbuffersTableDomainImpl& other)
+ : inner_(other.inner_) {
+ builder_.Clear();
+ }
+
+ FlatbuffersTableDomainImpl& operator=(
+ const FlatbuffersTableDomainImpl& other) {
+ if (this == &other) return *this;
+ inner_ = other.inner_;
+ builder_.Clear();
+ return *this;
+ }
+
+ FlatbuffersTableDomainImpl(FlatbuffersTableDomainImpl&& other)
+ : inner_(std::move(other.inner_)) {
+ builder_.Clear();
+ }
+
+ FlatbuffersTableDomainImpl& operator=(FlatbuffersTableDomainImpl&& other) {
+ if (this == &other) return *this;
+ inner_ = std::move(other.inner_);
+ builder_.Clear();
+ return *this;
+ }
+
+ // Initializes the table with random values.
+ corpus_type Init(absl::BitGenRef prng) {
+ if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed;
+
+ // Create new map of field ids to field values
+ auto val = inner_->Init(prng);
+ // Serialize the map into a flatbuffer
+ auto offset = inner_->BuildTable(val, builder_);
+ builder_.Finish(flatbuffers::Offset<flatbuffers::Table>(offset));
+ // Store the serialized buffer in a vector.
+ auto buffer =
+ std::vector<uint8_t>(builder_.GetBufferPointer(),
+ builder_.GetBufferPointer() + builder_.GetSize());
+ builder_.Clear();
+
+ // Return corpus value: pair of the map and the serialized buffer.
+ return std::make_pair(val, std::move(buffer));
+ }
+
+ // Returns the number of fields in the table.
+ uint64_t CountNumberOfFields(corpus_type& val) {
+ return inner_->CountNumberOfFields(val.first);
+ }
+
+ // Mutates the given corpus value.
+ void Mutate(corpus_type& val, absl::BitGenRef prng,
+ const domain_implementor::MutationMetadata& metadata,
+ bool only_shrink) {
+ // Modify values in the map.
+ inner_->Mutate(val.first, prng, metadata, only_shrink);
+ // Serialize the map into a flatbuffer and store it in vector
+ val.second = BuildBuffer(val.first);
+ }
+
+ // Converts corpus value into the exact flatbuffer.
+ value_type GetValue(const corpus_type& value) const {
+ return flatbuffers::GetRoot<T>(value.second.data());
+ }
+
+ // Creates corpus value from the exact flatbuffer.
+ std::optional<corpus_type> FromValue(const value_type& value) const {
+ auto val = inner_->FromValue((const flatbuffers::Table*)value);
+ if (!val.has_value()) return std::nullopt;
+ return std::make_optional(std::make_pair(*val, BuildBuffer(*val)));
+ }
+
+ // Returns the printer for the table.
+ auto GetPrinter() const { return Printer{*inner_}; }
+
+ // Returns the parsed corpus value.
+ std::optional<corpus_type> ParseCorpus(const IRObject& obj) const {
+ auto val = inner_->ParseCorpus(obj);
+ if (!val.has_value()) return std::nullopt;
+ return std::make_optional(std::make_pair(*val, BuildBuffer(*val)));
+ }
+
+ // Returns the serialized corpus value.
+ IRObject SerializeCorpus(const corpus_type& corpus_value) const {
+ return inner_->SerializeCorpus(corpus_value.first);
+ }
+
+ // Returns the status of the given corpus value.
+ absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const {
+ return inner_->ValidateCorpusValue(corpus_value.first);
+ }
+
+ private:
+ std::optional<FlatbuffersTableUntypedDomainImpl> inner_;
+ mutable flatbuffers::FlatBufferBuilder builder_;
+
+ struct Printer {
+ const FlatbuffersTableUntypedDomainImpl& inner;
+
+ void PrintCorpusValue(const corpus_type& value,
+ domain_implementor::RawSink out,
+ domain_implementor::PrintMode mode) const {
+ inner.GetPrinter().PrintCorpusValue(value.first, out, mode);
+ }
+ };
+
+ std::vector<uint8_t> BuildBuffer(
+ const typename corpus_type::first_type& val) const {
+ auto offset = inner_->BuildTable(val, builder_);
+ builder_.Finish(flatbuffers::Offset<flatbuffers::Table>(offset));
+ auto buffer =
+ std::vector<uint8_t>(builder_.GetBufferPointer(),
+ builder_.GetBufferPointer() + builder_.GetSize());
+ builder_.Clear();
+ return buffer;
+ }
+};
+
+template <typename T>
+class ArbitraryImpl<T, std::enable_if_t<is_flatbuffers_table_v<T>>>
+ : public FlatbuffersTableDomainImpl<T> {};
+
+} // namespace fuzztest::internal
+#endif // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_FLATBUFFERS_DOMAIN_IMPL_H_
diff --git a/fuzztest/internal/meta.h b/fuzztest/internal/meta.h
index 4ddada1..c36d80b 100644
--- a/fuzztest/internal/meta.h
+++ b/fuzztest/internal/meta.h
@@ -200,6 +200,22 @@
inline constexpr bool is_protocol_buffer_enum_v =
IsProtocolBufferEnumImpl<T>(true);
+template <typename, typename = void>
+inline constexpr bool is_flatbuffers_table_v = false;
+
+// Flatbuffers tables generated structs do not have a public base class, so we
+// check for a few specific methods:
+// - T is a struct.
+// - T has a `Builder` type.
+// - T has a `BinarySchema` type with a static method `data()` (only available
+// when passing `--bfbs-gen-embed` to the flatbuffer compiler).
+// - T has a static method called `GetFullyQualifiedName` (only available when
+// passing `--gen-name-strings` to the flatbuffer compiler).
+template <typename T>
+inline constexpr bool is_flatbuffers_table_v<
+ T, std::void_t<typename T::Builder, decltype(T::BinarySchema::data()),
+ decltype(T::GetFullyQualifiedName())>> = true;
+
template <typename T>
inline constexpr bool has_size_v =
Requires<T>([](auto v) -> decltype(v.size()) {});
diff --git a/fuzztest/internal/test_flatbuffers.fbs b/fuzztest/internal/test_flatbuffers.fbs
new file mode 100644
index 0000000..a34fa4a
--- /dev/null
+++ b/fuzztest/internal/test_flatbuffers.fbs
@@ -0,0 +1,140 @@
+namespace fuzztest.internal;
+
+enum Enum: byte {
+ First,
+ Second,
+ Third
+}
+
+struct BoolStruct {
+ b: bool;
+}
+
+struct DefaultStruct {
+ b: bool;
+ i8: byte;
+ i16: short;
+ i32: int;
+ i64: long;
+ u8: ubyte;
+ u16: ushort;
+ u32: uint;
+ u64: ulong;
+ f: float;
+ d: double;
+ e: Enum;
+ s: BoolStruct;
+}
+
+table BoolTable {
+ b: bool;
+}
+
+table StringTable {
+ str: string;
+}
+
+union Union {
+ BoolTable,
+ StringTable,
+ BoolStruct,
+}
+
+table UnionTable {
+ u: Union;
+}
+
+table DefaultTable {
+ b: bool;
+ i8: byte;
+ i16: short;
+ i32: int;
+ i64: long;
+ u8: ubyte;
+ u16: ushort;
+ u32: uint;
+ u64: ulong;
+ f: float;
+ d: double;
+ str: string;
+ e: Enum;
+ t: BoolTable;
+ u: Union;
+ s: DefaultStruct;
+ v_b: [bool];
+ v_i8: [byte];
+ v_i16: [short];
+ v_i32: [int];
+ v_i64: [long];
+ v_u8: [ubyte];
+ v_u16: [ushort];
+ v_u32: [uint];
+ v_u64: [ulong];
+ v_f: [float];
+ v_d: [double];
+ v_str: [string];
+ v_e: [Enum];
+ v_t: [BoolTable];
+ v_u: [Union];
+ v_s: [DefaultStruct];
+}
+
+table OptionalTable {
+ b: bool = null;
+ i8: byte = null;
+ i16: short = null;
+ i32: int = null;
+ i64: long = null;
+ u8: ubyte = null;
+ u16: ushort = null;
+ u32: uint = null;
+ u64: ulong = null;
+ f: float = null;
+ d: double = null;
+ str: string;
+ e: Enum = null;
+ t: BoolTable;
+ u: Union;
+ s: DefaultStruct;
+ v_b: [bool];
+ v_i8: [byte];
+ v_i16: [short];
+ v_i32: [int];
+ v_i64: [long];
+ v_u8: [ubyte];
+ v_u16: [ushort];
+ v_u32: [uint];
+ v_u64: [ulong];
+ v_f: [float];
+ v_d: [double];
+ v_str: [string];
+ v_e: [Enum];
+ v_t: [BoolTable];
+ v_u: [Union];
+ v_s: [DefaultStruct];
+}
+
+table RequiredTable {
+ str: string (required);
+ t: BoolTable (required);
+ u: Union (required);
+ s: DefaultStruct (required);
+ v_b: [bool] (required);
+ v_i8: [byte] (required);
+ v_i16: [short] (required);
+ v_i32: [int] (required);
+ v_i64: [long] (required);
+ v_u8: [ubyte] (required);
+ v_u16: [ushort] (required);
+ v_u32: [uint] (required);
+ v_u64: [ulong] (required);
+ v_f: [float] (required);
+ v_d: [double] (required);
+ v_str: [string] (required);
+ v_e: [Enum] (required);
+ v_t: [BoolTable] (required);
+ v_u: [Union] (required);
+ v_s: [DefaultStruct] (required);
+}
+
+root_type DefaultTable;