Add vector field support to FlatbuffersTableUntypedDomainImpl PiperOrigin-RevId: 745927987
diff --git a/domain_tests/arbitrary_domains_flatbuffers_test.cc b/domain_tests/arbitrary_domains_flatbuffers_test.cc index 3d397a2..b370da1 100644 --- a/domain_tests/arbitrary_domains_flatbuffers_test.cc +++ b/domain_tests/arbitrary_domains_flatbuffers_test.cc
@@ -19,6 +19,7 @@ #include <cstring> #include <optional> #include <string> +#include <type_traits> #include <vector> #include "gmock/gmock.h" @@ -36,17 +37,27 @@ #include "./domain_tests/domain_testing.h" #include "./fuzztest/flatbuffers.h" #include "./fuzztest/internal/meta.h" +#include "./fuzztest/internal/test_flatbuffers_64bits_generated.h" #include "./fuzztest/internal/test_flatbuffers_generated.h" namespace fuzztest { namespace { using ::fuzztest::internal::BoolTable; +using ::fuzztest::internal::ByteEnum; using ::fuzztest::internal::DefaultTable; +using ::fuzztest::internal::DefaultTable64; +using ::fuzztest::internal::IntEnum; +using ::fuzztest::internal::LongEnum; using ::fuzztest::internal::OptionalTable; using ::fuzztest::internal::RecursiveTable; using ::fuzztest::internal::RequiredTable; +using ::fuzztest::internal::ShortEnum; +using ::fuzztest::internal::UByteEnum; +using ::fuzztest::internal::UIntEnum; +using ::fuzztest::internal::ULongEnum; using ::fuzztest::internal::UnsupportedTypesTable; +using ::fuzztest::internal::UShortEnum; using ::testing::_; using ::testing::AllOf; using ::testing::Each; @@ -82,6 +93,24 @@ 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 <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); +} + template <> inline bool Eq<DefaultTable>(const DefaultTable& lhs, const DefaultTable& rhs) { const bool eq_b = lhs.b() == rhs.b(); @@ -105,14 +134,71 @@ const bool eq_eu32 = lhs.eu32() == rhs.eu32(); const bool eq_eu64 = lhs.eu64() == rhs.eu64(); const bool eq_t = Eq(lhs.t(), rhs.t()); + const bool eq_v_b = VectorEq(lhs.v_b(), rhs.v_b()); + const bool eq_v_i8 = VectorEq(lhs.v_i8(), rhs.v_i8()); + const bool eq_v_i16 = VectorEq(lhs.v_i16(), rhs.v_i16()); + const bool eq_v_i32 = VectorEq(lhs.v_i32(), rhs.v_i32()); + const bool eq_v_i64 = VectorEq(lhs.v_i64(), rhs.v_i64()); + const bool eq_v_u8 = VectorEq(lhs.v_u8(), rhs.v_u8()); + const bool eq_v_u16 = VectorEq(lhs.v_u16(), rhs.v_u16()); + const bool eq_v_u32 = VectorEq(lhs.v_u32(), rhs.v_u32()); + const bool eq_v_u64 = VectorEq(lhs.v_u64(), rhs.v_u64()); + const bool eq_v_f = VectorEq(lhs.v_f(), rhs.v_f()); + const bool eq_v_d = VectorEq(lhs.v_d(), rhs.v_d()); + const bool eq_v_str = VectorEq(lhs.v_str(), rhs.v_str()); + const bool eq_v_ei8 = VectorEq(lhs.v_ei8(), rhs.v_ei8()); + const bool eq_v_ei16 = VectorEq(lhs.v_ei16(), rhs.v_ei16()); + const bool eq_v_ei32 = VectorEq(lhs.v_ei32(), rhs.v_ei32()); + const bool eq_v_ei64 = VectorEq(lhs.v_ei64(), rhs.v_ei64()); + const bool eq_v_eu8 = VectorEq(lhs.v_eu8(), rhs.v_eu8()); + const bool eq_v_eu16 = VectorEq(lhs.v_eu16(), rhs.v_eu16()); + const bool eq_v_eu32 = VectorEq(lhs.v_eu32(), rhs.v_eu32()); + const bool eq_v_eu64 = VectorEq(lhs.v_eu64(), rhs.v_eu64()); + const bool eq_v_t = VectorEq(lhs.v_t(), rhs.v_t()); 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_ei32 && eq_ei64 && eq_eu8 && eq_eu16 && eq_eu32 && eq_eu64 && + eq_t && 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; } const internal::DefaultTable* CreateDefaultTable( flatbuffers::FlatBufferBuilder& fbb) { auto bool_table_offset = internal::CreateBoolTable(fbb, true); + 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}; + auto table_offset = internal::CreateDefaultTableDirect(fbb, /*b=*/true, @@ -135,7 +221,28 @@ /*eu16=*/internal::UShortEnum_Second, /*eu32=*/internal::UIntEnum_Second, /*eu64=*/internal::ULongEnum_Second, - /*t=*/bool_table_offset); + /*t=*/bool_table_offset, + /*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); fbb.Finish(table_offset); return flatbuffers::GetRoot<DefaultTable>(fbb.GetBufferPointer()); } @@ -270,6 +377,7 @@ EXPECT_EQ(new_table->u64(), 8); EXPECT_EQ(new_table->f(), 9.0); EXPECT_EQ(new_table->d(), 10.0); + ASSERT_THAT(new_table->str(), NotNull()); 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); @@ -281,6 +389,96 @@ EXPECT_EQ(new_table->eu64(), internal::ULongEnum_Second); ASSERT_THAT(new_table->t(), NotNull()); EXPECT_EQ(new_table->t()->b(), true); + ASSERT_THAT(new_table->v_b(), NotNull()); + EXPECT_EQ(new_table->v_b()->size(), 2); + EXPECT_EQ(new_table->v_b()->Get(0), true); + EXPECT_EQ(new_table->v_b()->Get(1), false); + ASSERT_THAT(new_table->v_i8(), NotNull()); + EXPECT_EQ(new_table->v_i8()->size(), 3); + EXPECT_EQ(new_table->v_i8()->Get(0), 1); + EXPECT_EQ(new_table->v_i8()->Get(1), 2); + EXPECT_EQ(new_table->v_i8()->Get(2), 3); + ASSERT_THAT(new_table->v_i16(), NotNull()); + EXPECT_EQ(new_table->v_i16()->size(), 3); + EXPECT_EQ(new_table->v_i16()->Get(0), 1); + EXPECT_EQ(new_table->v_i16()->Get(1), 2); + EXPECT_EQ(new_table->v_i16()->Get(2), 3); + ASSERT_THAT(new_table->v_i32(), NotNull()); + EXPECT_EQ(new_table->v_i32()->size(), 3); + EXPECT_EQ(new_table->v_i32()->Get(0), 1); + EXPECT_EQ(new_table->v_i32()->Get(1), 2); + EXPECT_EQ(new_table->v_i32()->Get(2), 3); + ASSERT_THAT(new_table->v_i64(), NotNull()); + EXPECT_EQ(new_table->v_i64()->size(), 3); + EXPECT_EQ(new_table->v_i64()->Get(0), 1); + EXPECT_EQ(new_table->v_i64()->Get(1), 2); + EXPECT_EQ(new_table->v_i64()->Get(2), 3); + ASSERT_THAT(new_table->v_u8(), NotNull()); + EXPECT_EQ(new_table->v_u8()->size(), 3); + EXPECT_EQ(new_table->v_u8()->Get(0), 1); + EXPECT_EQ(new_table->v_u8()->Get(1), 2); + EXPECT_EQ(new_table->v_u8()->Get(2), 3); + ASSERT_THAT(new_table->v_u16(), NotNull()); + EXPECT_EQ(new_table->v_u16()->size(), 3); + EXPECT_EQ(new_table->v_u16()->Get(0), 1); + EXPECT_EQ(new_table->v_u16()->Get(1), 2); + EXPECT_EQ(new_table->v_u16()->Get(2), 3); + ASSERT_THAT(new_table->v_u32(), NotNull()); + EXPECT_EQ(new_table->v_u32()->size(), 3); + EXPECT_EQ(new_table->v_u32()->Get(0), 1); + EXPECT_EQ(new_table->v_u32()->Get(1), 2); + EXPECT_EQ(new_table->v_u32()->Get(2), 3); + ASSERT_THAT(new_table->v_u64(), NotNull()); + EXPECT_EQ(new_table->v_u64()->size(), 3); + EXPECT_EQ(new_table->v_u64()->Get(0), 1); + EXPECT_EQ(new_table->v_u64()->Get(1), 2); + EXPECT_EQ(new_table->v_u64()->Get(2), 3); + ASSERT_THAT(new_table->v_f(), NotNull()); + EXPECT_EQ(new_table->v_f()->size(), 3); + EXPECT_EQ(new_table->v_f()->Get(0), 1); + EXPECT_EQ(new_table->v_f()->Get(1), 2); + EXPECT_EQ(new_table->v_f()->Get(2), 3); + ASSERT_THAT(new_table->v_d(), NotNull()); + EXPECT_EQ(new_table->v_d()->size(), 3); + EXPECT_EQ(new_table->v_d()->Get(0), 1); + EXPECT_EQ(new_table->v_d()->Get(1), 2); + EXPECT_EQ(new_table->v_d()->Get(2), 3); + EXPECT_EQ(new_table->v_str()->size(), 3); + EXPECT_EQ(new_table->v_str()->Get(0)->str(), "foo"); + EXPECT_EQ(new_table->v_str()->Get(1)->str(), "bar"); + EXPECT_EQ(new_table->v_str()->Get(2)->str(), "baz"); + ASSERT_THAT(new_table->v_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); } TEST(FlatbuffersTableDomainImplTest, InitGeneratesSeeds) { @@ -300,12 +498,17 @@ 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}, + {"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}, {"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}, }; auto domain = Arbitrary<const DefaultTable*>(); @@ -341,6 +544,27 @@ mutated_fields["eu32"] |= mut->eu32() != init->eu32(); mutated_fields["eu64"] |= mut->eu64() != init->eu64(); mutated_fields["t"] |= !Eq(mut->t(), init->t()); + 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()); if (std::all_of(mutated_fields.begin(), mutated_fields.end(), [](const auto& p) { return p.second; })) { @@ -354,6 +578,28 @@ TEST(FlatbuffersTableDomainImplTest, OptionalTableEventuallyBecomeEmpty) { flatbuffers::FlatBufferBuilder fbb; auto bool_table_offset = internal::CreateBoolTable(fbb, true); + 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{}; auto table_offset = internal::CreateOptionalTableDirect(fbb, true, // b @@ -376,7 +622,28 @@ internal::UShortEnum_Second, // eu16 internal::UIntEnum_Second, // eu32 internal::ULongEnum_Second, // eu64 - bool_table_offset // t + bool_table_offset, // t + &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 ); fbb.Finish(table_offset); auto table = flatbuffers::GetRoot<OptionalTable>(fbb.GetBufferPointer()); @@ -386,12 +653,17 @@ 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}, + {"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}, {"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}, }; // Optional fields are mutated to null with probability 1/100. @@ -422,6 +694,27 @@ null_fields["eu32"] |= !v->eu32().has_value(); null_fields["eu64"] |= !v->eu64().has_value(); null_fields["t"] |= v->t() == 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; if (std::all_of(null_fields.begin(), null_fields.end(), [](const auto& p) { return p.second; })) { @@ -460,39 +753,55 @@ 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 - )); + 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("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 + )); } TEST(FlatbuffersTableDomainImplTest, UnsupportedTypesRemainNull) { absl::flat_hash_map<std::string, bool> null_fields{ - {"u", true}, {"s", true}, {"v_b", true}, {"v_i8", true}, - {"v_i16", true}, {"v_i32", true}, {"v_i64", true}, {"v_u8", true}, - {"v_u16", true}, {"v_u32", true}, {"v_u64", true}, {"v_f", true}, - {"v_d", true}, {"v_str", true}, {"v_ei8", true}, {"v_ei16", true}, - {"v_ei32", true}, {"v_ei64", true}, {"v_eu8", true}, {"v_eu16", true}, - {"v_eu32", true}, {"v_eu64", true}, {"v_t", true}, {"v_u", true}, - {"v_s", true}}; + {"u", true}, {"s", true}, {"v_u", true}, {"v_s", true}}; auto domain = Arbitrary<const UnsupportedTypesTable*>(); @@ -506,27 +815,6 @@ null_fields["u"] &= mut->u() == nullptr; null_fields["s"] &= mut->s() == nullptr; - null_fields["v_b"] &= mut->v_b() == nullptr; - null_fields["v_i8"] &= mut->v_i8() == nullptr; - null_fields["v_i16"] &= mut->v_i16() == nullptr; - null_fields["v_i32"] &= mut->v_i32() == nullptr; - null_fields["v_i64"] &= mut->v_i64() == nullptr; - null_fields["v_u8"] &= mut->v_u8() == nullptr; - null_fields["v_u16"] &= mut->v_u16() == nullptr; - null_fields["v_u32"] &= mut->v_u32() == nullptr; - null_fields["v_u64"] &= mut->v_u64() == nullptr; - null_fields["v_f"] &= mut->v_f() == nullptr; - null_fields["v_d"] &= mut->v_d() == nullptr; - null_fields["v_str"] &= mut->v_str() == nullptr; - null_fields["v_ei8"] &= mut->v_ei8() == nullptr; - null_fields["v_ei16"] &= mut->v_ei16() == nullptr; - null_fields["v_ei32"] &= mut->v_ei32() == nullptr; - null_fields["v_ei64"] &= mut->v_ei64() == nullptr; - null_fields["v_eu8"] &= mut->v_eu8() == nullptr; - null_fields["v_eu16"] &= mut->v_eu16() == nullptr; - null_fields["v_eu32"] &= mut->v_eu32() == nullptr; - null_fields["v_eu64"] &= mut->v_eu64() == nullptr; - null_fields["v_t"] &= mut->v_t() == nullptr; null_fields["v_u"] &= mut->v_u() == nullptr; null_fields["v_s"] &= mut->v_s() == nullptr; @@ -573,7 +861,7 @@ auto domain = Arbitrary<const OptionalTable*>(); auto corpus = domain.FromValue(table); ASSERT_TRUE(corpus.has_value()); - EXPECT_EQ(domain.CountNumberOfFields(corpus.value()), 21); + EXPECT_EQ(domain.CountNumberOfFields(corpus.value()), 42); } TEST(FlatbuffersTableDomainImplTest, RecursiveTable) { @@ -601,5 +889,37 @@ ASSERT_THAT(new_table, IsNull()); } +TEST(FlatbuffersTableDomainImplTest, DefaultTable64ValueRoundTrip) { + flatbuffers::FlatBufferBuilder64 fbb; + auto str_offset = fbb.CreateString<flatbuffers::Offset64>("foo bar baz"); + std::vector<uint8_t> v_u8 = {1, 2, 3}; + auto v_u8_offset = fbb.CreateVector64(v_u8); + auto table_offset = + internal::CreateDefaultTable64(fbb, str_offset, v_u8_offset); + fbb.Finish(table_offset); + auto table = flatbuffers::GetRoot<DefaultTable64>(fbb.GetBufferPointer()); + + auto domain = Arbitrary<const DefaultTable64*>(); + 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); + ASSERT_THAT(new_table, NotNull()); + ASSERT_THAT(new_table->str(), NotNull()); + EXPECT_EQ(new_table->str()->str(), "foo bar baz"); + ASSERT_THAT(new_table->v_u8(), NotNull()); + ASSERT_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); +} + } // namespace } // namespace fuzztest
diff --git a/fuzztest/internal/BUILD b/fuzztest/internal/BUILD index 3eb5289..016d20b 100644 --- a/fuzztest/internal/BUILD +++ b/fuzztest/internal/BUILD
@@ -551,8 +551,13 @@ flatbuffer_library_public( name = "test_flatbuffers_fbs", - srcs = ["test_flatbuffers.fbs"], + srcs = [ + "test_flatbuffers.fbs", + "test_flatbuffers_64bits.fbs", + ], outs = [ + "test_flatbuffers_64bits_bfbs_generated.h", + "test_flatbuffers_64bits_generated.h", "test_flatbuffers_bfbs_generated.h", "test_flatbuffers_generated.h", ],
diff --git a/fuzztest/internal/CMakeLists.txt b/fuzztest/internal/CMakeLists.txt index 667a695..1fa3ce7 100644 --- a/fuzztest/internal/CMakeLists.txt +++ b/fuzztest/internal/CMakeLists.txt
@@ -504,6 +504,7 @@ test_flatbuffers_headers SCHEMAS "test_flatbuffers.fbs" + "test_flatbuffers_64bits.fbs" FLAGS --bfbs-gen-embed --gen-name-strings TESTONLY
diff --git a/fuzztest/internal/domains/BUILD b/fuzztest/internal/domains/BUILD index b5b8202..4fb0483 100644 --- a/fuzztest/internal/domains/BUILD +++ b/fuzztest/internal/domains/BUILD
@@ -202,6 +202,7 @@ "@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", ], )
diff --git a/fuzztest/internal/domains/flatbuffers_domain_impl.cc b/fuzztest/internal/domains/flatbuffers_domain_impl.cc index 1a94bbf..cd28242 100644 --- a/fuzztest/internal/domains/flatbuffers_domain_impl.cc +++ b/fuzztest/internal/domains/flatbuffers_domain_impl.cc
@@ -137,7 +137,9 @@ } field_counter++; - if (field->type()->base_type() == reflection::BaseType::Obj) { + 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 += @@ -146,7 +148,16 @@ selected_field_index - field_counter); } // TODO: Add support for structs. + } else if (base_type == reflection::BaseType::Vector) { + field_counter += MutateVectorField<FlatbuffersVectorTag>( + field, val, prng, metadata, only_shrink, + selected_field_index - field_counter); + } else if (base_type == reflection::BaseType::Vector64) { + field_counter += MutateVectorField<FlatbuffersVector64Tag>( + field, val, prng, metadata, only_shrink, + selected_field_index - field_counter); } + // TODO: Add support for unions. if (field_counter > selected_field_index) { return field_counter; @@ -266,15 +277,25 @@ auto sub_object = schema_->objects()->Get(field->type()->index()); return !sub_object->is_struct(); }; + 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) { + auto sub_object = schema_->objects()->Get(field->type()->index()); + return !sub_object->is_struct(); + } + } return false; } uint32_t FlatbuffersTableUntypedDomainImpl::BuildTable( - const corpus_type& value, flatbuffers::FlatBufferBuilder& builder) const { + const corpus_type& value, flatbuffers::FlatBufferBuilder64& 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> + absl::flat_hash_map<typename corpus_type::key_type, flatbuffers::uoffset64_t> offsets; // Some fields are stored inline in the flatbuffer table itself (a.k.a
diff --git a/fuzztest/internal/domains/flatbuffers_domain_impl.h b/fuzztest/internal/domains/flatbuffers_domain_impl.h index c95af4c..e3834e8 100644 --- a/fuzztest/internal/domains/flatbuffers_domain_impl.h +++ b/fuzztest/internal/domains/flatbuffers_domain_impl.h
@@ -20,6 +20,7 @@ #include <cstdint> #include <initializer_list> #include <limits> +#include <list> #include <optional> #include <string> #include <type_traits> @@ -38,21 +39,26 @@ #include "absl/strings/str_format.h" #include "absl/synchronization/mutex.h" #include "flatbuffers/base.h" +#include "flatbuffers/buffer.h" #include "flatbuffers/flatbuffer_builder.h" +#include "flatbuffers/reflection.h" #include "flatbuffers/reflection_generated.h" #include "flatbuffers/string.h" #include "flatbuffers/table.h" +#include "flatbuffers/vector.h" #include "flatbuffers/verifier.h" #include "./common/logging.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/meta.h" #include "./fuzztest/internal/serialization.h" #include "./fuzztest/internal/status.h" +#include "./fuzztest/internal/type_support.h" namespace fuzztest::internal { @@ -77,73 +83,152 @@ 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; + +template <typename T> +struct FlatbuffersVector64Tag { + using value_type = T; +}; + +template <typename T> +struct is_flatbuffers_vector64_tag : std::false_type {}; + +template <typename T> +struct is_flatbuffers_vector64_tag<FlatbuffersVector64Tag<T>> : std::true_type { +}; + +template <typename T> +inline constexpr bool is_flatbuffers_vector64_tag_v = + is_flatbuffers_vector64_tag<T>::value; + +template <typename T> +inline constexpr bool is_any_flatbuffers_vector_tag_v = + is_flatbuffers_vector_tag_v<T> || is_flatbuffers_vector64_tag_v<T>; + +template <typename T> +struct flatbuffers_vector_tag_offset; + +template <typename T> +struct flatbuffers_vector_tag_offset<FlatbuffersVectorTag<T>> { + using type = flatbuffers::uoffset_t; +}; + +template <typename T> +struct flatbuffers_vector_tag_offset<FlatbuffersVector64Tag<T>> { + using type = flatbuffers::uoffset64_t; +}; + +template <typename T> +using flatbuffers_vector_tag_offset_t = + typename flatbuffers_vector_tag_offset<T>::type; + struct FlatbuffersArrayTag; + +// Flatbuffers container element type detection. +template <typename T, typename ValueType> +inline constexpr bool is_flatbuffers_container_of_v = []() constexpr { + if constexpr (is_flatbuffers_vector_tag_v<T> || + is_flatbuffers_vector64_tag_v<T>) { + return std::is_same_v<ValueType, typename T::value_type>; + } else { + return false; + } +}(); + struct FlatbuffersTableTag; struct FlatbuffersStructTag; struct FlatbuffersUnionTag; -struct FlatbuffersVectorTag; + +// Helper to wrap the visitor with the correct tag type. +template <template <typename> typename Wrapper, typename Visitor> +struct VisitorTagWrapper { + Visitor&& visitor; + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) const { + std::forward<Visitor>(visitor).template Visit<Wrapper<T>>(field); + } +}; // Dynamic to static dispatch visitor pattern. -template <typename Visitor> -auto VisitFlatbufferField(const reflection::Schema* absl_nonnull schema, +template <typename Visitor, bool in_container = false> +void 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()) { + Visitor&& visitor) { + const auto type = + in_container ? field->type()->element() : field->type()->base_type(); + const auto field_index = field->type()->index(); + const bool is_enum = flatbuffers::IsInteger(type) && field_index >= 0; + switch (type) { case reflection::BaseType::Bool: visitor.template Visit<bool>(field); break; case reflection::BaseType::Byte: - if (field_index >= 0) { + if (is_enum) { 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) { + if (is_enum) { visitor.template Visit<FlatbuffersEnumTag<uint8_t>>(field); } else { visitor.template Visit<uint8_t>(field); } break; + case reflection::BaseType::Short: + if (is_enum) { + visitor.template Visit<FlatbuffersEnumTag<int16_t>>(field); + } else { + visitor.template Visit<int16_t>(field); + } + break; case reflection::BaseType::UShort: - if (field_index >= 0) { + if (is_enum) { visitor.template Visit<FlatbuffersEnumTag<uint16_t>>(field); } else { visitor.template Visit<uint16_t>(field); } break; + case reflection::BaseType::Int: + if (is_enum) { + visitor.template Visit<FlatbuffersEnumTag<int32_t>>(field); + } else { + visitor.template Visit<int32_t>(field); + } + break; case reflection::BaseType::UInt: - if (field_index >= 0) { + if (is_enum) { visitor.template Visit<FlatbuffersEnumTag<uint32_t>>(field); } else { visitor.template Visit<uint32_t>(field); } break; + case reflection::BaseType::Long: + if (is_enum) { + visitor.template Visit<FlatbuffersEnumTag<int64_t>>(field); + } else { + visitor.template Visit<int64_t>(field); + } + break; case reflection::BaseType::ULong: - if (field_index >= 0) { + if (is_enum) { visitor.template Visit<FlatbuffersEnumTag<uint64_t>>(field); } else { visitor.template Visit<uint64_t>(field); @@ -159,30 +244,47 @@ visitor.template Visit<std::string>(field); break; case reflection::BaseType::Vector: + if constexpr (in_container) { + FUZZTEST_LOG(FATAL) << "Nested containers are not supported."; + } else { + VisitFlatbufferField<VisitorTagWrapper<FlatbuffersVectorTag, Visitor>, + true>(schema, field, + {std::forward<Visitor>(visitor)}); + } + break; case reflection::BaseType::Vector64: - visitor.template Visit<FlatbuffersVectorTag>(field); + if constexpr (in_container) { + FUZZTEST_LOG(FATAL) << "Nested containers are not supported."; + } else { + VisitFlatbufferField<VisitorTagWrapper<FlatbuffersVector64Tag, Visitor>, + true>(schema, field, + {std::forward<Visitor>(visitor)}); + } break; case reflection::BaseType::Array: - visitor.template Visit<FlatbuffersArrayTag>(field); + if constexpr (in_container) { + FUZZTEST_LOG(FATAL) << "Nested containers are not supported."; + } else { + visitor.template Visit<FlatbuffersArrayTag>(field); + } break; - case reflection::BaseType::Obj: { - auto sub_object = schema->objects()->Get(field->type()->index()); - if (sub_object->is_struct()) { + case reflection::BaseType::Obj: + if (schema->objects()->Get(field_index)->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 + // Noop: Union type fields are handled when processing their + // corresponding union field break; default: FUZZTEST_LOG(FATAL) << "Unsupported base type: " - << field->type()->base_type(); + << reflection::EnumNameBaseType(type); } } @@ -296,7 +398,7 @@ // Domain implementation for flatbuffers untyped tables. // The corpus type is a map of field ids to field values. class FlatbuffersTableUntypedDomainImpl - : public fuzztest::domain_implementor::DomainBase< + : public domain_implementor::DomainBase< /*Derived=*/FlatbuffersTableUntypedDomainImpl, /*ValueType=*/const flatbuffers::Table* absl_nonnull, /*CorpusType=*/ @@ -374,7 +476,7 @@ bool IsSupportedField(const reflection::Field* absl_nonnull field) const; uint32_t BuildTable(const corpus_type& value, - flatbuffers::FlatBufferBuilder& builder) const; + flatbuffers::FlatBufferBuilder64& builder) const; // Returns the domain for the given field. // The domain is cached, and the same instance is returned for the same field. @@ -410,6 +512,32 @@ return it != table_object_->fields()->end() ? *it : nullptr; } + template <template <typename> typename VectorTag> + uint64_t MutateVectorField( + const reflection::Field* absl_nonnull field, corpus_type& val, + absl::BitGenRef prng, + const domain_implementor::MutationMetadata& metadata, bool only_shrink, + uint64_t sub_selected_field_index) { + 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()) { + return GetCachedDomain<VectorTag<FlatbuffersTableTag>>(field) + .MutateSelectedField(val[field->id()], prng, metadata, only_shrink, + sub_selected_field_index); + } else { + return GetCachedDomain<VectorTag<FlatbuffersStructTag>>(field) + .MutateSelectedField(val[field->id()], prng, metadata, only_shrink, + sub_selected_field_index); + } + } else if (elem_type == reflection::BaseType::Union) { + return GetCachedDomain<VectorTag<FlatbuffersUnionTag>>(field) + .MutateSelectedField(val[field->id()], prng, metadata, only_shrink, + sub_selected_field_index); + } + return 0; + } + struct SerializeVisitor { const FlatbuffersTableUntypedDomainImpl& self; const GenericDomainCorpusType& corpus_value; @@ -431,7 +559,8 @@ [[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; + using InnerDomain = std::decay_t<decltype(domain)>; + value_type_t<InnerDomain> inner_value; if constexpr (is_flatbuffers_enum_tag_v<T>) { if (!field->optional() || user_value->CheckField(field->offset())) { @@ -450,9 +579,15 @@ } } 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()); + if (field->offset64()) { + inner_value = std::optional( + user_value->GetPointer64<flatbuffers::String*>(field->offset()) + ->str()); + } else { + 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()); @@ -461,21 +596,95 @@ << "Field must be a table type."; inner_value = user_value->GetPointer<const flatbuffers::Table*>(field->offset()); + } else if constexpr (is_any_flatbuffers_vector_tag_v<T>) { + using ElementType = typename T::value_type; + if (user_value->CheckField(field->offset())) { + inner_value = typename value_type_t<InnerDomain>::value_type{}; + VisitVector<ElementType, std::decay_t<decltype(domain)>, + flatbuffers_vector_tag_offset_t<T>>(field, inner_value); + } } - auto inner = domain.FromValue(inner_value); - if (inner) { - corpus_value[field->id()] = *std::move(inner); + if (inner_value) { + auto inner = domain.FromValue(inner_value); + if (inner) { + corpus_value[field->id()] = *std::move(inner); + } } + } + + // Helper to get the flatbuffers vector type. + template <typename Element, typename Offset> + struct FlatbuffersVectorType { + private: + static_assert(std::is_same_v<Offset, flatbuffers::uoffset_t> || + std::is_same_v<Offset, flatbuffers::uoffset64_t>, + "Offset must be uoffset_t or uoffset64_t."); + static_assert(std::is_arithmetic_v<Element> || + is_flatbuffers_enum_tag_v<Element> || + std::is_same_v<Element, std::string> || + std::is_same_v<Element, FlatbuffersTableTag> || + std::is_same_v<Element, FlatbuffersStructTag> || + std::is_same_v<Element, FlatbuffersUnionTag>, + "Unsupported vector element type."); + + static constexpr auto get_flatbuffers_type_pointer() { + if constexpr (std::is_integral_v<Element> || + std::is_floating_point_v<Element>) { + return static_cast<flatbuffers::Vector<Element, Offset>*>(nullptr); + } else if constexpr (is_flatbuffers_enum_tag_v<Element>) { + return static_cast< + flatbuffers::Vector<typename Element::type, Offset>*>(nullptr); + } else if constexpr (std::is_same_v<Element, std::string>) { + return static_cast<flatbuffers::Vector< + flatbuffers::Offset<flatbuffers::String>, Offset>*>(nullptr); + } else { + return static_cast<flatbuffers::Vector< + flatbuffers::Offset<flatbuffers::Table>, Offset>*>(nullptr); + } + } + + public: + using type = + std::remove_pointer_t<decltype(get_flatbuffers_type_pointer())>; }; + + template <typename Element, typename Domain, typename Offset> + void VisitVector(const reflection::Field* field, + value_type_t<Domain>& vector_corpus) const { + using FlatbuffersVector = + typename FlatbuffersVectorType<Element, Offset>::type; + if constexpr (std::is_same_v<Element, FlatbuffersStructTag> || + std::is_same_v<Element, FlatbuffersUnionTag>) { + // TODO: Add support for structs and unions. + return; + } else { + const FlatbuffersVector* vec; + if constexpr (std::is_same_v<Offset, flatbuffers::uoffset64_t>) { + vec = user_value->GetPointer64<const FlatbuffersVector*>( + field->offset()); + } else { + vec = + user_value->GetPointer<const FlatbuffersVector*>(field->offset()); + } + vector_corpus->reserve(vec->size()); + for (decltype(vec->size()) i = 0; i < vec->size(); ++i) { + if constexpr (std::is_same_v<Element, std::string>) { + vector_corpus->push_back(vec->Get(i)->str()); + } else { + vector_corpus->push_back(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; + flatbuffers::FlatBufferBuilder64& builder; + absl::flat_hash_map<typename corpus_type::key_type, + flatbuffers::uoffset64_t>& offsets; const typename corpus_type::mapped_type& corpus_value; template <typename T> @@ -484,25 +693,96 @@ 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; + flatbuffers::uoffset64_t offset; + if (field->offset64()) { + offset = builder + .CreateString<flatbuffers::Offset64>( + user_value->data(), user_value->size()) + .o; + } else { + 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 optional_corpus = corpus_value.GetAs< - std::variant<std::monostate, fuzztest::GenericDomainCorpusType>>(); - if (std::holds_alternative<fuzztest::GenericDomainCorpusType>( - optional_corpus)) { - auto inner_corpus = - std::get<fuzztest::GenericDomainCorpusType>(optional_corpus) - .GetAs<corpus_type>(); + auto optional_corpus = + corpus_value + .GetAs<std::variant<std::monostate, GenericDomainCorpusType>>(); + if (std::holds_alternative<GenericDomainCorpusType>(optional_corpus)) { + auto inner_corpus = std::get<GenericDomainCorpusType>(optional_corpus) + .GetAs<corpus_type>(); auto offset = inner_domain.BuildTable(inner_corpus, builder); offsets.insert({field->id(), offset}); } // Else if the variant is std::monostate the optional field is null and // there is no table to build. + } else if constexpr (is_any_flatbuffers_vector_tag_v<T>) { + VisitVector<typename T::value_type, flatbuffers_vector_tag_offset_t<T>>( + field, self.GetCachedDomain<T>(field)); + } + } + + private: + template <typename Element, typename Offset, + int&... ExplicitArgumentBarrier, typename Domain> + void VisitVector(const reflection::Field* field, + const Domain& domain) const { + if constexpr (std::is_integral_v<Element> || + std::is_floating_point_v<Element> || + is_flatbuffers_enum_tag_v<Element>) { + auto value = domain.GetValue(corpus_value); + if (!value) { + return; + } + if constexpr (std::is_same_v<Offset, flatbuffers::uoffset_t>) { + offsets.insert({field->id(), builder.CreateVector(*value).o}); + } else { + if constexpr (std::is_same_v<Element, bool>) { + // Workaround for missing overload for CreateVector64(const + // std::vector<T>&) + builder.StartVector<uint8_t, flatbuffers::Offset64>(value->size()); + for (auto i = value->size(); i > 0;) { + builder.PushElement(static_cast<uint8_t>(value->at(--i))); + } + auto offset = + builder.EndVector<flatbuffers::uoffset64_t, + flatbuffers::uoffset64_t>(value->size()); + offsets.insert({field->id(), offset}); + } else { + offsets.insert({field->id(), builder.CreateVector64(*value).o}); + } + } + } else if constexpr (std::is_same_v<Element, FlatbuffersTableTag>) { + FlatbuffersTableUntypedDomainImpl 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<std::monostate>(opt_corpus)) { + return; + } + auto container_corpus = std::get<GenericDomainCorpusType>(opt_corpus) + .GetAs<std::list<corpus_type>>(); + std::vector<flatbuffers::Offset<flatbuffers::Table>> vec_offsets; + for (auto& inner_corpus : container_corpus) { + auto offset = domain.BuildTable(inner_corpus, builder); + vec_offsets.push_back(offset); + } + offsets.insert({field->id(), builder.CreateVector(vec_offsets).o}); + } else if constexpr (std::is_same_v<Element, std::string>) { + auto value = domain.GetValue(corpus_value); + if (!value) { + return; + } + std::vector<flatbuffers::Offset<flatbuffers::String>> vec_offsets; + for (const auto& str : *value) { + auto offset = builder.CreateString(str); + vec_offsets.push_back(offset); + } + offsets.insert({field->id(), builder.CreateVector(vec_offsets).o}); } } }; @@ -511,9 +791,9 @@ // offsets for "out-of-line fields". See `BuildTable` for details. struct TableBuilderVisitor { const FlatbuffersTableUntypedDomainImpl& self; - flatbuffers::FlatBufferBuilder& builder; - const absl::flat_hash_map<typename corpus_type::key_type, - flatbuffers::uoffset_t>& offsets; + flatbuffers::FlatBufferBuilder64& builder; + absl::flat_hash_map<typename corpus_type::key_type, + flatbuffers::uoffset64_t>& offsets; const typename corpus_type::value_type::second_type& corpus_value; template <typename T> @@ -527,12 +807,19 @@ } // Store "inline field" value inline. builder.AddElement(field->offset(), v.value()); - } else if constexpr (std::is_same_v<T, std::string>) { + } else if constexpr (std::is_same_v<T, std::string> || + is_any_flatbuffers_vector_tag_v<T>) { // "Out-of-line field". Store just offset. if (auto it = offsets.find(field->id()); it != offsets.end()) { - builder.AddOffset( - field->offset(), - flatbuffers::Offset<flatbuffers::String>(it->second)); + if (field->offset64()) { + builder.AddOffset( + field->offset(), + flatbuffers::Offset64<flatbuffers::String>(it->second)); + } else { + 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. @@ -632,7 +919,6 @@ domain_implementor::RawSink out, domain_implementor::PrintMode mode) const { std::vector<typename corpus_type::key_type> field_ids; - field_ids.reserve(value.size()); for (const auto& [id, _] : value) { field_ids.push_back(id); } @@ -660,15 +946,58 @@ struct PrinterVisitor { const FlatbuffersTableUntypedDomainImpl& self; - const GenericDomainCorpusType& val; - domain_implementor::RawSink out; + 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.GetCachedDomain<T>(field); - absl::Format(out, "%s: ", field->name()->str()); - domain_implementor::PrintValue(domain, val, out, mode); + absl::Format(sink, "%s: ", field->name()->str()); + if constexpr (is_flatbuffers_container_of_v<T, uint8_t> || + is_flatbuffers_container_of_v< + T, FlatbuffersEnumTag<uint8_t>>) { + // Handle the case where the field is a vector<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 (is_flatbuffers_container_of_v<T, 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 { // container of 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); + } + absl::Format(sink, ")"); + } else { + domain.GetPrinter().PrintCorpusValue(field_corpus, sink, mode); + } } }; }; @@ -695,9 +1024,8 @@ } else if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) { // TODO: support unions. return placeholder; - } else if constexpr (std::is_same_v<T, FlatbuffersVectorTag>) { - // TODO: support vectors. - return placeholder; + } else if constexpr (is_any_flatbuffers_vector_tag_v<T>) { + return VectorOf(GetDefaultDomain<typename T::value_type>(schema, field)); } else { return Arbitrary<T>(); } @@ -717,7 +1045,7 @@ // - The serialized buffer of the table. template <typename T> class FlatbuffersTableDomainImpl - : public fuzztest::domain_implementor::DomainBase< + : public domain_implementor::DomainBase< /*Derived=*/FlatbuffersTableDomainImpl<T>, /*ValueType=*/const T*, /*CorpusType=*/FlatbuffersTableDomainCorpusType> { @@ -762,7 +1090,7 @@ // Converts corpus value into the exact flatbuffer. value_type GetValue(const corpus_type& value) const { - flatbuffers::FlatBufferBuilder builder; + flatbuffers::FlatBufferBuilder64 builder; const uint32_t offset = inner_->BuildTable(value.untyped_corpus, builder); builder.Finish(flatbuffers::Offset<flatbuffers::Table>(offset)); value.buffer =
diff --git a/fuzztest/internal/test_flatbuffers.fbs b/fuzztest/internal/test_flatbuffers.fbs index 02177c0..4207880 100644 --- a/fuzztest/internal/test_flatbuffers.fbs +++ b/fuzztest/internal/test_flatbuffers.fbs
@@ -1,3 +1,17 @@ +// 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. + namespace fuzztest.internal; enum ByteEnum: byte { @@ -78,6 +92,27 @@ eu32: UIntEnum; eu64: ULongEnum; t: BoolTable; + 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]; } table OptionalTable { @@ -102,24 +137,6 @@ eu32: UIntEnum = null; eu64: ULongEnum = null; t: BoolTable; -} - -table RequiredTable { - str: string (required); - t: BoolTable (required); -} - -table RecursiveTable { - t: NestedRecursiveTable; -} - -table NestedRecursiveTable { - t: RecursiveTable; -} - -table UnsupportedTypesTable { - u: Union; - s: BoolStruct; v_b: [bool]; v_i8: [byte]; v_i16: [short]; @@ -141,6 +158,45 @@ v_eu32: [UIntEnum]; v_eu64: [ULongEnum]; v_t: [BoolTable]; +} + +table RequiredTable { + str: string (required); + t: BoolTable (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); +} + +table RecursiveTable { + t: NestedRecursiveTable; +} + +table NestedRecursiveTable { + t: RecursiveTable; +} + +table UnsupportedTypesTable { + u: Union; + s: BoolStruct; v_u: [Union]; v_s: [BoolStruct]; }
diff --git a/fuzztest/internal/test_flatbuffers_64bits.fbs b/fuzztest/internal/test_flatbuffers_64bits.fbs new file mode 100644 index 0000000..469e79a --- /dev/null +++ b/fuzztest/internal/test_flatbuffers_64bits.fbs
@@ -0,0 +1,22 @@ +// 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. + +namespace fuzztest.internal; + +table DefaultTable64 { + str: string (offset64); + v_u8: [ubyte] (vector64); +} + +root_type DefaultTable64;