Add array support to flatbuffers domain PiperOrigin-RevId: 759586755
diff --git a/.github/workflows/cmake_test.yml b/.github/workflows/cmake_test.yml index 8ad0fd9..98b1133 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 0d5c514..f9f71df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -16,6 +16,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 8aa515e..f21437e 100644 --- a/MODULE.bazel +++ b/MODULE.bazel
@@ -35,6 +35,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. @@ -48,8 +52,6 @@ name = "protobuf", version = "31.1", ) -# 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 de9323f..d8565c0 100644 --- a/cmake/BuildDependencies.cmake +++ b/cmake/BuildDependencies.cmake
@@ -35,6 +35,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) @@ -64,6 +67,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( @@ -101,3 +112,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/FuzzTestHelpers.cmake b/cmake/FuzzTestHelpers.cmake index 31a7db4..2869a77 100644 --- a/cmake/FuzzTestHelpers.cmake +++ b/cmake/FuzzTestHelpers.cmake
@@ -338,4 +338,191 @@ add_library(fuzztest::${FUZZTEST_PROTO_LIB_NAME} ALIAS ${_NAME}) -endfunction() \ No newline at end of file +endfunction() + +# fuzztest_flatbuffers_generate_headers() +# +# Modified version of `flatbuffers_generate_headers` +# from https://github.com/google/flatbuffers/blob/master/CMake/BuildFlatBuffers.cmake +# Supports having the embedded schema header in the output set. +# +# Creates a target that can be linked against that generates flatbuffer headers. +# +# This function takes a target name and a list of schemas. You can also specify +# other flagc flags using the FLAGS option to change the behavior of the flatc +# tool. +# +# When the target_link_libraries is done within a different directory than +# flatbuffers_generate_headers is called, then the target should also be dependent +# the custom generation target called fuzztest_GENERATE_<TARGET>. +# +# Arguments: +# TARGET: The name of the target to generate. +# SCHEMAS: The list of schema files to generate code for. +# BINARY_SCHEMAS_DIR: Optional. The directory in which to generate binary +# schemas. Binary schemas will only be generated if a path is provided. +# INCLUDE: Optional. Search for includes in the specified paths. (Use this +# instead of "-I <path>" and the FLAGS option so that CMake is aware of +# the directories that need to be searched). +# INCLUDE_PREFIX: Optional. The directory in which to place the generated +# files. Use this instead of the --include-prefix option. +# FLAGS: Optional. A list of any additional flags that you would like to pass +# to flatc. +# TESTONLY: When added, this target will only be built if both +# BUILD_TESTING=ON and FUZZTEST_BUILD_TESTING=ON. +# +# Example: +# +# fuzztest_flatbuffers_library( +# TARGET my_generated_headers_target +# INCLUDE_PREFIX ${MY_INCLUDE_PREFIX}" +# SCHEMAS ${MY_SCHEMA_FILES} +# BINARY_SCHEMAS_DIR "${MY_BINARY_SCHEMA_DIRECTORY}" +# FLAGS --gen-object-api) +# +# target_link_libraries(MyExecutableTarget +# PRIVATE fuzztest_my_generated_headers_target +# ) +# +# Optional (only needed within different directory): +# add_dependencies(app fuzztest_GENERATE_my_generated_headers_target) +function(fuzztest_flatbuffers_generate_headers) + # Parse function arguments. + set(options TESTONLY) + set(one_value_args + "TARGET" + "INCLUDE_PREFIX" + "BINARY_SCHEMAS_DIR") + set(multi_value_args + "SCHEMAS" + "INCLUDE" + "FLAGS") + cmake_parse_arguments( + PARSE_ARGV 0 + FLATBUFFERS_GENERATE_HEADERS + "${options}" + "${one_value_args}" + "${multi_value_args}") + + if(FLATBUFFERS_GENERATE_HEADERS_TESTONLY AND NOT (BUILD_TESTING AND FUZZTEST_BUILD_TESTING)) + return() + endif() + + # Test if including from FindFlatBuffers + if(FLATBUFFERS_FLATC_EXECUTABLE) + set(FLATC_TARGET "") + set(FLATC ${FLATBUFFERS_FLATC_EXECUTABLE}) + elseif(TARGET flatbuffers::flatc) + set(FLATC_TARGET flatbuffers::flatc) + set(FLATC flatbuffers::flatc) + else() + set(FLATC_TARGET flatc) + set(FLATC flatc) + endif() + + set(working_dir "${CMAKE_CURRENT_SOURCE_DIR}") + + # Generate the include files parameters. + set(include_params "") + foreach (include_dir ${FLATBUFFERS_GENERATE_HEADERS_INCLUDE}) + set(include_params -I ${include_dir} ${include_params}) + endforeach() + + # Create a directory to place the generated code. + set(generated_target_dir "${CMAKE_CURRENT_BINARY_DIR}") + set(generated_include_dir "${generated_target_dir}") + if (NOT ${FLATBUFFERS_GENERATE_HEADERS_INCLUDE_PREFIX} STREQUAL "") + set(generated_include_dir "${generated_include_dir}/${FLATBUFFERS_GENERATE_HEADERS_INCLUDE_PREFIX}") + list(APPEND FLATBUFFERS_GENERATE_HEADERS_FLAGS + "--include-prefix" ${FLATBUFFERS_GENERATE_HEADERS_INCLUDE_PREFIX}) + endif() + + set(generated_custom_commands) + + # Create rules to generate the code for each schema. + foreach(schema ${FLATBUFFERS_GENERATE_HEADERS_SCHEMAS}) + get_filename_component(filename ${schema} NAME_WE) + set(generated_include "${generated_include_dir}/${filename}_generated.h") + # Add the embedded schema header in the output set if requested. + if("${FLATBUFFERS_GENERATE_HEADERS_FLAGS}" MATCHES "--bfbs-gen-embed") + list(APPEND generated_include "${generated_include_dir}/${filename}_bfbs_generated.h") + endif() + + # Generate files for grpc if needed + set(generated_source_file) + if("${FLATBUFFERS_GENERATE_HEADERS_FLAGS}" MATCHES "--grpc") + # Check if schema file contain a rpc_service definition + file(STRINGS ${schema} has_grpc REGEX "rpc_service") + if(has_grpc) + list(APPEND generated_include "${generated_include_dir}/${filename}.grpc.fb.h") + set(generated_source_file "${generated_include_dir}/${filename}.grpc.fb.cc") + endif() + endif() + + add_custom_command( + OUTPUT ${generated_include} ${generated_source_file} + COMMAND ${FLATC} ${FLATC_ARGS} + -o ${generated_include_dir} + ${include_params} + -c ${schema} + ${FLATBUFFERS_GENERATE_HEADERS_FLAGS} + DEPENDS ${FLATC_TARGET} ${schema} + WORKING_DIRECTORY "${working_dir}" + COMMENT "Building ${schema} flatbuffers...") + list(APPEND all_generated_header_files ${generated_include}) + list(APPEND all_generated_source_files ${generated_source_file}) + list(APPEND generated_custom_commands "${generated_include}" "${generated_source_file}") + + # Generate the binary flatbuffers schemas if instructed to. + if (NOT ${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR} STREQUAL "") + set(binary_schema + "${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR}/${filename}.bfbs") + add_custom_command( + OUTPUT ${binary_schema} + COMMAND ${FLATC} -b --schema + -o ${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR} + ${include_params} + ${schema} + DEPENDS ${FLATC_TARGET} ${schema} + WORKING_DIRECTORY "${working_dir}") + list(APPEND generated_custom_commands "${binary_schema}") + list(APPEND all_generated_binary_files ${binary_schema}) + endif() + endforeach() + + # Create an additional target as add_custom_command scope is only within same directory (CMakeFile.txt) + set(generate_target fuzztest_GENERATE_${FLATBUFFERS_GENERATE_HEADERS_TARGET}) + add_custom_target(${generate_target} ALL + DEPENDS ${generated_custom_commands} + COMMENT "Generating flatbuffer target fuzztest_${FLATBUFFERS_GENERATE_HEADERS_TARGET}") + + # Set up interface library + add_library(fuzztest_${FLATBUFFERS_GENERATE_HEADERS_TARGET} INTERFACE) + add_dependencies( + fuzztest_${FLATBUFFERS_GENERATE_HEADERS_TARGET} + ${FLATC} + ${FLATBUFFERS_GENERATE_HEADERS_SCHEMAS}) + target_include_directories( + fuzztest_${FLATBUFFERS_GENERATE_HEADERS_TARGET} + INTERFACE ${generated_target_dir}) + + # Organize file layout for IDEs. + source_group( + TREE "${generated_target_dir}" + PREFIX "Flatbuffers/Generated/Headers Files" + FILES ${all_generated_header_files}) + source_group( + TREE "${generated_target_dir}" + PREFIX "Flatbuffers/Generated/Source Files" + FILES ${all_generated_source_files}) + source_group( + TREE ${working_dir} + PREFIX "Flatbuffers/Schemas" + FILES ${FLATBUFFERS_GENERATE_HEADERS_SCHEMAS}) + if (NOT ${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR} STREQUAL "") + source_group( + TREE "${FLATBUFFERS_GENERATE_HEADERS_BINARY_SCHEMAS_DIR}" + PREFIX "Flatbuffers/Generated/Binary Schemas" + FILES ${all_generated_binary_files}) + endif() +endfunction()
diff --git a/cmake/generate_cmake_from_bazel.py b/cmake/generate_cmake_from_bazel.py index 1816c62..86bfd59 100755 --- a/cmake/generate_cmake_from_bazel.py +++ b/cmake/generate_cmake_from_bazel.py
@@ -66,6 +66,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 a2584b1..0f1b7dc 100644 --- a/domain_tests/BUILD +++ b/domain_tests/BUILD
@@ -38,6 +38,25 @@ ) 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:fuzztest_macros", + "@com_google_fuzztest//fuzztest/internal:meta", + "@com_google_fuzztest//fuzztest/internal:serialization", + "@com_google_fuzztest//fuzztest/internal: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 4a858b0..a9f9bb7 100644 --- a/domain_tests/CMakeLists.txt +++ b/domain_tests/CMakeLists.txt
@@ -18,6 +18,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::domain + fuzztest::domain_testing + fuzztest::flatbuffers + fuzztest::fuzztest_macros + GTest::gmock_main + fuzztest_test_flatbuffers_headers + ) + add_dependencies(fuzztest_arbitrary_domains_flatbuffers_test + fuzztest_GENERATE_test_flatbuffers_headers + ) +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..7f5bdf0 --- /dev/null +++ b/domain_tests/arbitrary_domains_flatbuffers_test.cc
@@ -0,0 +1,1312 @@ +// 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 <array> +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <cstring> +#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/array.h" +#include "flatbuffers/base.h" +#include "flatbuffers/buffer.h" +#include "flatbuffers/flatbuffer_builder.h" +#include "flatbuffers/reflection_generated.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/fuzztest_macros.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::ByteEnum; +using ::fuzztest::internal::DefaultStruct; +using ::fuzztest::internal::DefaultTable; +using ::fuzztest::internal::IntEnum; +using ::fuzztest::internal::LongEnum; +using ::fuzztest::internal::OptionalTable; +using ::fuzztest::internal::RequiredTable; +using ::fuzztest::internal::ShortEnum; +using ::fuzztest::internal::StringTable; +using ::fuzztest::internal::UByteEnum; +using ::fuzztest::internal::UIntEnum; +using ::fuzztest::internal::ULongEnum; +using ::fuzztest::internal::UnionTable; +using ::fuzztest::internal::UShortEnum; +using ::testing::_; +using ::testing::AllOf; +using ::testing::Each; +using ::testing::HasSubstr; +using ::testing::IsFalse; +using ::testing::IsTrue; +using ::testing::NotNull; +using ::testing::Pair; +using ::testing::ResultOf; + +template <typename T> +inline bool Eq(const T& lhs, const T& rhs) { + return rhs == lhs; +} +template <typename T> +inline bool Eq(const T* lhs, const T* rhs) { + if (lhs == nullptr && rhs == nullptr) return true; + if (lhs == nullptr || rhs == nullptr) return false; + return Eq(*lhs, *rhs); +} + +template <typename T, uint16_t Size> +inline bool ArrayEq(const flatbuffers::Array<T, Size>* lhs, + const flatbuffers::Array<T, Size>* rhs) { + if (lhs == nullptr && rhs == nullptr) return true; + if (lhs == nullptr || rhs == nullptr) return false; + if (lhs->size() != rhs->size()) return false; + for (int i = 0; i < lhs->size(); ++i) { + if (!Eq(lhs->Get(i), rhs->Get(i))) return false; + } + return true; +} + +template <> +inline bool Eq<BoolStruct>(const BoolStruct& lhs, const BoolStruct& rhs) { + return Eq(lhs.b(), rhs.b()); +} + +template <> +inline bool Eq<DefaultStruct>(const DefaultStruct& lhs, + const DefaultStruct& rhs) { + bool eq_b = lhs.b() == rhs.b(); + bool eq_i8 = lhs.i8() == rhs.i8(); + bool eq_i16 = lhs.i16() == rhs.i16(); + bool eq_i32 = lhs.i32() == rhs.i32(); + bool eq_i64 = lhs.i64() == rhs.i64(); + bool eq_u8 = lhs.u8() == rhs.u8(); + bool eq_u16 = lhs.u16() == rhs.u16(); + bool eq_u32 = lhs.u32() == rhs.u32(); + bool eq_u64 = lhs.u64() == rhs.u64(); + bool eq_f = lhs.f() == rhs.f(); + bool eq_d = lhs.d() == rhs.d(); + bool eq_ei8 = lhs.ei8() == rhs.ei8(); + bool eq_ei16 = lhs.ei16() == rhs.ei16(); + bool eq_ei32 = lhs.ei32() == rhs.ei32(); + bool eq_ei64 = lhs.ei64() == rhs.ei64(); + bool eq_eu8 = lhs.eu8() == rhs.eu8(); + bool eq_eu16 = lhs.eu16() == rhs.eu16(); + bool eq_eu32 = lhs.eu32() == rhs.eu32(); + bool eq_eu64 = lhs.eu64() == rhs.eu64(); + bool eq_s = Eq(lhs.s(), rhs.s()); + bool eq_a_b = ArrayEq(lhs.a_b(), rhs.a_b()); + bool eq_a_i8 = ArrayEq(lhs.a_i8(), rhs.a_i8()); + bool eq_a_i16 = ArrayEq(lhs.a_i16(), rhs.a_i16()); + bool eq_a_i32 = ArrayEq(lhs.a_i32(), rhs.a_i32()); + bool eq_a_i64 = ArrayEq(lhs.a_i64(), rhs.a_i64()); + bool eq_a_u8 = ArrayEq(lhs.a_u8(), rhs.a_u8()); + bool eq_a_u16 = ArrayEq(lhs.a_u16(), rhs.a_u16()); + bool eq_a_u32 = ArrayEq(lhs.a_u32(), rhs.a_u32()); + bool eq_a_u64 = ArrayEq(lhs.a_u64(), rhs.a_u64()); + bool eq_a_f = ArrayEq(lhs.a_f(), rhs.a_f()); + bool eq_a_d = ArrayEq(lhs.a_d(), rhs.a_d()); + bool eq_a_ei8 = ArrayEq(lhs.a_ei8(), rhs.a_ei8()); + bool eq_a_ei16 = ArrayEq(lhs.a_ei16(), rhs.a_ei16()); + bool eq_a_ei32 = ArrayEq(lhs.a_ei32(), rhs.a_ei32()); + bool eq_a_ei64 = ArrayEq(lhs.a_ei64(), rhs.a_ei64()); + bool eq_a_eu8 = ArrayEq(lhs.a_eu8(), rhs.a_eu8()); + bool eq_a_eu16 = ArrayEq(lhs.a_eu16(), rhs.a_eu16()); + bool eq_a_eu32 = ArrayEq(lhs.a_eu32(), rhs.a_eu32()); + bool eq_a_eu64 = ArrayEq(lhs.a_eu64(), rhs.a_eu64()); + bool eq_a_s = ArrayEq(lhs.a_s(), rhs.a_s()); + return eq_b && eq_i8 && eq_i16 && eq_i32 && eq_i64 && eq_u8 && eq_u16 && + eq_u32 && eq_u64 && eq_f && eq_d && eq_ei8 && eq_ei16 && eq_ei32 && + eq_ei64 && eq_eu8 && eq_eu16 && eq_eu32 && eq_eu64 && eq_s && eq_a_b && + eq_a_i8 && eq_a_i16 && eq_a_i32 && eq_a_i64 && eq_a_u8 && eq_a_u16 && + eq_a_u32 && eq_a_u64 && eq_a_f && eq_a_d && eq_a_ei8 && eq_a_ei16 && + eq_a_ei32 && eq_a_ei64 && eq_a_eu8 && eq_a_eu16 && eq_a_eu32 && + eq_a_eu64 && eq_a_s; +} + +template <> +inline bool Eq<flatbuffers::String>(const flatbuffers::String& lhs, + const flatbuffers::String& rhs) { + if (lhs.size() != rhs.size()) return false; + return memcmp(lhs.data(), rhs.data(), lhs.size()) == 0; +} + +template <> +inline bool Eq<BoolTable>(const BoolTable& lhs, const BoolTable& rhs) { + return lhs.b() == rhs.b(); +} + +template <typename T> +inline bool VectorEq(const flatbuffers::Vector<T>& lhs, + const flatbuffers::Vector<T>& rhs) { + if (lhs.size() != rhs.size()) return false; + for (int i = 0; i < lhs.size(); ++i) { + if (!Eq(lhs.Get(i), rhs.Get(i))) return false; + } + return true; +} + +template <> +inline bool Eq<StringTable>(const StringTable& lhs, const StringTable& rhs) { + return Eq(lhs.str(), rhs.str()); +} + +template <> +inline bool Eq<std::pair<uint8_t, const void*>>( + const std::pair<uint8_t, const void*>& lhs, + const std::pair<uint8_t, const void*>& rhs) { + if (lhs.first == internal::Union_NONE && rhs.first == internal::Union_NONE) { + return true; + } + if (lhs.first != rhs.first) return false; + + switch (lhs.first) { + case internal::Union_BoolTable: + return Eq(static_cast<const BoolTable*>(lhs.second), + static_cast<const BoolTable*>(rhs.second)); + case internal::Union_StringTable: + return Eq(static_cast<const StringTable*>(lhs.second), + static_cast<const StringTable*>(rhs.second)); + case internal::Union_BoolStruct: + return Eq(static_cast<const BoolStruct*>(rhs.second), + static_cast<const BoolStruct*>(lhs.second)); + default: + CHECK(false) << "Unsupported union type"; + } +} + +template <typename T> +inline bool VectorEq(const flatbuffers::Vector<T>* lhs, + const flatbuffers::Vector<T>* rhs) { + if (lhs == nullptr && rhs == nullptr) return true; + if (lhs == nullptr || rhs == nullptr) return false; + return VectorEq(*lhs, *rhs); +} + +inline bool VectorUnionEq( + const flatbuffers::Vector<uint8_t>* lhs_type, + const flatbuffers::Vector<::flatbuffers::Offset<void>>* lhs, + const flatbuffers::Vector<uint8_t>* rhs_type, + const flatbuffers::Vector<::flatbuffers::Offset<void>>* rhs) { + if (!VectorEq(lhs_type, rhs_type)) return false; + if (lhs == nullptr && rhs == nullptr) return true; + if (lhs == nullptr || rhs == nullptr) return false; + if (lhs->size() != rhs->size()) return false; + + for (int i = 0; i < lhs->size(); ++i) { + if (!Eq(std::pair(lhs_type->Get(i), lhs->Get(i)), + std::pair(rhs_type->Get(i), rhs->Get(i)))) { + return false; + } + } + return true; +} + +template <> +inline bool Eq<DefaultTable>(const DefaultTable& lhs, const DefaultTable& rhs) { + bool eq_b = lhs.b() == rhs.b(); + bool eq_i8 = lhs.i8() == rhs.i8(); + bool eq_i16 = lhs.i16() == rhs.i16(); + bool eq_i32 = lhs.i32() == rhs.i32(); + bool eq_i64 = lhs.i64() == rhs.i64(); + bool eq_u8 = lhs.u8() == rhs.u8(); + bool eq_u16 = lhs.u16() == rhs.u16(); + bool eq_u32 = lhs.u32() == rhs.u32(); + bool eq_u64 = lhs.u64() == rhs.u64(); + bool eq_f = lhs.f() == rhs.f(); + bool eq_d = lhs.d() == rhs.d(); + bool eq_str = Eq(lhs.str(), rhs.str()); + bool eq_ei8 = lhs.ei8() == rhs.ei8(); + bool eq_ei16 = lhs.ei16() == rhs.ei16(); + bool eq_ei32 = lhs.ei32() == rhs.ei32(); + bool eq_ei64 = lhs.ei64() == rhs.ei64(); + bool eq_eu8 = lhs.eu8() == rhs.eu8(); + bool eq_eu16 = lhs.eu16() == rhs.eu16(); + bool eq_eu32 = lhs.eu32() == rhs.eu32(); + bool eq_eu64 = lhs.eu64() == rhs.eu64(); + bool eq_t = Eq(lhs.t(), rhs.t()); + bool eq_u = Eq(std::pair(static_cast<uint8_t>(lhs.u_type()), lhs.u()), + std::pair(static_cast<uint8_t>(rhs.u_type()), rhs.u())); + bool eq_s = Eq(lhs.s(), rhs.s()); + bool eq_v_b = VectorEq(lhs.v_b(), rhs.v_b()); + bool eq_v_i8 = VectorEq(lhs.v_i8(), rhs.v_i8()); + bool eq_v_i16 = VectorEq(lhs.v_i16(), rhs.v_i16()); + bool eq_v_i32 = VectorEq(lhs.v_i32(), rhs.v_i32()); + bool eq_v_i64 = VectorEq(lhs.v_i64(), rhs.v_i64()); + bool eq_v_u8 = VectorEq(lhs.v_u8(), rhs.v_u8()); + bool eq_v_u16 = VectorEq(lhs.v_u16(), rhs.v_u16()); + bool eq_v_u32 = VectorEq(lhs.v_u32(), rhs.v_u32()); + bool eq_v_u64 = VectorEq(lhs.v_u64(), rhs.v_u64()); + bool eq_v_f = VectorEq(lhs.v_f(), rhs.v_f()); + bool eq_v_d = VectorEq(lhs.v_d(), rhs.v_d()); + bool eq_v_str = VectorEq(lhs.v_str(), rhs.v_str()); + bool eq_v_ei8 = VectorEq(lhs.v_ei8(), rhs.v_ei8()); + bool eq_v_ei16 = VectorEq(lhs.v_ei16(), rhs.v_ei16()); + bool eq_v_ei32 = VectorEq(lhs.v_ei32(), rhs.v_ei32()); + bool eq_v_ei64 = VectorEq(lhs.v_ei64(), rhs.v_ei64()); + bool eq_v_eu8 = VectorEq(lhs.v_eu8(), rhs.v_eu8()); + bool eq_v_eu16 = VectorEq(lhs.v_eu16(), rhs.v_eu16()); + bool eq_v_eu32 = VectorEq(lhs.v_eu32(), rhs.v_eu32()); + bool eq_v_eu64 = VectorEq(lhs.v_eu64(), rhs.v_eu64()); + bool eq_v_t = VectorEq(lhs.v_t(), rhs.v_t()); + bool eq_v_u_type = VectorEq(lhs.v_u_type(), rhs.v_u_type()); + bool eq_v_u = + VectorUnionEq(lhs.v_u_type(), lhs.v_u(), rhs.v_u_type(), rhs.v_u()); + bool eq_v_s = VectorEq(lhs.v_s(), rhs.v_s()); + return eq_b && eq_i8 && eq_i16 && eq_i32 && eq_i64 && eq_u8 && eq_u16 && + eq_u32 && eq_u64 && eq_f && eq_d && eq_str && eq_ei8 && eq_ei16 && + eq_ei32 && eq_ei64 && eq_eu8 && eq_eu16 && eq_eu32 && eq_eu64 && + eq_t && eq_u && eq_s && eq_v_b && eq_v_i8 && eq_v_i16 && eq_v_i32 && + eq_v_i64 && eq_v_u8 && eq_v_u16 && eq_v_u32 && eq_v_u64 && eq_v_f && + eq_v_d && eq_v_str && eq_v_ei8 && eq_v_ei16 && eq_v_ei32 && + eq_v_ei64 && eq_v_eu8 && eq_v_eu16 && eq_v_eu32 && eq_v_eu64 && + eq_v_t && eq_v_u_type && eq_v_u && eq_v_s; +} + +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::ByteEnum_First, // ei8 + internal::ShortEnum_First, // ei16 + internal::IntEnum_First, // ei32 + internal::LongEnum_First, // ei64 + internal::UByteEnum_First, // eu8 + internal::UShortEnum_First, // eu16 + internal::UIntEnum_First, // eu32 + internal::ULongEnum_First, // eu64 + BoolStruct{true, std::array<uint8_t, 2>{true, false}}, // s + std::array<uint8_t, 2>{true, false}, // a_b + std::array<int8_t, 2>{11, 12}, // a_i8 + std::array<int16_t, 2>{13, 14}, // a_i16 + std::array<int32_t, 2>{15, 16}, // a_i32 + std::array<int64_t, 2>{17, 18}, // a_i64 + std::array<uint8_t, 2>{19, 20}, // a_u8 + std::array<uint16_t, 2>{21, 22}, // a_u16 + std::array<uint32_t, 2>{23, 24}, // a_u32 + std::array<uint64_t, 2>{25, 26}, // a_u64 + std::array<float, 2>{27.0f, 28.0f}, // a_f + std::array<double, 2>{29.0, 30.0}, // a_d + std::array<internal::ByteEnum, 2>{internal::ByteEnum_First, + internal::ByteEnum_Second}, // a_ei8 + std::array<internal::ShortEnum, 2>{internal::ShortEnum_First, + internal::ShortEnum_Second}, // a_ei16 + std::array<internal::IntEnum, 2>{internal::IntEnum_First, + internal::IntEnum_Second}, // a_ei32 + std::array<internal::LongEnum, 2>{internal::LongEnum_First, + internal::LongEnum_Second}, // a_ei64 + std::array<internal::UByteEnum, 2>{internal::UByteEnum_First, + internal::UByteEnum_Second}, // a_eu8 + std::array<internal::UShortEnum, 2>{ + internal::UShortEnum_First, internal::UShortEnum_Second}, // a_eu16 + std::array<internal::UIntEnum, 2>{internal::UIntEnum_First, + internal::UIntEnum_Second}, // a_eu32 + std::array<internal::ULongEnum, 2>{internal::ULongEnum_First, + internal::ULongEnum_Second}, // a_eu64 + std::array<BoolStruct, 2>{ + BoolStruct(true, std::array<uint8_t, 2>{true, false}), + BoolStruct(false, std::array<uint8_t, 2>{false, true})} // a_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<ByteEnum>> v_ei8{ + internal::ByteEnum_First, internal::ByteEnum_Second}; + std::vector<std::underlying_type_t<ShortEnum>> v_ei16{ + internal::ShortEnum_First, internal::ShortEnum_Second}; + std::vector<std::underlying_type_t<IntEnum>> v_ei32{internal::IntEnum_First, + internal::IntEnum_Second}; + std::vector<std::underlying_type_t<LongEnum>> v_ei64{ + internal::LongEnum_First, internal::LongEnum_Second}; + std::vector<std::underlying_type_t<UByteEnum>> v_eu8{ + internal::UByteEnum_First, internal::UByteEnum_Second}; + std::vector<std::underlying_type_t<UShortEnum>> v_eu16{ + internal::UShortEnum_First, internal::UShortEnum_Second}; + std::vector<std::underlying_type_t<UIntEnum>> v_eu32{ + internal::UIntEnum_First, internal::UIntEnum_Second}; + std::vector<std::underlying_type_t<ULongEnum>> v_eu64{ + internal::ULongEnum_First, internal::ULongEnum_Second}; + 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, + /*b=*/true, + /*i8=*/1, + /*i16=*/2, + /*i32=*/3, + /*i64=*/4, + /*u8=*/5, + /*u16=*/6, + /*u32=*/7, + /*u64=*/8, + /*f=*/9.0, + /*d=*/10.0, + /*str=*/"foo bar baz", + /*ei8=*/internal::ByteEnum_Second, + /*ei16=*/internal::ShortEnum_Second, + /*ei32=*/internal::IntEnum_Second, + /*ei64=*/internal::LongEnum_Second, + /*eu8=*/internal::UByteEnum_Second, + /*eu16=*/internal::UShortEnum_Second, + /*eu32=*/internal::UIntEnum_Second, + /*eu64=*/internal::ULongEnum_Second, + /*t=*/bool_table_offset, + /*u_type=*/internal::Union_BoolTable, + /*u=*/bool_table_offset.Union(), + /*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_ei8=*/&v_ei8, + /*v_ei16=*/&v_ei16, + /*v_ei32=*/&v_ei32, + /*v_ei64=*/&v_ei64, + /*v_eu8=*/&v_eu8, + /*v_eu16=*/&v_eu16, + /*v_eu32=*/&v_eu32, + /*v_eu64=*/&v_eu64, + /*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()); +} + +template <typename Domain> +std::vector<typename Domain::corpus_type> GenerateNonUniqueCorpusValues( + Domain domain, int num_seeds = 10, int num_mutations = 100, + const domain_implementor::MutationMetadata& metadata = {}, + bool only_shrink = false) { + using CorpusT = typename Domain::corpus_type; + absl::BitGen bitgen; + + std::vector<CorpusT> seeds; + seeds.reserve(num_seeds); + while (seeds.size() < num_seeds) { + seeds.push_back(domain.Init(bitgen)); + } + + std::vector<CorpusT> values = seeds; + + for (const auto& seed : seeds) { + CorpusT value = seed; + std::vector<CorpusT> mutations; + mutations.reserve(num_mutations); + while (mutations.size() < num_mutations) { + domain.Mutate(value, bitgen, metadata, only_shrink); + mutations.push_back(value); + } + values.insert(values.end(), mutations.begin(), mutations.end()); + } + + return values; +}; + +template <typename Domain> +std::vector<typename Domain::corpus_type> GenerateInitialCorpusValues( + Domain domain, int n) { + std::vector<typename Domain::corpus_type> values; + absl::BitGen bitgen; + values.reserve(n); + for (int i = 0; i < n; ++i) { + values.push_back(domain.Init(bitgen)); + } + return values; +} + +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<const 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->ei8(), internal::ByteEnum_Second); + EXPECT_EQ(new_table->ei16(), internal::ShortEnum_Second); + EXPECT_EQ(new_table->ei32(), internal::IntEnum_Second); + EXPECT_EQ(new_table->ei64(), internal::LongEnum_Second); + EXPECT_EQ(new_table->eu8(), internal::UByteEnum_Second); + EXPECT_EQ(new_table->eu16(), internal::UShortEnum_Second); + EXPECT_EQ(new_table->eu32(), internal::UIntEnum_Second); + EXPECT_EQ(new_table->eu64(), internal::ULongEnum_Second); + ASSERT_THAT(new_table->t(), NotNull()); + EXPECT_EQ(new_table->t()->b(), true); + EXPECT_EQ(new_table->u_type(), internal::Union_BoolTable); + EXPECT_EQ(new_table->u_as_BoolTable()->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()->ei8(), internal::ByteEnum_First); + EXPECT_EQ(new_table->s()->ei16(), internal::ShortEnum_First); + EXPECT_EQ(new_table->s()->ei32(), internal::IntEnum_First); + EXPECT_EQ(new_table->s()->ei64(), internal::LongEnum_First); + EXPECT_EQ(new_table->s()->eu8(), internal::UByteEnum_First); + EXPECT_EQ(new_table->s()->eu16(), internal::UShortEnum_First); + EXPECT_EQ(new_table->s()->eu32(), internal::UIntEnum_First); + EXPECT_EQ(new_table->s()->eu64(), internal::ULongEnum_First); + EXPECT_EQ(new_table->s()->s().b(), true); + EXPECT_EQ(new_table->s()->s().a_b()->size(), 2); + EXPECT_EQ(new_table->s()->s().a_b()->Get(0), true); + EXPECT_EQ(new_table->s()->s().a_b()->Get(1), false); + EXPECT_EQ(new_table->s()->a_b()->size(), 2); + EXPECT_EQ(new_table->s()->a_b()->Get(0), true); + EXPECT_EQ(new_table->s()->a_b()->Get(1), false); + EXPECT_EQ(new_table->s()->a_i8()->size(), 2); + EXPECT_EQ(new_table->s()->a_i8()->Get(0), 11); + EXPECT_EQ(new_table->s()->a_i8()->Get(1), 12); + EXPECT_EQ(new_table->s()->a_i16()->size(), 2); + EXPECT_EQ(new_table->s()->a_i16()->Get(0), 13); + EXPECT_EQ(new_table->s()->a_i16()->Get(1), 14); + EXPECT_EQ(new_table->s()->a_i32()->size(), 2); + EXPECT_EQ(new_table->s()->a_i32()->Get(0), 15); + EXPECT_EQ(new_table->s()->a_i32()->Get(1), 16); + EXPECT_EQ(new_table->s()->a_i64()->size(), 2); + EXPECT_EQ(new_table->s()->a_i64()->Get(0), 17); + EXPECT_EQ(new_table->s()->a_i64()->Get(1), 18); + EXPECT_EQ(new_table->s()->a_u8()->size(), 2); + EXPECT_EQ(new_table->s()->a_u8()->Get(0), 19); + EXPECT_EQ(new_table->s()->a_u8()->Get(1), 20); + EXPECT_EQ(new_table->s()->a_u16()->size(), 2); + EXPECT_EQ(new_table->s()->a_u16()->Get(0), 21); + EXPECT_EQ(new_table->s()->a_u16()->Get(1), 22); + EXPECT_EQ(new_table->s()->a_u32()->size(), 2); + EXPECT_EQ(new_table->s()->a_u32()->Get(0), 23); + EXPECT_EQ(new_table->s()->a_u32()->Get(1), 24); + EXPECT_EQ(new_table->s()->a_u64()->size(), 2); + EXPECT_EQ(new_table->s()->a_u64()->Get(0), 25); + EXPECT_EQ(new_table->s()->a_u64()->Get(1), 26); + EXPECT_EQ(new_table->s()->a_f()->size(), 2); + EXPECT_EQ(new_table->s()->a_f()->Get(0), 27); + EXPECT_EQ(new_table->s()->a_f()->Get(1), 28); + EXPECT_EQ(new_table->s()->a_d()->size(), 2); + EXPECT_EQ(new_table->s()->a_d()->Get(0), 29); + EXPECT_EQ(new_table->s()->a_d()->Get(1), 30); + EXPECT_EQ(new_table->s()->a_ei8()->size(), 2); + EXPECT_EQ(new_table->s()->a_ei8()->Get(0), internal::ByteEnum_First); + EXPECT_EQ(new_table->s()->a_ei8()->Get(1), internal::ByteEnum_Second); + EXPECT_EQ(new_table->s()->a_ei16()->size(), 2); + EXPECT_EQ(new_table->s()->a_ei16()->Get(0), internal::ShortEnum_First); + EXPECT_EQ(new_table->s()->a_ei16()->Get(1), internal::ShortEnum_Second); + EXPECT_EQ(new_table->s()->a_ei32()->size(), 2); + EXPECT_EQ(new_table->s()->a_ei32()->Get(0), internal::IntEnum_First); + EXPECT_EQ(new_table->s()->a_ei32()->Get(1), internal::IntEnum_Second); + EXPECT_EQ(new_table->s()->a_ei64()->size(), 2); + EXPECT_EQ(new_table->s()->a_ei64()->Get(0), internal::LongEnum_First); + EXPECT_EQ(new_table->s()->a_ei64()->Get(1), internal::LongEnum_Second); + EXPECT_EQ(new_table->s()->a_eu8()->size(), 2); + EXPECT_EQ(new_table->s()->a_eu8()->Get(0), internal::UByteEnum_First); + EXPECT_EQ(new_table->s()->a_eu8()->Get(1), internal::UByteEnum_Second); + EXPECT_EQ(new_table->s()->a_eu16()->size(), 2); + EXPECT_EQ(new_table->s()->a_eu16()->Get(0), internal::UShortEnum_First); + EXPECT_EQ(new_table->s()->a_eu16()->Get(1), internal::UShortEnum_Second); + EXPECT_EQ(new_table->s()->a_eu32()->size(), 2); + EXPECT_EQ(new_table->s()->a_eu32()->Get(0), internal::UIntEnum_First); + EXPECT_EQ(new_table->s()->a_eu32()->Get(1), internal::UIntEnum_Second); + EXPECT_EQ(new_table->s()->a_eu64()->size(), 2); + EXPECT_EQ(new_table->s()->a_eu64()->Get(0), internal::ULongEnum_First); + EXPECT_EQ(new_table->s()->a_eu64()->Get(1), internal::ULongEnum_Second); + EXPECT_EQ(new_table->s()->a_s()->size(), 2); + EXPECT_EQ(new_table->s()->a_s()->Get(0)->b(), true); + EXPECT_EQ(new_table->s()->a_s()->Get(0)->a_b()->size(), 2); + EXPECT_EQ(new_table->s()->a_s()->Get(0)->a_b()->Get(0), true); + EXPECT_EQ(new_table->s()->a_s()->Get(0)->a_b()->Get(1), false); + EXPECT_EQ(new_table->s()->a_s()->Get(1)->b(), false); + EXPECT_EQ(new_table->s()->a_s()->Get(1)->a_b()->size(), 2); + EXPECT_EQ(new_table->s()->a_s()->Get(1)->a_b()->Get(0), false); + EXPECT_EQ(new_table->s()->a_s()->Get(1)->a_b()->Get(1), true); + 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_ei8(), NotNull()); + EXPECT_EQ(new_table->v_ei8()->size(), 2); + EXPECT_EQ(new_table->v_ei8()->Get(0), internal::ByteEnum_First); + EXPECT_EQ(new_table->v_ei8()->Get(1), internal::ByteEnum_Second); + ASSERT_THAT(new_table->v_ei16(), NotNull()); + EXPECT_EQ(new_table->v_ei16()->size(), 2); + EXPECT_EQ(new_table->v_ei16()->Get(0), internal::ShortEnum_First); + EXPECT_EQ(new_table->v_ei16()->Get(1), internal::ShortEnum_Second); + ASSERT_THAT(new_table->v_ei32(), NotNull()); + EXPECT_EQ(new_table->v_ei32()->size(), 2); + EXPECT_EQ(new_table->v_ei32()->Get(0), internal::IntEnum_First); + EXPECT_EQ(new_table->v_ei32()->Get(1), internal::IntEnum_Second); + ASSERT_THAT(new_table->v_ei64(), NotNull()); + EXPECT_EQ(new_table->v_ei64()->size(), 2); + EXPECT_EQ(new_table->v_ei64()->Get(0), internal::LongEnum_First); + EXPECT_EQ(new_table->v_ei64()->Get(1), internal::LongEnum_Second); + ASSERT_THAT(new_table->v_eu8(), NotNull()); + EXPECT_EQ(new_table->v_eu8()->size(), 2); + EXPECT_EQ(new_table->v_eu8()->Get(0), internal::UByteEnum_First); + EXPECT_EQ(new_table->v_eu8()->Get(1), internal::UByteEnum_Second); + ASSERT_THAT(new_table->v_eu16(), NotNull()); + EXPECT_EQ(new_table->v_eu16()->size(), 2); + EXPECT_EQ(new_table->v_eu16()->Get(0), internal::UShortEnum_First); + EXPECT_EQ(new_table->v_eu16()->Get(1), internal::UShortEnum_Second); + ASSERT_THAT(new_table->v_eu32(), NotNull()); + EXPECT_EQ(new_table->v_eu32()->size(), 2); + EXPECT_EQ(new_table->v_eu32()->Get(0), internal::UIntEnum_First); + EXPECT_EQ(new_table->v_eu32()->Get(1), internal::UIntEnum_Second); + 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); + auto v_s_0 = new_table->v_s()->Get(0); + ASSERT_THAT(v_s_0, NotNull()); + EXPECT_EQ(v_s_0->b(), true); + EXPECT_EQ(v_s_0->i8(), 1); + EXPECT_EQ(v_s_0->i16(), 2); + EXPECT_EQ(v_s_0->i32(), 3); + EXPECT_EQ(v_s_0->i64(), 4); + EXPECT_EQ(v_s_0->u8(), 5); + EXPECT_EQ(v_s_0->u16(), 6); + EXPECT_EQ(v_s_0->u32(), 7); + EXPECT_EQ(v_s_0->u64(), 8); + EXPECT_EQ(v_s_0->f(), 9.0); + EXPECT_EQ(v_s_0->d(), 10.0); + EXPECT_EQ(v_s_0->ei8(), internal::ByteEnum_First); + EXPECT_EQ(v_s_0->ei16(), internal::ShortEnum_First); + EXPECT_EQ(v_s_0->ei32(), internal::IntEnum_First); + EXPECT_EQ(v_s_0->ei64(), internal::LongEnum_First); + EXPECT_EQ(v_s_0->eu8(), internal::UByteEnum_First); + EXPECT_EQ(v_s_0->eu16(), internal::UShortEnum_First); + EXPECT_EQ(v_s_0->eu32(), internal::UIntEnum_First); + EXPECT_EQ(v_s_0->eu64(), internal::ULongEnum_First); + EXPECT_EQ(v_s_0->s().b(), true); + EXPECT_EQ(v_s_0->s().a_b()->size(), 2); + EXPECT_EQ(v_s_0->s().a_b()->Get(0), true); + EXPECT_EQ(v_s_0->s().a_b()->Get(1), false); + EXPECT_EQ(v_s_0->a_b()->size(), 2); + EXPECT_EQ(v_s_0->a_b()->Get(0), true); + EXPECT_EQ(v_s_0->a_b()->Get(1), false); + EXPECT_EQ(v_s_0->a_i8()->size(), 2); + EXPECT_EQ(v_s_0->a_i8()->Get(0), 11); + EXPECT_EQ(v_s_0->a_i8()->Get(1), 12); + EXPECT_EQ(v_s_0->a_i16()->size(), 2); + EXPECT_EQ(v_s_0->a_i16()->Get(0), 13); + EXPECT_EQ(v_s_0->a_i16()->Get(1), 14); + EXPECT_EQ(v_s_0->a_i32()->size(), 2); + EXPECT_EQ(v_s_0->a_i32()->Get(0), 15); + EXPECT_EQ(v_s_0->a_i32()->Get(1), 16); + EXPECT_EQ(v_s_0->a_i64()->size(), 2); + EXPECT_EQ(v_s_0->a_i64()->Get(0), 17); + EXPECT_EQ(v_s_0->a_i64()->Get(1), 18); + EXPECT_EQ(v_s_0->a_u8()->size(), 2); + EXPECT_EQ(v_s_0->a_u8()->Get(0), 19); + EXPECT_EQ(v_s_0->a_u8()->Get(1), 20); + EXPECT_EQ(v_s_0->a_u16()->size(), 2); + EXPECT_EQ(v_s_0->a_u16()->Get(0), 21); + EXPECT_EQ(v_s_0->a_u16()->Get(1), 22); + EXPECT_EQ(v_s_0->a_u32()->size(), 2); + EXPECT_EQ(v_s_0->a_u32()->Get(0), 23); + EXPECT_EQ(v_s_0->a_u32()->Get(1), 24); + EXPECT_EQ(v_s_0->a_u64()->size(), 2); + EXPECT_EQ(v_s_0->a_u64()->Get(0), 25); + EXPECT_EQ(v_s_0->a_f()->size(), 2); + EXPECT_EQ(v_s_0->a_f()->Get(0), 27); + EXPECT_EQ(v_s_0->a_f()->Get(1), 28); + EXPECT_EQ(v_s_0->a_d()->size(), 2); + EXPECT_EQ(v_s_0->a_d()->Get(0), 29); + EXPECT_EQ(v_s_0->a_d()->Get(1), 30); + EXPECT_EQ(v_s_0->a_ei8()->size(), 2); + EXPECT_EQ(v_s_0->a_ei8()->Get(0), internal::ByteEnum_First); + EXPECT_EQ(v_s_0->a_ei8()->Get(1), internal::ByteEnum_Second); + EXPECT_EQ(v_s_0->a_ei16()->size(), 2); + EXPECT_EQ(v_s_0->a_ei16()->Get(0), internal::ShortEnum_First); + EXPECT_EQ(v_s_0->a_ei16()->Get(1), internal::ShortEnum_Second); + EXPECT_EQ(v_s_0->a_ei32()->size(), 2); + EXPECT_EQ(v_s_0->a_ei32()->Get(0), internal::IntEnum_First); + EXPECT_EQ(v_s_0->a_ei32()->Get(1), internal::IntEnum_Second); + EXPECT_EQ(v_s_0->a_ei64()->size(), 2); + EXPECT_EQ(v_s_0->a_ei64()->Get(0), internal::LongEnum_First); + EXPECT_EQ(v_s_0->a_ei64()->Get(1), internal::LongEnum_Second); + EXPECT_EQ(v_s_0->a_eu8()->size(), 2); + EXPECT_EQ(v_s_0->a_eu8()->Get(0), internal::UByteEnum_First); + EXPECT_EQ(v_s_0->a_eu8()->Get(1), internal::UByteEnum_Second); + EXPECT_EQ(v_s_0->a_eu16()->size(), 2); + EXPECT_EQ(v_s_0->a_eu16()->Get(0), internal::UShortEnum_First); + EXPECT_EQ(v_s_0->a_eu16()->Get(1), internal::UShortEnum_Second); + EXPECT_EQ(v_s_0->a_eu32()->size(), 2); + EXPECT_EQ(v_s_0->a_eu32()->Get(0), internal::UIntEnum_First); + EXPECT_EQ(v_s_0->a_eu32()->Get(1), internal::UIntEnum_Second); + EXPECT_EQ(v_s_0->a_eu64()->size(), 2); + EXPECT_EQ(v_s_0->a_eu64()->Get(0), internal::ULongEnum_First); + EXPECT_EQ(v_s_0->a_eu64()->Get(1), internal::ULongEnum_Second); + EXPECT_EQ(v_s_0->a_s()->size(), 2); + EXPECT_EQ(v_s_0->a_s()->Get(0)->b(), true); + EXPECT_EQ(v_s_0->a_s()->Get(0)->a_b()->size(), 2); + EXPECT_EQ(v_s_0->a_s()->Get(0)->a_b()->Get(0), true); + EXPECT_EQ(v_s_0->a_s()->Get(0)->a_b()->Get(1), false); + EXPECT_EQ(v_s_0->a_s()->Get(1)->b(), false); + EXPECT_EQ(v_s_0->a_s()->Get(1)->a_b()->size(), 2); + EXPECT_EQ(v_s_0->a_s()->Get(1)->a_b()->Get(0), false); + EXPECT_EQ(v_s_0->a_s()->Get(1)->a_b()->Get(1), true); +} + +TEST(FlatbuffersTableDomainImplTest, InitGeneratesSeeds) { + flatbuffers::FlatBufferBuilder fbb; + auto table = CreateDefaultTable(fbb); + + auto domain = Arbitrary<const DefaultTable*>().WithSeeds({table}); + + EXPECT_THAT(GenerateInitialCorpusValues(domain, IterationsToHitAll(1, 0.5)), + Contains(ResultOf( + [table, &domain]( + const typename decltype(domain)::corpus_type& corpus) { + return Eq(domain.GetValue(corpus), table); + }, + IsTrue()))); +} + +TEST(FlatbuffersTableDomainImplTest, CanMutateAnyTableField) { + 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}, + {"ei8", false}, {"ei16", false}, {"ei32", false}, + {"ei64", false}, {"eu8", false}, {"eu16", false}, + {"eu32", false}, {"eu64", false}, {"t", false}, + {"u_type", false}, {"u", false}, {"s", false}, + {"v_b", false}, {"v_i8", false}, {"v_i16", false}, + {"v_i32", false}, {"v_i64", false}, {"v_u8", false}, + {"v_u16", false}, {"v_u32", false}, {"v_u64", false}, + {"v_f", false}, {"v_d", false}, {"v_str", false}, + {"v_ei8", false}, {"v_ei16", false}, {"v_ei32", false}, + {"v_ei64", false}, {"v_eu8", false}, {"v_eu16", false}, + {"v_eu32", false}, {"v_eu64", false}, {"v_t", false}, + {"v_u_type", false}, {"v_u", false}, {"v_s", false}, + }; + + auto domain = Arbitrary<const DefaultTable*>(); + + absl::BitGen bitgen; + for (size_t i = 0; i < IterationsToHitAll(mutated_fields.size(), + 1.0 / mutated_fields.size()); + ++i) { + Value initial_val(domain, bitgen); + Value val(initial_val); + val.Mutate(domain, bitgen, {}, false); + const auto& mut = val.user_value; + const auto& init = initial_val.user_value; + + mutated_fields["b"] |= mut->b() != init->b(); + mutated_fields["i8"] |= mut->i8() != init->i8(); + mutated_fields["i16"] |= mut->i16() != init->i16(); + mutated_fields["i32"] |= mut->i32() != init->i32(); + mutated_fields["i64"] |= mut->i64() != init->i64(); + mutated_fields["u8"] |= mut->u8() != init->u8(); + mutated_fields["u16"] |= mut->u16() != init->u16(); + mutated_fields["u32"] |= mut->u32() != init->u32(); + mutated_fields["u64"] |= mut->u64() != init->u64(); + mutated_fields["f"] |= mut->f() != init->f(); + mutated_fields["d"] |= mut->d() != init->d(); + mutated_fields["str"] |= !Eq(mut->str(), init->str()); + mutated_fields["ei8"] |= mut->ei8() != init->ei8(); + mutated_fields["ei16"] |= mut->ei16() != init->ei16(); + mutated_fields["ei32"] |= mut->ei32() != init->ei32(); + mutated_fields["ei64"] |= mut->ei64() != init->ei64(); + mutated_fields["eu8"] |= mut->eu8() != init->eu8(); + mutated_fields["eu16"] |= mut->eu16() != init->eu16(); + mutated_fields["eu32"] |= mut->eu32() != init->eu32(); + mutated_fields["eu64"] |= mut->eu64() != init->eu64(); + mutated_fields["t"] |= !Eq(mut->t(), init->t()); + mutated_fields["u_type"] |= !Eq(mut->u_type(), init->u_type()); + mutated_fields["u"] |= + !Eq(std::pair(static_cast<uint8_t>(mut->u_type()), mut->u()), + std::pair(static_cast<uint8_t>(init->u_type()), init->u())); + mutated_fields["s"] |= !Eq(mut->s(), init->s()); + mutated_fields["v_b"] |= !VectorEq(mut->v_b(), init->v_b()); + mutated_fields["v_i8"] |= !VectorEq(mut->v_i8(), init->v_i8()); + mutated_fields["v_i16"] |= !VectorEq(mut->v_i16(), init->v_i16()); + mutated_fields["v_i32"] |= !VectorEq(mut->v_i32(), init->v_i32()); + mutated_fields["v_i64"] |= !VectorEq(mut->v_i64(), init->v_i64()); + mutated_fields["v_u8"] |= !VectorEq(mut->v_u8(), init->v_u8()); + mutated_fields["v_u16"] |= !VectorEq(mut->v_u16(), init->v_u16()); + mutated_fields["v_u32"] |= !VectorEq(mut->v_u32(), init->v_u32()); + mutated_fields["v_u64"] |= !VectorEq(mut->v_u64(), init->v_u64()); + mutated_fields["v_f"] |= !VectorEq(mut->v_f(), init->v_f()); + mutated_fields["v_d"] |= !VectorEq(mut->v_d(), init->v_d()); + mutated_fields["v_str"] |= !VectorEq(mut->v_str(), init->v_str()); + mutated_fields["v_ei8"] |= !VectorEq(mut->v_ei8(), init->v_ei8()); + mutated_fields["v_ei16"] |= !VectorEq(mut->v_ei16(), init->v_ei16()); + mutated_fields["v_ei32"] |= !VectorEq(mut->v_ei32(), init->v_ei32()); + mutated_fields["v_ei64"] |= !VectorEq(mut->v_ei64(), init->v_ei64()); + mutated_fields["v_eu8"] |= !VectorEq(mut->v_eu8(), init->v_eu8()); + mutated_fields["v_eu16"] |= !VectorEq(mut->v_eu16(), init->v_eu16()); + mutated_fields["v_eu32"] |= !VectorEq(mut->v_eu32(), init->v_eu32()); + mutated_fields["v_eu64"] |= !VectorEq(mut->v_eu64(), init->v_eu64()); + mutated_fields["v_t"] |= !VectorEq(mut->v_str(), init->v_str()); + mutated_fields["v_u_type"] |= !VectorEq(mut->v_u_type(), init->v_u_type()); + mutated_fields["v_u"] |= !VectorUnionEq(mut->v_u_type(), mut->v_u(), + init->v_u_type(), init->v_u()); + mutated_fields["v_s"] |= !VectorEq(mut->v_s(), init->v_s()); + + if (std::all_of(mutated_fields.begin(), mutated_fields.end(), + [](const auto& p) { return p.second; })) { + 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<ByteEnum>> v_ei8{}; + std::vector<std::underlying_type_t<ShortEnum>> v_ei16{}; + std::vector<std::underlying_type_t<IntEnum>> v_ei32{}; + std::vector<std::underlying_type_t<LongEnum>> v_ei64{}; + std::vector<std::underlying_type_t<UByteEnum>> v_eu8{}; + std::vector<std::underlying_type_t<UShortEnum>> v_eu16{}; + std::vector<std::underlying_type_t<UIntEnum>> v_eu32{}; + std::vector<std::underlying_type_t<ULongEnum>> v_eu64{}; + 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::ByteEnum_Second, // ei8 + internal::ShortEnum_Second, // ei16 + internal::IntEnum_Second, // ei32 + internal::LongEnum_Second, // ei64 + internal::UByteEnum_Second, // eu8 + internal::UShortEnum_Second, // eu16 + internal::UIntEnum_Second, // eu32 + internal::ULongEnum_Second, // eu64 + 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_ei8, // v_ei8 + &v_ei16, // v_ei16 + &v_ei32, // v_ei32 + &v_ei64, // v_ei64 + &v_eu8, // v_eu8 + &v_eu16, // v_eu16 + &v_eu32, // v_eu32 + &v_eu64, // v_eu64 + &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<const 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}, + {"ei8", false}, {"ei16", false}, {"ei32", false}, + {"ei64", false}, {"eu8", false}, {"eu16", false}, + {"eu32", false}, {"eu64", false}, {"t", false}, + {"u_type", false}, {"u", false}, {"s", false}, + {"v_b", false}, {"v_i8", false}, {"v_i16", false}, + {"v_i32", false}, {"v_i64", false}, {"v_u8", false}, + {"v_u16", false}, {"v_u32", false}, {"v_u64", false}, + {"v_f", false}, {"v_d", false}, {"v_str", false}, + {"v_ei8", false}, {"v_ei16", false}, {"v_ei32", false}, + {"v_ei64", false}, {"v_eu8", false}, {"v_eu16", false}, + {"v_eu32", false}, {"v_eu64", false}, {"v_t", false}, + {"v_u_type", false}, {"v_u", false}, {"v_s", false}, + }; + + for (size_t i = 0; i < 10'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["ei8"] |= !v->ei8().has_value(); + null_fields["ei16"] |= !v->ei16().has_value(); + null_fields["ei32"] |= !v->ei32().has_value(); + null_fields["ei64"] |= !v->ei64().has_value(); + null_fields["eu8"] |= !v->eu8().has_value(); + null_fields["eu16"] |= !v->eu16().has_value(); + null_fields["eu32"] |= !v->eu32().has_value(); + null_fields["eu64"] |= !v->eu64().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["v_b"] |= v->v_b() == nullptr; + null_fields["v_i8"] |= v->v_i8() == nullptr; + null_fields["v_i16"] |= v->v_i16() == nullptr; + null_fields["v_i32"] |= v->v_i32() == nullptr; + null_fields["v_i64"] |= v->v_i64() == nullptr; + null_fields["v_u8"] |= v->v_u8() == nullptr; + null_fields["v_u16"] |= v->v_u16() == nullptr; + null_fields["v_u32"] |= v->v_u32() == nullptr; + null_fields["v_u64"] |= v->v_u64() == nullptr; + null_fields["v_f"] |= v->v_f() == nullptr; + null_fields["v_d"] |= v->v_d() == nullptr; + null_fields["v_str"] |= v->v_str() == nullptr; + null_fields["v_ei8"] |= v->v_ei8() == nullptr; + null_fields["v_ei16"] |= v->v_ei16() == nullptr; + null_fields["v_ei32"] |= v->v_ei32() == nullptr; + null_fields["v_ei64"] |= v->v_ei64() == nullptr; + null_fields["v_eu8"] |= v->v_eu8() == nullptr; + null_fields["v_eu16"] |= v->v_eu16() == nullptr; + null_fields["v_eu32"] |= v->v_eu32() == nullptr; + null_fields["v_eu64"] |= v->v_eu64() == nullptr; + null_fields["v_t"] |= v->v_t() == nullptr; + null_fields["v_u_type"] |= v->v_u_type() == nullptr; + null_fields["v_u"] |= v->v_u() == nullptr; + null_fields["v_s"] |= v->v_s() == nullptr; + + if (std::all_of(null_fields.begin(), null_fields.end(), + [](const auto& p) { return p.second; })) { + break; + } + } + + EXPECT_THAT(null_fields, Each(Pair(_, true))); +} + +TEST(FlatbuffersTableDomainImplTest, RequiredTableFieldsAlwaysSet) { + auto domain = Arbitrary<const RequiredTable*>(); + + EXPECT_THAT(GenerateNonUniqueCorpusValues( + domain, + /*num_seeds=*/1, + /*num_mutations=*/IterationsToHitAll(1, 1.0 / 100), {}, + /*only_shrink=*/true), + Each(ResultOf( + [&](const typename decltype(domain)::corpus_type& corpus) { + auto value = domain.GetValue(corpus); + return value->str() == nullptr && value->t() == nullptr; + }, + IsFalse()))); +} + +TEST(FlatbuffersTableDomainImplTest, Printer) { + flatbuffers::FlatBufferBuilder fbb; + auto table = CreateDefaultTable(fbb); + auto domain = Arbitrary<const DefaultTable*>(); + auto corpus = domain.FromValue(table); + ASSERT_TRUE(corpus.has_value()); + + auto printer = domain.GetPrinter(); + std::string out; + printer.PrintCorpusValue(*corpus, &out, + domain_implementor::PrintMode::kHumanReadable); + + EXPECT_THAT( + out, + AllOf(HasSubstr("b: (true)"), // b + HasSubstr("i8: (1)"), // i8 + HasSubstr("i16: (2)"), // i16 + HasSubstr("i32: (3)"), // i32 + HasSubstr("i64: (4)"), // i64 + HasSubstr("u8: (5)"), // u8 + HasSubstr("u16: (6)"), // u16 + HasSubstr("u32: (7)"), // u32 + HasSubstr("u64: (8)"), // u64 + HasSubstr("f: (9.f)"), // f + HasSubstr("d: (10.)"), // d + HasSubstr("str: (\"foo bar baz\")"), // str + HasSubstr("ei8: (Second)"), // ei8 + HasSubstr("ei16: (Second)"), // ei16 + HasSubstr("ei32: (Second)"), // ei32 + HasSubstr("ei64: (Second)"), // ei64 + HasSubstr("eu8: (Second)"), // eu8 + HasSubstr("eu16: (Second)"), // eu16 + HasSubstr("eu32: (Second)"), // eu32 + HasSubstr("eu64: (Second)"), // eu64 + HasSubstr("t: ({b: (true)})"), // t + HasSubstr("u: (<BoolTable>({b: (true)}))"), // u + HasSubstr( + "s: ({b: true, i8: 1, i16: 2, i32: 3, i64: 4, u8: 5, u16: 6, " + "u32: 7, u64: 8, f: 9.f, d: 10., ei8: First, ei16: First, " + "ei32: First, ei64: First, eu8: First, eu16: First, eu32: " + "First, eu64: First, s: {b: true, a_b: {true, false}}, a_b: " + "{true, false}, a_i8: {11, 12}, a_i16: {13, 14}, a_i32: {15, " + "16}, a_i64: {17, 18}, a_u8: {19, 20}, a_u16: {21, 22}, a_u32: " + "{23, 24}, a_u64: {25, 26}, a_f: {27.f, 28.f}, a_d: {29., " + "30.}, a_ei8: {First, Second}, a_ei16: {First, Second}, " + "a_ei32: {First, Second}, a_ei64: {First, Second}, a_eu8: " + "{First, Second}, a_eu16: {First, Second}, a_eu32: {First, " + "Second}, a_eu64: {First, Second}, a_s: {{b: true, a_b: {true, " + "false}}, {b: false, a_b: {false, true}}}})"), // s + HasSubstr("v_b: ({true, false})"), // v_b + HasSubstr("v_i8: ({1, 2, 3})"), // v_i8 + HasSubstr("v_i16: ({1, 2, 3})"), // v_i16 + HasSubstr("v_i32: ({1, 2, 3})"), // v_i32 + HasSubstr("v_i64: ({1, 2, 3})"), // v_i64 + HasSubstr("v_u8: ({1, 2, 3})"), // v_u8 + HasSubstr("v_u16: ({1, 2, 3})"), // v_u16 + HasSubstr("v_u32: ({1, 2, 3})"), // v_u32 + HasSubstr("v_u64: ({1, 2, 3})"), // v_u64 + HasSubstr("v_f: ({1.f, 2.f, 3.f})"), // v_f + HasSubstr("v_d: ({1., 2., 3.})"), // v_d + HasSubstr("v_str: ({\"foo\", \"bar\", \"baz\"})"), // v_str + HasSubstr("v_ei8: ({First, Second})"), // v_ei8 + HasSubstr("v_ei16: ({First, Second})"), // v_ei16 + HasSubstr("v_ei32: ({First, Second})"), // v_ei32 + HasSubstr("v_ei64: ({First, Second})"), // v_ei64 + HasSubstr("v_eu8: ({First, Second})"), // v_eu8 + HasSubstr("v_eu16: ({First, Second})"), // v_eu16 + HasSubstr("v_eu32: ({First, Second})"), // v_eu32 + HasSubstr("v_eu64: ({First, Second})"), // v_eu64 + HasSubstr("v_t: ({{b: (true)}})"), // v_t + HasSubstr("v_u: ({<BoolTable>({b: (true)}), " + "<StringTable>({str: (\"foo bar baz\")})})"), // v_u + HasSubstr( + "v_s: ({{b: true, i8: 1, i16: 2, i32: 3, i64: 4, u8: 5, u16: " + "6, u32: 7, u64: 8, f: 9.f, d: 10., ei8: First, ei16: First, " + "ei32: First, ei64: First, eu8: First, eu16: First, eu32: " + "First, eu64: First, s: {b: true, a_b: {true, false}}, a_b: " + "{true, false}, a_i8: {11, 12}, a_i16: {13, 14}, a_i32: {15, " + "16}, a_i64: {17, 18}, a_u8: {19, 20}, a_u16: {21, 22}, a_u32: " + "{23, 24}, a_u64: {25, 26}, a_f: {27.f, 28.f}, a_d: {29., " + "30.}, a_ei8: {First, Second}, a_ei16: {First, Second}, " + "a_ei32: {First, Second}, a_ei64: {First, Second}, a_eu8: " + "{First, Second}, a_eu16: {First, Second}, a_eu32: {First, " + "Second}, a_eu64: {First, Second}, a_s: {{b: true, a_b: {true, " + "false}}, {b: false, a_b: {false, true}}}}})}") // v_s + )); +} + +TEST(FlatbuffersTableDomainImplTest, MutateAlwaysChangesValues) { + auto domain = Arbitrary<const DefaultTable*>(); + const reflection::Schema* schema = + reflection::GetSchema(DefaultTable::BinarySchema::data()); + const reflection::Object* object = + schema->objects()->LookupByKey(DefaultTable::GetFullyQualifiedName()); + + absl::BitGen bitgen; + size_t iterations = IterationsToHitAll( + object->fields()->size(), 1.0 / (object->fields()->size() * 0.5)); + std::vector<typename decltype(domain)::corpus_type> corpus_values{iterations}; + corpus_values.emplace_back(domain.Init(bitgen)); + while (corpus_values.size() < iterations) { + auto corpus_value = corpus_values.back(); + domain.Mutate(corpus_value, bitgen, {}, false); + corpus_values.push_back(corpus_value); + } + + std::vector<bool> has_changed; + has_changed.reserve(corpus_values.size() - 1); + for (size_t i = 0; i < corpus_values.size() - 2; ++i) { + has_changed.push_back(Eq(domain.GetValue(corpus_values[i]), + domain.GetValue(corpus_values[i + 1]))); + } + + EXPECT_THAT(has_changed, Each(IsTrue())); +} + +// Check that the domain is properly deduced by the macros. +void FnUnderTest(const DefaultTable* table) { EXPECT_THAT(table, NotNull()); } +FUZZ_TEST(MacrosTest, FnUnderTest); + +TEST(FlatbuffersTableDomainImplTest, CountNumberOfFieldsWithNull) { + flatbuffers::FlatBufferBuilder fbb; + auto table_offset = internal::CreateOptionalTableDirect(fbb); + fbb.Finish(table_offset); + auto table = flatbuffers::GetRoot<OptionalTable>(fbb.GetBufferPointer()); + + auto domain = Arbitrary<const OptionalTable*>(); + auto corpus = domain.FromValue(table); + ASSERT_TRUE(corpus.has_value()); + EXPECT_EQ(domain.CountNumberOfFields(corpus.value()), 46); +} + +TEST(FlatbuffersUnionDomainImpl, ParseCorpusRejectsInvalidValues) { + auto domain = Arbitrary<const 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/e2e_tests/functional_test.cc b/e2e_tests/functional_test.cc index 9ee676e..b9b8ec8 100644 --- a/e2e_tests/functional_test.cc +++ b/e2e_tests/functional_test.cc
@@ -1857,6 +1857,33 @@ ExpectTargetAbort(status, std_err); } +TEST_P(FuzzingModeCrashFindingTest, FlatbuffersFailsWhenFieldsAreNotDefault) { + TempDir out_dir; + auto [status, std_out, std_err] = + Run("MySuite.FlatbuffersFailsWhenFieldsAreNotDefault"); + EXPECT_THAT(std_err, HasSubstr("argument 0: {b: (")); + EXPECT_THAT(std_err, HasSubstr("i8: (")); + EXPECT_THAT(std_err, HasSubstr("i16: (")); + EXPECT_THAT(std_err, HasSubstr("i32: (")); + EXPECT_THAT(std_err, HasSubstr("i64: (")); + EXPECT_THAT(std_err, HasSubstr("u8: (")); + EXPECT_THAT(std_err, HasSubstr("u16: (")); + EXPECT_THAT(std_err, HasSubstr("u32: (")); + EXPECT_THAT(std_err, HasSubstr("u64: (")); + EXPECT_THAT(std_err, HasSubstr("f: (")); + EXPECT_THAT(std_err, HasSubstr("d: (")); + EXPECT_THAT(std_err, HasSubstr("str: ")); + EXPECT_THAT(std_err, HasSubstr("ei8: (")); + EXPECT_THAT(std_err, HasSubstr("ei16: (")); + EXPECT_THAT(std_err, HasSubstr("ei32: (")); + EXPECT_THAT(std_err, HasSubstr("ei64: (")); + EXPECT_THAT(std_err, HasSubstr("eu8: (")); + EXPECT_THAT(std_err, HasSubstr("eu16: (")); + EXPECT_THAT(std_err, HasSubstr("eu32: (")); + EXPECT_THAT(std_err, HasSubstr("eu64: (")); + ExpectTargetAbort(status, std_err); +} + INSTANTIATE_TEST_SUITE_P(FuzzingModeCrashFindingTestWithExecutionModel, FuzzingModeCrashFindingTest, testing::ValuesIn(GetAvailableExecutionModels()));
diff --git a/e2e_tests/testdata/BUILD b/e2e_tests/testdata/BUILD index 3c623f3..2abbffa 100644 --- a/e2e_tests/testdata/BUILD +++ b/e2e_tests/testdata/BUILD
@@ -101,9 +101,11 @@ "@abseil-cpp//absl/time", "@abseil-cpp//absl/types:span", "@com_google_fuzztest//fuzztest", + "@com_google_fuzztest//fuzztest:flatbuffers", "@com_google_fuzztest//fuzztest:fuzztest_gtest_main", "@com_google_fuzztest//fuzztest:googletest_fixture_adapter", "@com_google_fuzztest//fuzztest/internal:logging", + "@com_google_fuzztest//fuzztest/internal:test_flatbuffers_cc_fbs", "@com_google_fuzztest//fuzztest/internal:test_protobuf_cc_proto", "@protobuf", "@re2",
diff --git a/e2e_tests/testdata/CMakeLists.txt b/e2e_tests/testdata/CMakeLists.txt index 4e802b1..946d373 100644 --- a/e2e_tests/testdata/CMakeLists.txt +++ b/e2e_tests/testdata/CMakeLists.txt
@@ -39,8 +39,10 @@ absl::strings absl::time re2::re2 + fuzztest_flatbuffers fuzztest_googletest_fixture_adapter fuzztest_logging + fuzztest_test_flatbuffers_headers ) link_fuzztest(fuzz_tests_for_functional_testing.stripped) set_target_properties(
diff --git a/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc b/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc index 8049505..b23a49a 100644 --- a/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc +++ b/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc
@@ -30,6 +30,7 @@ #include <utility> #include <vector> +#include "./fuzztest/flatbuffers.h" // IWYU pragma: keep #include "./fuzztest/fuzztest.h" #include "absl/algorithm/container.h" #include "absl/functional/function_ref.h" @@ -39,6 +40,7 @@ #include "absl/time/clock.h" #include "absl/time/time.h" #include "./fuzztest/internal/logging.h" +#include "./fuzztest/internal/test_flatbuffers_generated.h" #include "./fuzztest/internal/test_protobuf.pb.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" @@ -56,6 +58,7 @@ using ::fuzztest::StructOf; using ::fuzztest::TupleOf; using ::fuzztest::VectorOf; +using ::fuzztest::internal::DefaultTable; using ::fuzztest::internal::ProtoExtender; using ::fuzztest::internal::SingleInt32Field; using ::fuzztest::internal::TestProtobuf; @@ -847,4 +850,68 @@ }; FUZZ_TEST_F(FaultySetupTest, NoOp); +void FlatbuffersFailsWhenFieldsAreNotDefault(const DefaultTable* table) { + // Abort if any of the fields are not set to their default values. + if (table->b() != false) { + std::abort(); + } + if (table->i8() != 0) { + std::abort(); + } + if (table->i16() != 0) { + std::abort(); + } + if (table->i32() != 0) { + std::abort(); + } + if (table->i64() != 0) { + std::abort(); + } + if (table->u8() != 0) { + std::abort(); + } + if (table->u16() != 0) { + std::abort(); + } + if (table->u32() != 0) { + std::abort(); + } + if (table->u64() != 0) { + std::abort(); + } + if (table->f() != 0.0f) { + std::abort(); + } + if (table->d() != 0.0) { + std::abort(); + } + if (table->str() != nullptr) { + std::abort(); + } + if (table->ei8() != fuzztest::internal::ByteEnum_First) { + std::abort(); + } + if (table->ei16() != fuzztest::internal::ShortEnum_First) { + std::abort(); + } + if (table->ei32() != fuzztest::internal::IntEnum_First) { + std::abort(); + } + if (table->ei64() != fuzztest::internal::LongEnum_First) { + std::abort(); + } + if (table->eu8() != fuzztest::internal::UByteEnum_First) { + std::abort(); + } + if (table->eu16() != fuzztest::internal::UShortEnum_First) { + std::abort(); + } + if (table->eu32() != fuzztest::internal::UIntEnum_First) { + std::abort(); + } + if (table->eu64() != fuzztest::internal::ULongEnum_First) { + std::abort(); + } +} +FUZZ_TEST(MySuite, FlatbuffersFailsWhenFieldsAreNotDefault); } // namespace
diff --git a/fuzztest/BUILD b/fuzztest/BUILD index 7dc2a14..d184dc1 100644 --- a/fuzztest/BUILD +++ b/fuzztest/BUILD
@@ -205,6 +205,14 @@ ) cc_library( + name = "flatbuffers", + hdrs = ["flatbuffers.h"], + deps = [ + "@com_google_fuzztest//fuzztest/internal/domains:flatbuffers_domain_impl", + ], +) + +cc_library( name = "fuzzing_bit_gen", srcs = ["fuzzing_bit_gen.cc"], hdrs = ["fuzzing_bit_gen.h"],
diff --git a/fuzztest/CMakeLists.txt b/fuzztest/CMakeLists.txt index 7d191f7..5a664f2 100644 --- a/fuzztest/CMakeLists.txt +++ b/fuzztest/CMakeLists.txt
@@ -88,6 +88,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_gtest_main
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/BUILD b/fuzztest/internal/BUILD index 5f88da0..c79706d 100644 --- a/fuzztest/internal/BUILD +++ b/fuzztest/internal/BUILD
@@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@flatbuffers//:build_defs.bzl", "flatbuffer_library_public") load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["@com_google_fuzztest//:__subpackages__"]) @@ -539,3 +540,35 @@ "@protobuf", ], ) + +flatbuffer_library_public( + name = "test_flatbuffers_fbs", + srcs = ["test_flatbuffers.fbs"], + outs = [ + "test_flatbuffers_bfbs_generated.h", + "test_flatbuffers_generated.h", + ], + flatc_args = [ + "--no-union-value-namespacing", + "--gen-name-strings", + "--bfbs-gen-embed", + ], + language_flag = "-c", +) + +cc_library( + name = "test_flatbuffers_cc_fbs", + srcs = [":test_flatbuffers_fbs"], + hdrs = [":test_flatbuffers_fbs"], + features = ["-parse_headers"], + deps = ["@flatbuffers//:runtime_cc"], +) + +# Stops build_cleaner from generating flatbuffers_library for the test flatbuffers schema. +filegroup( + name = "build_cleaner_ignore", + srcs = [ + "test_flatbuffers_fbs", + ], + tags = ["ignore_srcs"], +)
diff --git a/fuzztest/internal/CMakeLists.txt b/fuzztest/internal/CMakeLists.txt index 8fd2e4e..61f6da8 100644 --- a/fuzztest/internal/CMakeLists.txt +++ b/fuzztest/internal/CMakeLists.txt
@@ -494,6 +494,21 @@ TESTONLY ) +if (FUZZTEST_BUILD_FLATBUFFERS) + fuzztest_flatbuffers_generate_headers( + TARGET + test_flatbuffers_headers + SCHEMAS + "test_flatbuffers.fbs" + FLAGS + --bfbs-gen-embed --gen-name-strings + TESTONLY + ) + add_dependencies(fuzztest_test_flatbuffers_headers + fuzztest_GENERATE_test_flatbuffers_headers + ) +endif() + fuzztest_cc_library( NAME type_support
diff --git a/fuzztest/internal/domains/BUILD b/fuzztest/internal/domains/BUILD index 5a1d738..e15e2cc 100644 --- a/fuzztest/internal/domains/BUILD +++ b/fuzztest/internal/domains/BUILD
@@ -188,6 +188,33 @@ ) cc_library( + name = "flatbuffers_domain_impl", + srcs = ["flatbuffers_domain_impl.cc"], + hdrs = ["flatbuffers_domain_impl.h"], + deps = [ + ":core_domains_impl", + "@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", + "@com_google_fuzztest//fuzztest:domain_core", + "@com_google_fuzztest//fuzztest/internal:any", + "@com_google_fuzztest//fuzztest/internal:logging", + "@com_google_fuzztest//fuzztest/internal:meta", + "@com_google_fuzztest//fuzztest/internal:serialization", + "@com_google_fuzztest//fuzztest/internal:status", + "@com_google_fuzztest//fuzztest/internal:type_support", + "@flatbuffers//:runtime_cc", + ], +) + +cc_library( name = "regexp_dfa", srcs = ["regexp_dfa.cc"], hdrs = ["regexp_dfa.h"],
diff --git a/fuzztest/internal/domains/arbitrary_impl.h b/fuzztest/internal/domains/arbitrary_impl.h index d151d50..f6b1702 100644 --- a/fuzztest/internal/domains/arbitrary_impl.h +++ b/fuzztest/internal/domains/arbitrary_impl.h
@@ -458,7 +458,9 @@ // Monostates have their own domain. !is_monostate_v<T> && // std::array uses the Tuple domain. - !is_array_v<T>>> + !is_array_v<T> && + // Flatbuffers tables have their own domain. + !is_flatbuffers_table_v<T>>> : public decltype(DetectAggregateOfImpl<T>()) {}; // Arbitrary for std::pair.
diff --git a/fuzztest/internal/domains/flatbuffers_domain_impl.cc b/fuzztest/internal/domains/flatbuffers_domain_impl.cc new file mode 100644 index 0000000..4c0bb8c --- /dev/null +++ b/fuzztest/internal/domains/flatbuffers_domain_impl.cc
@@ -0,0 +1,483 @@ +// 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/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 "flatbuffers/base.h" +#include "flatbuffers/flatbuffer_builder.h" +#include "flatbuffers/reflection.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::internal { + +// Gets a domain for a specific struct type. +template <> +auto FlatbuffersUnionDomainImpl::GetDefaultDomainForType<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}); +} + +// Get a domain for a specific table type. +template <> +auto FlatbuffersUnionDomainImpl::GetDefaultDomainForType<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.type = *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 = + GetCachedDomain<FlatbuffersStructTag>(*type_enumval).Init(prng); + val.value = std::move(inner_val); + } else { + auto inner_val = + GetCachedDomain<FlatbuffersTableTag>(*type_enumval).Init(prng); + val.value = std::move(inner_val); + } + return val; +} + +// Mutates the corpus value. +void FlatbuffersUnionDomainImpl::Mutate( + corpus_type& corpus_value, absl::BitGenRef prng, + const domain_implementor::MutationMetadata& metadata, bool only_shrink) { + auto total_weight = CountNumberOfFields(corpus_value); + 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(corpus_value.type, prng, metadata, only_shrink); + corpus_value.value = + GenericDomainCorpusType(std::in_place_type<void*>, nullptr); + auto type_value = type_domain_.GetValue(corpus_value.type); + 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 = + GetCachedDomain<FlatbuffersStructTag>(*type_enumval).Init(prng); + corpus_value.value = std::move(inner_val); + } else { + auto inner_val = + GetCachedDomain<FlatbuffersTableTag>(*type_enumval).Init(prng); + corpus_value.value = std::move(inner_val); + } + } else { + // Keep the type, mutate the value. + auto type_value = type_domain_.GetValue(corpus_value.type); + 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 = GetCachedDomain<FlatbuffersStructTag>(*type_enumval); + domain.MutateSelectedField(corpus_value.value, prng, metadata, + only_shrink, selected_weight - 1); + } else { + auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval); + domain.MutateSelectedField(corpus_value.value, prng, metadata, + only_shrink, selected_weight - 1); + } + } +} + +uint64_t FlatbuffersUnionDomainImpl::CountNumberOfFields( + corpus_type& corpus_value) { + // 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(corpus_value.type); + 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 = GetCachedDomain<FlatbuffersStructTag>(*type_enumval); + count += domain.CountNumberOfFields(corpus_value.value); + } else { + auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval); + count += domain.CountNumberOfFields(corpus_value.value); + } + 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.type); + 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.value.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 = GetCachedDomain<FlatbuffersStructTag>(*type_enumval); + return domain.ValidateCorpusValue(corpus_value.value); + } else { + auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval); + return domain.ValidateCorpusValue(corpus_value.value); + } +} + +// 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.type); + if (type_value.has_value()) { + out->type = *type_value; + } + auto type_enumval = union_def_->values()->LookupByKey(value.type); + 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 = GetCachedDomain<FlatbuffersStructTag>(*type_enumval); + inner_corpus = + domain.FromValue(static_cast<const flatbuffers::Struct*>(value.value)); + } else { + auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval); + inner_corpus = + domain.FromValue(static_cast<const flatbuffers::Table*>(value.value)); + } + if (inner_corpus.has_value()) { + out->value = 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.type = *type_corpus; + auto type_value = type_domain_.GetValue(out.type); + 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 = GetCachedDomain<FlatbuffersStructTag>(*type_enumval); + // The value is stored in the second field of the IRObject subs. + inner_corpus = domain.ParseCorpus((*subs)[1]); + } else { + auto domain = GetCachedDomain<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.value = std::move(inner_corpus.value()); + } + return out; +} + +// Converts the corpus value to an IRObject. +IRObject FlatbuffersUnionDomainImpl::SerializeCorpus( + const corpus_type& corpus_value) const { + IRObject out; + auto type_value = type_domain_.GetValue(corpus_value.type); + 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(corpus_value.type)); + + 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 = GetCachedDomain<FlatbuffersStructTag>(*type_enumval); + pair.push_back(domain.SerializeCorpus(corpus_value.value)); + } else { + auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval); + pair.push_back(domain.SerializeCorpus(corpus_value.value)); + } + return out; +} + +std::optional<flatbuffers::uoffset_t> FlatbuffersUnionDomainImpl::BuildValue( + const corpus_type& corpus_value, + flatbuffers::FlatBufferBuilder& builder) const { + // Get the object type. + auto type_value = type_domain_.GetValue(corpus_value.type); + auto type_enumval = union_def_->values()->LookupByKey(type_value); + if (type_enumval == nullptr || type_value == 0 /* NONE */ || + !corpus_value.value.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( + corpus_value.value + .GetAs<corpus_type_t<FlatbuffersStructUntypedDomainImpl>>(), + builder); + } else { + FlatbuffersTableUntypedDomainImpl domain{schema_, object}; + return domain.BuildTable( + corpus_value.value + .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.type); + 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.GetCachedDomain<FlatbuffersStructTag>(*type_enumval); + domain_implementor::PrintValue(domain, value.value, out, mode); + } else { + auto domain = self.GetCachedDomain<FlatbuffersTableTag>(*type_enumval); + domain_implementor::PrintValue(domain, value.value, out, mode); + } + } + absl::Format(out, ")"); +} + +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 : *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(object_->bytesize()); + BuildValue(value, buf.data()); + builder.StartStruct(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 : *object_->fields()) { + VisitFlatbufferField(schema_, field, BuildValueVisitor{*this, value, buf}); + } +} + +// Converts the table pointer to a corpus value. +std::optional<FlatbuffersTableUntypedDomainImpl::corpus_type> +FlatbuffersTableUntypedDomainImpl::FromValue(const value_type& value) const { + if (value == nullptr) { + return std::nullopt; + } + corpus_type ret; + for (const auto* field : *object_->fields()) { + VisitFlatbufferField(schema_, field, FromValueVisitor{*this, value, ret}); + } + return ret; +} + +uint32_t FlatbuffersTableUntypedDomainImpl::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); +} +} // namespace fuzztest::internal
diff --git a/fuzztest/internal/domains/flatbuffers_domain_impl.h b/fuzztest/internal/domains/flatbuffers_domain_impl.h new file mode 100644 index 0000000..767a8e1 --- /dev/null +++ b/fuzztest/internal/domains/flatbuffers_domain_impl.h
@@ -0,0 +1,2031 @@ +// 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/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/container_of_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" +#include "./fuzztest/internal/type_support.h" + +namespace fuzztest::internal { + +// +// Flatbuffers enum detection. +// +template <typename Underlying, + typename = std::enable_if_t<std::is_integral_v<Underlying> && + !std::is_same_v<Underlying, bool>>> +struct FlatbuffersEnumTag { + using type = Underlying; +}; + +template <typename T> +struct is_flatbuffers_enum_tag : std::false_type {}; + +template <typename Underlying, typename Enable> +struct is_flatbuffers_enum_tag<FlatbuffersEnumTag<Underlying, Enable>> + : 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; + +// +// Flatbuffers array detection. +// +template <typename T> +struct FlatbuffersArrayTag { + using value_type = T; +}; + +template <typename T> +struct is_flatbuffers_array_tag : std::false_type {}; + +template <typename T> +struct is_flatbuffers_array_tag<FlatbuffersArrayTag<T>> : std::true_type {}; + +template <typename T> +inline constexpr bool is_flatbuffers_array_tag_v = + is_flatbuffers_array_tag<T>::value; + +struct FlatbuffersTableTag; +struct FlatbuffersStructTag; +struct FlatbuffersUnionTag; + +// Dynamic to static dispatch visitor pattern for flatbuffers container +// elements. +template <template <typename> typename ContainerTag, typename Visitor> +auto VisitFlatbufferContainerElementField(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<ContainerTag<bool>>(field); + break; + case reflection::BaseType::Byte: + if (field_index >= 0) { + visitor.template Visit<ContainerTag<FlatbuffersEnumTag<int8_t>>>(field); + } else { + visitor.template Visit<ContainerTag<int8_t>>(field); + } + break; + case reflection::BaseType::Short: + if (field_index >= 0) { + visitor.template Visit<ContainerTag<FlatbuffersEnumTag<int16_t>>>( + field); + } else { + visitor.template Visit<ContainerTag<int16_t>>(field); + } + break; + case reflection::BaseType::Int: + if (field_index >= 0) { + visitor.template Visit<ContainerTag<FlatbuffersEnumTag<int32_t>>>( + field); + } else { + visitor.template Visit<ContainerTag<int32_t>>(field); + } + break; + case reflection::BaseType::Long: + if (field_index >= 0) { + visitor.template Visit<ContainerTag<FlatbuffersEnumTag<int64_t>>>( + field); + } else { + visitor.template Visit<ContainerTag<int64_t>>(field); + } + break; + case reflection::BaseType::UByte: + if (field_index >= 0) { + visitor.template Visit<ContainerTag<FlatbuffersEnumTag<uint8_t>>>( + field); + } else { + visitor.template Visit<ContainerTag<uint8_t>>(field); + } + break; + case reflection::BaseType::UShort: + if (field_index >= 0) { + visitor.template Visit<ContainerTag<FlatbuffersEnumTag<uint16_t>>>( + field); + } else { + visitor.template Visit<ContainerTag<uint16_t>>(field); + } + break; + case reflection::BaseType::UInt: + if (field_index >= 0) { + visitor.template Visit<ContainerTag<FlatbuffersEnumTag<uint32_t>>>( + field); + } else { + visitor.template Visit<ContainerTag<uint32_t>>(field); + } + break; + case reflection::BaseType::ULong: + if (field_index >= 0) { + visitor.template Visit<ContainerTag<FlatbuffersEnumTag<uint64_t>>>( + field); + } else { + visitor.template Visit<ContainerTag<uint64_t>>(field); + } + break; + case reflection::BaseType::Float: + visitor.template Visit<ContainerTag<float>>(field); + break; + case reflection::BaseType::Double: + visitor.template Visit<ContainerTag<double>>(field); + break; + case reflection::BaseType::String: + if constexpr (is_flatbuffers_vector_tag_v<ContainerTag<std::string>>) { + visitor.template Visit<ContainerTag<std::string>>(field); + } else if constexpr (is_flatbuffers_array_tag_v< + ContainerTag<std::string>>) { + FUZZTEST_INTERNAL_CHECK(false, + "Strings are not supported as array elements"); + } + break; + case reflection::BaseType::Obj: { + auto sub_object = schema->objects()->Get(field_index); + if (sub_object->is_struct()) { + visitor.template Visit<ContainerTag<FlatbuffersStructTag>>(field); + } else if constexpr (is_flatbuffers_vector_tag_v< + ContainerTag<FlatbuffersTableTag>>) { + visitor.template Visit<ContainerTag<FlatbuffersTableTag>>(field); + } else if constexpr (is_flatbuffers_array_tag_v< + ContainerTag<FlatbuffersTableTag>>) { + FUZZTEST_INTERNAL_CHECK(false, + "Tables are not supported as array elements"); + } + break; + } + case reflection::BaseType::Union: + if constexpr (is_flatbuffers_vector_tag_v< + ContainerTag<FlatbuffersUnionTag>>) { + visitor.template Visit<ContainerTag<FlatbuffersUnionTag>>(field); + } else if constexpr (is_flatbuffers_array_tag_v< + ContainerTag<FlatbuffersUnionTag>>) { + FUZZTEST_INTERNAL_CHECK(false, + "Unions are not supported as array elements"); + } + 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 container element 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: + VisitFlatbufferContainerElementField<FlatbuffersVectorTag, Visitor>( + schema, field, visitor); + break; + case reflection::BaseType::Array: + VisitFlatbufferContainerElementField<FlatbuffersArrayTag, Visitor>( + schema, field, visitor); + 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, absl::StrCat("Unsupported base type: ", + field->type()->base_type())); + } +} + +// Forward declaration of the domain factory for flatbuffers fields. +template <typename T> +auto GetDefaultDomain(const reflection::Schema* absl_nonnull schema, + const reflection::Field* absl_nonnull field); + +// Flatbuffers enum domain implementation. +template <typename Underlaying> +class FlatbuffersEnumDomainImpl + : public domain_implementor::DomainBase< + /*Derived=*/FlatbuffersEnumDomainImpl<Underlaying>, + /*ValueType=*/Underlaying, + /*CorpusType=*/ElementOfImplCorpusType> { + public: + using typename FlatbuffersEnumDomainImpl::DomainBase::corpus_type; + using typename FlatbuffersEnumDomainImpl::DomainBase::value_type; + + explicit FlatbuffersEnumDomainImpl(const reflection::Enum* enum_def) + : enum_def_(enum_def), inner_(GetEnumValues(enum_def)) {} + + corpus_type Init(absl::BitGenRef prng) { + if (auto seed = this->MaybeGetRandomSeed(prng)) return *seed; + return inner_.Init(prng); + } + + void Mutate(corpus_type& val, absl::BitGenRef prng, + const domain_implementor::MutationMetadata& metadata, + bool only_shrink) { + inner_.Mutate(val, prng, metadata, only_shrink); + } + + value_type GetValue(corpus_type value) const { + return inner_.GetValue(value); + } + + std::optional<corpus_type> FromValue(const value_type& v) const { + return inner_.FromValue(v); + } + + std::optional<corpus_type> ParseCorpus(const IRObject& obj) const { + return inner_.ParseCorpus(obj); + } + + IRObject SerializeCorpus(const corpus_type& v) const { + return inner_.SerializeCorpus(v); + } + + absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const { + return inner_.ValidateCorpusValue(corpus_value); + } + + auto GetPrinter() const { return Printer{*this}; } + + private: + const reflection::Enum* enum_def_; + ElementOfImpl<Underlaying> inner_; + + 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); + } + } + }; +}; + +// Forward declaration of the domain factory for flatbuffers fields. +template <typename T> +auto GetDefaultDomain(const reflection::Schema* absl_nonnull schema, + const reflection::Field* absl_nonnull field); + +// 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>; + +// Base class for flatbuffers struct and table domain implementations. +// The corpus type is a map of field ids to field values. +template <typename Derived, typename ValueType> +class FlatbuffersUntypedObjectDomainBase + : public domain_implementor::DomainBase< + /*Derived=*/Derived, + /*ValueType=*/ValueType, + /*CorpusType=*/ + absl::flat_hash_map< + decltype(static_cast<reflection::Field*>(nullptr)->id()), + GenericDomainCorpusType>> { + public: + using DomainBase = domain_implementor::DomainBase< + Derived, ValueType, + absl::flat_hash_map< + decltype(static_cast<reflection::Field*>(nullptr)->id()), + GenericDomainCorpusType>>; + using typename FlatbuffersUntypedObjectDomainBase::DomainBase::corpus_type; + using typename FlatbuffersUntypedObjectDomainBase::DomainBase::value_type; + + FlatbuffersUntypedObjectDomainBase( + const reflection::Schema* absl_nonnull schema, + const reflection::Object* absl_nonnull object) + : schema_(schema), object_(object) {} + + virtual ~FlatbuffersUntypedObjectDomainBase() = default; + + FlatbuffersUntypedObjectDomainBase( + const FlatbuffersUntypedObjectDomainBase& other) + : DomainBase(other), schema_(other.schema_), object_(other.object_) { + absl::MutexLock l(&mutex_); + absl::MutexLock l_other(&other.mutex_); + domains_ = other.domains_; + } + + FlatbuffersUntypedObjectDomainBase& operator=( + const FlatbuffersUntypedObjectDomainBase& other) { + DomainBase::operator=(other); + schema_ = other.schema_; + object_ = other.object_; + absl::MutexLock l(&mutex_); + absl::MutexLock l_other(&other.mutex_); + domains_ = other.domains_; + return *this; + } + + FlatbuffersUntypedObjectDomainBase(FlatbuffersUntypedObjectDomainBase&& other) + : DomainBase(std::move(other)), + schema_(other.schema_), + object_(other.object_) { + absl::MutexLock l(&mutex_); + absl::MutexLock l_other(&other.mutex_); + domains_ = std::move(other.domains_); + } + + FlatbuffersUntypedObjectDomainBase& operator=( + FlatbuffersUntypedObjectDomainBase&& other) { + DomainBase::operator=(std::move(other)); + schema_ = other.schema_; + object_ = other.object_; + absl::MutexLock l(&mutex_); + absl::MutexLock l_other(&other.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 : *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 field_count = CountNumberOfFields(val); + auto selected_field_index = absl::Uniform(prng, 0ul, field_count); + + MutateSelectedField(val, prng, metadata, only_shrink, selected_field_index); + } + + // 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 field_count = 0; + for (const auto* field : *object_->fields()) { + VisitFlatbufferField( + schema_, field, + CountNumberOfMutableFieldsVisitor{*this, field_count, val}); + } + return field_count; + } + + // 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 : *object_->fields()) { + if (IsSupportedField(field)) { + if (only_shrink && !val.contains(field->id())) continue; + ++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 += + derived() + .template GetCachedDomain<FlatbuffersStructTag>(field) + .MutateSelectedField(val[field->id()], prng, metadata, + only_shrink, + selected_field_index - field_counter); + } else { + field_counter += + derived() + .template GetCachedDomain<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 += + derived() + .template GetCachedDomain< + FlatbuffersVectorTag<FlatbuffersTableTag>>(field) + .MutateSelectedField(val[field->id()], prng, metadata, + only_shrink, + selected_field_index - field_counter); + } else { + field_counter += + derived() + .template GetCachedDomain< + 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 += + derived() + .template GetCachedDomain< + FlatbuffersVectorTag<FlatbuffersUnionTag>>(field) + .MutateSelectedField(val[field->id()], prng, metadata, + only_shrink, + selected_field_index - field_counter); + } + } + + if (base_type == reflection::BaseType::Union) { + field_counter += + derived() + .template GetCachedDomain<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) { + return absl::InvalidArgumentError( + absl::StrCat("Field id ", id, " is not found in the object.")); + } + 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 { + FUZZTEST_INTERNAL_CHECK( + false, "GetValue is not supported for the untyped Flatbuffers domain."); + // Untyped domain does not support GetValue since if it is a nested object + // it would need the top level object corpus value to be able to build it. + return nullptr; + } + + // 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; + } + + protected: + const reflection::Schema* schema_; + const reflection::Object* object_; + mutable absl::Mutex mutex_; + mutable absl::flat_hash_map< + decltype(static_cast<reflection::Field*>(nullptr)->id()), CopyableAny> + domains_ ABSL_GUARDED_BY(mutex_); + + // Helper function to downcast to the derived type + Derived& derived() { return static_cast<Derived&>(*this); } + const Derived& derived() const { return static_cast<const Derived&>(*this); } + + bool IsSupportedField(const reflection::Field* absl_nonnull field) const { + auto base_type = field->type()->base_type(); + if (flatbuffers::IsScalar(base_type)) return true; + if (base_type == reflection::BaseType::String) return true; + if (base_type == reflection::BaseType::Obj) return true; + if (base_type == reflection::BaseType::Union) return true; + if (base_type == reflection::BaseType::Vector || + base_type == reflection::BaseType::Vector64) { + auto elem_type = field->type()->element(); + if (flatbuffers::IsScalar(elem_type)) return true; + if (elem_type == reflection::BaseType::String) return true; + if (elem_type == reflection::BaseType::Obj) return true; + if (elem_type == reflection::BaseType::Union) return true; + } + if (base_type == reflection::BaseType::Array) { + auto elem_type = field->type()->element(); + if (flatbuffers::IsScalar(elem_type)) return true; + if (elem_type == reflection::BaseType::Obj && + schema_->objects()->Get(field->type()->index())->is_struct()) + return true; + } + return false; + } + + const reflection::Field* absl_nullable GetFieldById( + typename corpus_type::key_type id) const { + const auto it = + absl::c_find_if(*object_->fields(), + [id](const auto* field) { return field->id() == id; }); + return it != object_->fields()->end() ? *it : nullptr; + } + + // 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& GetCachedDomain(const reflection::Field* absl_nonnull field) const { + using TypedDomainT = decltype(GetDefaultDomain<T>(schema_, field)); + using DomainT = Domain<value_type_t<TypedDomainT>>; + // 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>, + DomainT{GetDefaultDomain<T>(schema_, field)}) + .first; + } + return it->second.template GetAs<DomainT>(); + } + + struct PrinterVisitor { + const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self; + const GenericDomainCorpusType& field_corpus; + domain_implementor::RawSink sink; + domain_implementor::PrintMode mode; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) const { + auto& domain = self.derived().template GetCachedDomain<T>(field); + absl::Format(sink, "%s: ", field->name()->str()); + if constexpr (std::is_same_v<T, FlatbuffersVectorTag<uint8_t>> || + std::is_same_v< + T, FlatbuffersVectorTag<FlatbuffersEnumTag<uint8_t>>> || + std::is_same_v<T, FlatbuffersArrayTag<uint8_t>> || + std::is_same_v< + T, FlatbuffersArrayTag<FlatbuffersEnumTag<uint8_t>>>) { + // Handle the case where the field is a vector or array of uint8_t or + // enum<uint8_t> since the container domain would try to print it as a + // string. + GenericDomainCorpusType object_corpus; + if (field_corpus + .Has<std::variant<std::monostate, GenericDomainCorpusType>>()) { + auto opt_corpus = field_corpus.GetAs< + std::variant<std::monostate, GenericDomainCorpusType>>(); + if (std::holds_alternative<GenericDomainCorpusType>(opt_corpus)) { + object_corpus = std::get<GenericDomainCorpusType>(opt_corpus); + absl::Format(sink, "("); + } else { + absl::Format(sink, "std::nullopt"); + return; + } + } else { + object_corpus = field_corpus; + } + + if constexpr (std::is_same_v<T, FlatbuffersVectorTag<uint8_t>> || + std::is_same_v<T, FlatbuffersArrayTag<uint8_t>>) { + auto inner_corpus = object_corpus.GetAs<corpus_type_t< + ContainerOfImpl<std::vector<uint8_t>, ArbitraryImpl<uint8_t>>>>(); + auto inner_domain = Arbitrary<uint8_t>(); + auto printer = ContainerPrinter< + ContainerOfImpl<std::vector<uint8_t>, ArbitraryImpl<uint8_t>>, + ArbitraryImpl<uint8_t>>{inner_domain}; + printer.PrintCorpusValue(inner_corpus, sink, mode); + } else if constexpr ( + std::is_same_v<T, + FlatbuffersVectorTag<FlatbuffersEnumTag<uint8_t>>> || + std::is_same_v<T, + FlatbuffersArrayTag<FlatbuffersEnumTag<uint8_t>>>) { + auto inner_corpus = object_corpus.GetAs<corpus_type_t<ContainerOfImpl< + std::vector<uint8_t>, FlatbuffersEnumDomainImpl<uint8_t>>>>(); + auto enum_object = self.schema_->enums()->Get(field->type()->index()); + auto inner_domain = FlatbuffersEnumDomainImpl<uint8_t>(enum_object); + auto printer = ContainerPrinter< + ContainerOfImpl<std::vector<uint8_t>, + FlatbuffersEnumDomainImpl<uint8_t>>, + FlatbuffersEnumDomainImpl<uint8_t>>{inner_domain}; + printer.PrintCorpusValue(inner_corpus, sink, mode); + } + + if (field_corpus + .Has<std::variant<std::monostate, GenericDomainCorpusType>>()) { + absl::Format(sink, ")"); + } + } else { + domain.GetPrinter().PrintCorpusValue(field_corpus, sink, mode); + } + } + }; + + struct Printer { + const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self; + + void PrintCorpusValue(const corpus_type& value, + domain_implementor::RawSink out, + domain_implementor::PrintMode mode) const { + std::vector<typename corpus_type::key_type> field_ids; + for (const auto& [id, _] : value) { + field_ids.push_back(id); + } + // Sort the field ids to make the output deterministic. + std::sort(field_ids.begin(), field_ids.end()); + + absl::Format(out, "{"); + bool first = true; + for (const auto id : field_ids) { + 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, value.at(id), out, mode}); + } + first = false; + } + absl::Format(out, "}"); + } + }; + + struct InitializeVisitor { + const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self; + absl::BitGenRef prng; + corpus_type& corpus; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + auto& domain = self.derived().template GetCachedDomain<T>(field); + corpus[field->id()] = domain.Init(prng); + } + }; + + struct CountNumberOfMutableFieldsVisitor { + const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self; + uint64_t& field_count; + corpus_type& corpus; + bool only_shrink = false; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) const { + if (!self.derived().IsSupportedField(field)) return; + if (only_shrink && !corpus.contains(field->id())) return; + + field_count++; + + if constexpr (!std::is_integral_v<T> && !std::is_floating_point_v<T> && + !is_flatbuffers_enum_tag_v<T>) { + // Count the number of fields in sub-domain. + auto& domain = self.derived().template GetCachedDomain<T>(field); + if (auto it = corpus.find(field->id()); it != corpus.end()) { + field_count += domain.CountNumberOfFields(it->second); + } + } + } + }; + + struct MutateVisitor { + const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self; + absl::BitGenRef prng; + const domain_implementor::MutationMetadata& metadata; + bool only_shrink; + corpus_type& corpus; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + auto& domain = self.derived().template GetCachedDomain<T>(field); + if (auto it = corpus.find(field->id()); it != corpus.end()) { + domain.Mutate(it->second, prng, metadata, only_shrink); + } else if (!only_shrink) { + corpus[field->id()] = domain.Init(prng); + } + } + }; + + struct ParseVisitor { + const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self; + const IRObject& ir_object; + std::optional<GenericDomainCorpusType>& corpus; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + auto& domain = self.derived().template GetCachedDomain<T>(field); + corpus = domain.ParseCorpus(ir_object); + } + }; + + struct SerializeVisitor { + const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self; + const GenericDomainCorpusType& corpus; + IRObject& ir_object; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + auto& domain = self.derived().template GetCachedDomain<T>(field); + ir_object = domain.SerializeCorpus(corpus); + } + }; + + struct ValidateVisitor { + const FlatbuffersUntypedObjectDomainBase<Derived, ValueType>& self; + const GenericDomainCorpusType& inner_corpus; + absl::Status& status; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + auto& domain = self.derived().template GetCachedDomain<T>(field); + status = domain.ValidateCorpusValue(inner_corpus); + if (!status.ok()) { + status = Prefix(status, absl::StrCat("Invalid value for field ", + field->name()->str())); + } + } + }; +}; + +// Domain implementation for flatbuffers struct types. +// The corpus type is a map of field ids to field values. +class FlatbuffersStructUntypedDomainImpl + : public FlatbuffersUntypedObjectDomainBase< + /*Derived=*/FlatbuffersStructUntypedDomainImpl, + /*ValueType=*/const flatbuffers::Struct*> { + public: + template <typename Derived, typename ValueType> + friend class FlatbuffersUntypedObjectDomainBase; + + using typename FlatbuffersStructUntypedDomainImpl::DomainBase::corpus_type; + using typename FlatbuffersStructUntypedDomainImpl::DomainBase::value_type; + + explicit FlatbuffersStructUntypedDomainImpl( + const reflection::Schema* absl_nonnull schema, + const reflection::Object* absl_nonnull struct_object) + : FlatbuffersUntypedObjectDomainBase(schema, struct_object) { + FUZZTEST_INTERNAL_CHECK(struct_object->is_struct(), + "Object must be a struct type."); + } + + // 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; + + private: + 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.GetCachedDomain<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); + } else if constexpr (is_flatbuffers_array_tag_v<T>) { + using ValueT = typename T::value_type; + auto element_type = field->type()->element(); + if constexpr (std::is_integral_v<ValueT> || + std::is_floating_point_v<ValueT>) { + FUZZTEST_INTERNAL_CHECK(flatbuffers::IsScalar(element_type), + "Field element type must be an scalar type."); + std::vector<ValueT> inner_values; + for (size_t i = 0; i < field->type()->fixed_length(); ++i) { + auto inner_value = value->GetField<ValueT>( + field->offset() + i * field->type()->element_size()); + inner_values.push_back(inner_value); + } + inner_corpus = domain.FromValue(inner_values); + } else if constexpr (is_flatbuffers_enum_tag_v<ValueT>) { + FUZZTEST_INTERNAL_CHECK(flatbuffers::IsScalar(element_type) && + element_type != reflection::Bool, + "Field element type must be an scalar type."); + std::vector<typename ValueT::type> inner_values; + for (size_t i = 0; i < field->type()->fixed_length(); ++i) { + auto inner_value = value->GetField<typename ValueT::type>( + field->offset() + i * field->type()->element_size()); + inner_values.push_back(inner_value); + } + inner_corpus = domain.FromValue(inner_values); + } else if constexpr (std::is_same_v<ValueT, FlatbuffersStructTag>) { + auto sub_object = + self.schema_->objects()->Get(field->type()->index()); + FUZZTEST_INTERNAL_CHECK(element_type == reflection::BaseType::Obj && + sub_object->is_struct(), + "Field element type must be a struct type."); + std::vector<const flatbuffers::Struct*> inner_values; + for (size_t i = 0; i < field->type()->fixed_length(); ++i) { + auto inner_value = value->GetStruct<const flatbuffers::Struct*>( + field->offset() + i * sub_object->bytesize()); + inner_values.push_back(inner_value); + } + inner_corpus = domain.FromValue(inner_values); + } + } + + 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.GetCachedDomain<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())); + if constexpr (is_flatbuffers_enum_tag_v<T>) { + flatbuffers::WriteScalar<typename T::type>( + struct_ptr + field->offset(), inner_value); + } else { + flatbuffers::WriteScalar<T>(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 (is_flatbuffers_array_tag_v<T>) { + if constexpr (is_flatbuffers_enum_tag_v<typename T::value_type> || + std::is_integral_v<typename T::value_type> || + std::is_floating_point_v<typename T::value_type>) { + FUZZTEST_INTERNAL_CHECK( + flatbuffers::IsScalar(field->type()->element()), + "Field must be an scalar type."); + auto inner_values = domain.GetValue(corpus_value.at(field->id())); + for (size_t i = 0; i < field->type()->fixed_length(); ++i) { + auto offset = field->offset() + i * field->type()->element_size(); + if constexpr (is_flatbuffers_enum_tag_v<typename T::value_type>) { + flatbuffers::WriteScalar<typename T::value_type::type>( + struct_ptr + offset, inner_values[i]); + } else { + flatbuffers::WriteScalar<typename T::value_type>( + struct_ptr + offset, inner_values[i]); + } + } + } else if constexpr (std::is_same_v<typename T::value_type, + FlatbuffersStructTag>) { + auto sub_object = + self.schema_->objects()->Get(field->type()->index()); + FUZZTEST_INTERNAL_CHECK( + field->type()->element() == reflection::BaseType::Obj && + sub_object->is_struct(), + "Field must be a struct type."); + auto container_corpus = + corpus_value.at(field->id()).GetAs<std::list<corpus_type>>(); + FlatbuffersStructUntypedDomainImpl sub_domain(self.schema_, + sub_object); + size_t i = 0; + for (const auto& element_corpus : container_corpus) { + for (const auto* nested_field : *sub_object->fields()) { + VisitFlatbufferField( + sub_domain.schema_, nested_field, + BuildValueVisitor{sub_domain, element_corpus, + struct_ptr + field->offset() + + i * sub_object->bytesize()}); + } + ++i; + } + } + } + } + }; +}; + +// Union domain corpus type. +struct FlatbuffersUnionDomainCorpusType { + using type_type = typename FlatbuffersUnionTypeDomainImpl::corpus_type; + using value_type = GenericDomainCorpusType; + + type_type type; + value_type value; +}; + +// Union domain value type. +struct FlatbuffersUnionDomainValueType { + using type_type = typename FlatbuffersUnionTypeDomainImpl::value_type; + using value_type = const void*; + + type_type type; + value_type value; +}; + +// Flatbuffers union domain implementation. +class FlatbuffersUnionDomainImpl + : public domain_implementor::DomainBase< + /*Derived=*/FlatbuffersUnionDomainImpl, + /*ValueType=*/FlatbuffersUnionDomainValueType, + /*CorpusType=*/FlatbuffersUnionDomainCorpusType> { + public: + friend class FlatbuffersTableUntypedDomainImpl; + + 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(&mutex_); + absl::MutexLock l_other(&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(&mutex_); + absl::MutexLock l_other(&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(&mutex_); + absl::MutexLock l_other(&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(&mutex_); + absl::MutexLock l_other(&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& corpus_value, absl::BitGenRef prng, + const domain_implementor::MutationMetadata& metadata, + bool only_shrink); + + uint64_t CountNumberOfFields(corpus_type& corpus_value); + + 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& corpus_value) const { + FUZZTEST_INTERNAL_CHECK(false, "GetValue is not supported for unions."); + } + + // Gets the type of the union field. + auto GetType(const corpus_type& corpus_value) const { + return type_domain_.GetValue(corpus_value.type); + } + + // 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& corpus_value) const; + + private: + const reflection::Schema* schema_; + const reflection::Enum* union_def_; + FlatbuffersEnumDomainImpl<typename FlatbuffersUnionTypeDomainImpl::value_type> + type_domain_; + mutable absl::Mutex mutex_; + mutable absl::flat_hash_map< + typename FlatbuffersUnionTypeDomainImpl::value_type, CopyableAny> + domains_ ABSL_GUARDED_BY(mutex_); + + // Creates flatbuffer from the corpus value. + std::optional<flatbuffers::uoffset_t> BuildValue( + const corpus_type& corpus_value, + flatbuffers::FlatBufferBuilder& builder) const; + + // Returns the domain for the given enum value. + template <typename T> + auto& GetCachedDomain(const reflection::EnumVal& enum_value) const { + using DomainT = decltype(GetDefaultDomainForType<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>, + GetDefaultDomainForType<T>(enum_value)) + .first; + } + return it->second.GetAs<DomainT>(); + } + + // Creates new or returns existing domain for the given enum value. + template <typename T> + auto GetDefaultDomainForType(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 FlatbuffersUntypedObjectDomainBase< + /*Derived=*/FlatbuffersTableUntypedDomainImpl, + /*ValueType=*/const flatbuffers::Table*> { + public: + template <typename Derived, typename ValueType> + friend class FlatbuffersUntypedObjectDomainBase; + template <typename T> + friend class FlatbuffersTableDomainImpl; + friend class FlatbuffersUnionDomainImpl; + + using typename FlatbuffersTableUntypedDomainImpl::DomainBase::corpus_type; + using typename FlatbuffersTableUntypedDomainImpl::DomainBase::value_type; + + explicit FlatbuffersTableUntypedDomainImpl( + const reflection::Schema* absl_nonnull schema, + const reflection::Object* absl_nonnull table_object) + : FlatbuffersUntypedObjectDomainBase(schema, table_object) {} + + // Converts the table pointer to a corpus value. + std::optional<corpus_type> FromValue(const value_type& value) const; + + private: + uint32_t BuildTable(const corpus_type& value, + flatbuffers::FlatBufferBuilder& builder) const; + + // 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& GetCachedDomain(const reflection::Field* absl_nonnull field) const { + auto get_opt_domain = [this, field]() { + auto opt_domain = OptionalOf(GetDefaultDomain<T>(schema_, field)); + if (!field->optional()) opt_domain.SetWithoutNull(); + return Domain<value_type_t<decltype(opt_domain)>>{opt_domain}; + }; + + using DomainT = decltype(get_opt_domain()); + // 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>, + get_opt_domain()) + .first; + } + return it->second.template GetAs<DomainT>(); + } + + struct FromValueVisitor { + const FlatbuffersTableUntypedDomainImpl& self; + value_type user_value; + corpus_type& corpus_value; + + 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.GetCachedDomain<T>(field); + value_type_t<std::decay_t<decltype(domain)>> inner_value; + + if constexpr (is_flatbuffers_enum_tag_v<T>) { + if (!field->optional() || user_value->CheckField(field->offset())) { + inner_value = user_value->GetField<typename T::type>( + field->offset(), field->default_integer()); + } + } else if constexpr (std::is_integral_v<T>) { + if (!field->optional() || user_value->CheckField(field->offset())) { + inner_value = std::optional(user_value->GetField<T>( + field->offset(), field->default_integer())); + } + } else if constexpr (std::is_floating_point_v<T>) { + if (!field->optional() || user_value->CheckField(field->offset())) { + inner_value = std::optional( + user_value->GetField<T>(field->offset(), field->default_real())); + } + } else if constexpr (std::is_same_v<T, std::string>) { + if (user_value->CheckField(field->offset())) { + inner_value = std::optional( + user_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 = + user_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 = + user_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 (!user_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.object_->fields()->LookupByKey( + (field->name()->str() + kUnionTypeFieldSuffix).c_str()); + if (type_field == nullptr) { + return; + } + auto union_type = + user_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 = + user_value->template GetPointer<flatbuffers::Struct*>( + field->offset()); + inner_value = + FlatbuffersUnionDomainValueType{union_type, union_value}; + } else { + auto union_value = + user_value->GetPointer<flatbuffers::Table*>(field->offset()); + inner_value = + FlatbuffersUnionDomainValueType{union_type, union_value}; + } + } + } + + auto inner = domain.FromValue(inner_value); + if (inner) { + corpus_value[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 = user_value->GetPointer<flatbuffers::Vector<ElementType>*>( + field->offset()); + inner_value = std::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 = user_value->GetPointer<flatbuffers::Vector<Underlaying>*>( + field->offset()); + inner_value = std::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 = user_value->GetPointer< + flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>*>( + field->offset()); + inner_value = std::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 = user_value->GetPointer< + flatbuffers::Vector<flatbuffers::Offset<flatbuffers::Table>>*>( + field->offset()); + inner_value = std::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 = + user_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.object_->fields()->LookupByKey( + (field->name()->str() + kUnionTypeFieldSuffix).c_str()); + if (type_field == nullptr) { + return; + } + auto type_vec = user_value->GetPointer<flatbuffers::Vector<uint8_t>*>( + type_field->offset()); + auto value_vec = + user_value + ->GetPointer<flatbuffers::Vector<flatbuffers::Offset<void>>*>( + field->offset()); + inner_value = + std::optional(typename value_type_t<Domain>::value_type{}); + inner_value->reserve(value_vec->size()); + for (auto i = 0; i < value_vec->size(); ++i) { + inner_value->emplace_back(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::mapped_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.GetCachedDomain<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.GetCachedDomain<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.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)>>::type_type> + vec_types; + vec_types.reserve(container_corpus.size()); + std::vector<flatbuffers::Offset<flatbuffers::Table>> vec_offsets; + 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 { + if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T> || + is_flatbuffers_enum_tag_v<T>) { + auto& domain = self.GetCachedDomain<T>(field); + auto v = domain.GetValue(corpus_value); + if (!v) { + return; + } + // Store "inline field" value inline. + builder.AddElement(field->offset(), v.value()); + } 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.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.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()); + } + } + } + }; +}; + +// Domain factory for flatbuffers fields. +template <typename T> +auto GetDefaultDomain(const reflection::Schema* absl_nonnull schema, + const reflection::Field* absl_nonnull field) { + // Used to satisfy the compiler return type deduction rules. + auto placeholder = Arbitrary<bool>(); + + if constexpr (is_flatbuffers_array_tag_v<T>) { + using ElementT = typename T::value_type; + auto size = field->type()->fixed_length(); + if constexpr (is_flatbuffers_enum_tag_v<ElementT>) { + auto enum_object = schema->enums()->Get(field->type()->index()); + return VectorOf(FlatbuffersEnumDomainImpl<typename ElementT::type>( + enum_object)) + .WithSize(size); + } else if constexpr (std::is_same_v<ElementT, FlatbuffersStructTag>) { + const reflection::Object* sub_object = + schema->objects()->Get(field->type()->index()); + return VectorOf(FlatbuffersStructUntypedDomainImpl{schema, sub_object}) + .WithSize(size); + } else if constexpr (std::is_integral_v<ElementT> || + std::is_floating_point_v<ElementT>) { + return VectorOf(Arbitrary<ElementT>()).WithSize(size); + } else { + FUZZTEST_INTERNAL_CHECK(false, + "Unsupported type: ", field->type()->element()); + return VectorOf(placeholder).WithSize(0); + } + } else if constexpr (is_flatbuffers_enum_tag_v<T>) { + auto enum_object = schema->enums()->Get(field->type()->index()); + return FlatbuffersEnumDomainImpl<typename T::type>(enum_object); + } else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) { + auto table_object = schema->objects()->Get(field->type()->index()); + return FlatbuffersTableUntypedDomainImpl{schema, table_object}; + } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) { + auto struct_object = schema->objects()->Get(field->type()->index()); + return FlatbuffersStructUntypedDomainImpl{schema, struct_object}; + } else if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) { + auto union_type = schema->enums()->Get(field->type()->index()); + return FlatbuffersUnionDomainImpl{schema, union_type}; + } else if constexpr (is_flatbuffers_vector_tag_v<T>) { + return VectorOf(GetDefaultDomain<typename T::value_type>(schema, field)) + .WithMaxSize(std::numeric_limits<flatbuffers::uoffset_t>::max()); + } else { + return Arbitrary<T>(); + } +} + +// Corpus type for the table domain +struct FlatbuffersTableDomainCorpusType { + // Map of field ids to field values. + typename FlatbuffersTableUntypedDomainImpl::corpus_type untyped_corpus; + // Serialized flatbuffer. + mutable std::vector<uint8_t> buffer; +}; + +// 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=*/FlatbuffersTableDomainImpl<T>, + /*ValueType=*/const T*, + /*CorpusType=*/FlatbuffersTableDomainCorpusType> { + 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}; + } + + // 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); + // Return corpus value: pair of the map and the serialized buffer. + return FlatbuffersTableDomainCorpusType{val, {}}; + } + + // Returns the number of fields in the table. + uint64_t CountNumberOfFields(corpus_type& val) { + return inner_->CountNumberOfFields(val.untyped_corpus); + } + + // 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.untyped_corpus, prng, metadata, only_shrink); + } + + // Converts corpus value into the exact flatbuffer. + value_type GetValue(const corpus_type& value) const { + value.buffer = BuildBuffer(value.untyped_corpus); + return flatbuffers::GetRoot<T>(value.buffer.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::optional( + FlatbuffersTableDomainCorpusType{*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::optional( + FlatbuffersTableDomainCorpusType{*val, BuildBuffer(*val)}); + } + + // Returns the serialized corpus value. + IRObject SerializeCorpus(const corpus_type& corpus_value) const { + return inner_->SerializeCorpus(corpus_value.untyped_corpus); + } + + // Returns the status of the given corpus value. + absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const { + return inner_->ValidateCorpusValue(corpus_value.untyped_corpus); + } + + private: + std::optional<FlatbuffersTableUntypedDomainImpl> inner_; + + struct Printer { + const FlatbuffersTableUntypedDomainImpl& inner; + + void PrintCorpusValue(const corpus_type& value, + domain_implementor::RawSink out, + domain_implementor::PrintMode mode) const { + inner.GetPrinter().PrintCorpusValue(value.untyped_corpus, out, mode); + } + }; + + std::vector<uint8_t> BuildBuffer( + const corpus_type_t<FlatbuffersTableUntypedDomainImpl>& val) const { + flatbuffers::FlatBufferBuilder builder; + 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()); + return buffer; + } +}; + +template <typename T> +class ArbitraryImpl<const 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..515def1 --- /dev/null +++ b/fuzztest/internal/test_flatbuffers.fbs
@@ -0,0 +1,234 @@ +namespace fuzztest.internal; + +enum ByteEnum: byte { + First, + Second +} +enum ShortEnum: short { + First, + Second +} + +enum IntEnum: int { + First, + Second +} + +enum LongEnum: long { + First, + Second +} + +enum UByteEnum: ubyte { + First, + Second +} + +enum UShortEnum: ushort { + First, + Second +} +enum UIntEnum: uint { + First, + Second +} +enum ULongEnum: ulong { + First, + Second +} + +struct BoolStruct { + b: bool; + a_b: [bool:2]; +} + +struct DefaultStruct { + b: bool; + i8: byte; + i16: short; + i32: int; + i64: long; + u8: ubyte; + u16: ushort; + u32: uint; + u64: ulong; + f: float; + d: double; + ei8: ByteEnum; + ei16: ShortEnum; + ei32: IntEnum; + ei64: LongEnum; + eu8: UByteEnum; + eu16: UShortEnum; + eu32: UIntEnum; + eu64: ULongEnum; + s: BoolStruct; + a_b: [bool:2]; + a_i8: [byte:2]; + a_i16: [short:2]; + a_i32: [int:2]; + a_i64: [long:2]; + a_u8: [ubyte:2]; + a_u16: [ushort:2]; + a_u32: [uint:2]; + a_u64: [ulong:2]; + a_f: [float:2]; + a_d: [double:2]; + a_ei8: [ByteEnum:2]; + a_ei16: [ShortEnum:2]; + a_ei32: [IntEnum:2]; + a_ei64: [LongEnum:2]; + a_eu8: [UByteEnum:2]; + a_eu16: [UShortEnum:2]; + a_eu32: [UIntEnum:2]; + a_eu64: [ULongEnum:2]; + a_s: [BoolStruct:2]; +} + +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; + ei8: ByteEnum; + ei16: ShortEnum; + ei32: IntEnum; + ei64: LongEnum; + eu8: UByteEnum; + eu16: UShortEnum; + eu32: UIntEnum; + eu64: ULongEnum; + 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_ei8: [ByteEnum]; + v_ei16: [ShortEnum]; + v_ei32: [IntEnum]; + v_ei64: [LongEnum]; + v_eu8: [UByteEnum]; + v_eu16: [UShortEnum]; + v_eu32: [UIntEnum]; + v_eu64: [ULongEnum]; + 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; // always optional, no need to specify the default value. + ei8: ByteEnum = null; + ei16: ShortEnum = null; + ei32: IntEnum = null; + ei64: LongEnum = null; + eu8: UByteEnum = null; + eu16: UShortEnum = null; + eu32: UIntEnum = null; + eu64: ULongEnum = 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_ei8: [ByteEnum]; + v_ei16: [ShortEnum]; + v_ei32: [IntEnum]; + v_ei64: [LongEnum]; + v_eu8: [UByteEnum]; + v_eu16: [UShortEnum]; + v_eu32: [UIntEnum]; + v_eu64: [ULongEnum]; + 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_ei8: [ByteEnum] (required); + v_ei16: [ShortEnum] (required); + v_ei32: [IntEnum] (required); + v_ei64: [LongEnum] (required); + v_eu8: [UByteEnum] (required); + v_eu16: [UShortEnum] (required); + v_eu32: [UIntEnum] (required); + v_eu64: [ULongEnum] (required); + v_t: [BoolTable] (required); + v_u: [Union] (required); + v_s: [DefaultStruct] (required); +} + +root_type DefaultTable;