Add struct support to the flatbuffers domain PiperOrigin-RevId: 743170054
diff --git a/domain_tests/BUILD b/domain_tests/BUILD index 0dfd652..e2b7215 100644 --- a/domain_tests/BUILD +++ b/domain_tests/BUILD
@@ -46,11 +46,13 @@ deps = [ ":domain_testing", "@abseil-cpp//absl/container:flat_hash_map", + "@abseil-cpp//absl/log:check", "@abseil-cpp//absl/random", "@abseil-cpp//absl/status", "@com_google_fuzztest//fuzztest:domain", "@com_google_fuzztest//fuzztest:flatbuffers", "@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",
diff --git a/domain_tests/arbitrary_domains_flatbuffers_test.cc b/domain_tests/arbitrary_domains_flatbuffers_test.cc index d96c24d..59bb0c4 100644 --- a/domain_tests/arbitrary_domains_flatbuffers_test.cc +++ b/domain_tests/arbitrary_domains_flatbuffers_test.cc
@@ -19,11 +19,14 @@ #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 "absl/status/status.h" #include "flatbuffers/base.h" @@ -32,23 +35,74 @@ #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/internal/meta.h" +#include "./fuzztest/internal/serialization.h" #include "./fuzztest/internal/test_flatbuffers_64bits_generated.h" #include "./fuzztest/internal/test_flatbuffers_generated.h" namespace fuzztest { +namespace internal { +template <typename Sink> +void AbslStringify(Sink& sink, const Union& e) { + absl::Format(&sink, "%s", EnumNameUnion(e)); +} +template <typename Sink> +void AbslStringify(Sink& sink, const ByteEnum& e) { + absl::Format(&sink, "%s", EnumNameByteEnum(e)); +} +template <typename Sink> +void AbslStringify(Sink& sink, const ShortEnum& e) { + absl::Format(&sink, "%s", EnumNameShortEnum(e)); +} +template <typename Sink> +void AbslStringify(Sink& sink, const IntEnum& e) { + absl::Format(&sink, "%s", EnumNameIntEnum(e)); +} +template <typename Sink> +void AbslStringify(Sink& sink, const LongEnum& e) { + absl::Format(&sink, "%s", EnumNameLongEnum(e)); +} +template <typename Sink> +void AbslStringify(Sink& sink, const UByteEnum& e) { + absl::Format(&sink, "%s", EnumNameUByteEnum(e)); +} +template <typename Sink> +void AbslStringify(Sink& sink, const UShortEnum& e) { + absl::Format(&sink, "%s", EnumNameUShortEnum(e)); +} +template <typename Sink> +void AbslStringify(Sink& sink, const UIntEnum& e) { + absl::Format(&sink, "%s", EnumNameUIntEnum(e)); +} +template <typename Sink> +void AbslStringify(Sink& sink, const ULongEnum& e) { + absl::Format(&sink, "%s", EnumNameULongEnum(e)); +} +} // namespace internal namespace { +using ::fuzztest::internal::BoolStruct; using ::fuzztest::internal::BoolTable; +using ::fuzztest::internal::ByteEnum; +using ::fuzztest::internal::DefaultStruct; 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::UnsupportedTypesTable; +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; @@ -73,6 +127,40 @@ } 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) { + const bool eq_b = lhs.b() == rhs.b(); + const bool eq_i8 = lhs.i8() == rhs.i8(); + const bool eq_i16 = lhs.i16() == rhs.i16(); + const bool eq_i32 = lhs.i32() == rhs.i32(); + const bool eq_i64 = lhs.i64() == rhs.i64(); + const bool eq_u8 = lhs.u8() == rhs.u8(); + const bool eq_u16 = lhs.u16() == rhs.u16(); + const bool eq_u32 = lhs.u32() == rhs.u32(); + const bool eq_u64 = lhs.u64() == rhs.u64(); + const bool eq_f = lhs.f() == rhs.f(); + const bool eq_d = lhs.d() == rhs.d(); + const bool eq_ei8 = lhs.ei8() == rhs.ei8(); + const bool eq_ei16 = lhs.ei16() == rhs.ei16(); + const bool eq_ei32 = lhs.ei32() == rhs.ei32(); + const bool eq_ei64 = lhs.ei64() == rhs.ei64(); + const bool eq_eu8 = lhs.eu8() == rhs.eu8(); + const bool eq_eu16 = lhs.eu16() == rhs.eu16(); + const bool eq_eu32 = lhs.eu32() == rhs.eu32(); + const bool eq_eu64 = lhs.eu64() == rhs.eu64(); + const bool eq_s = Eq(lhs.s(), rhs.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; +} + +template <> inline bool Eq<flatbuffers::String>(const flatbuffers::String& lhs, const flatbuffers::String& rhs) { if (lhs.size() != rhs.size()) return false; @@ -84,6 +172,78 @@ return lhs.b() == rhs.b(); } +template <typename T> +inline bool Eq(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 Eq(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 Eq(*lhs, *rhs); +} + +template <> +inline bool +Eq<std::pair<const flatbuffers::Vector<uint8_t>*, + const flatbuffers::Vector<::flatbuffers::Offset<void>>*>>( + const std::pair<const flatbuffers::Vector<uint8_t>*, + const flatbuffers::Vector<::flatbuffers::Offset<void>>*>& + lhs, + const std::pair<const flatbuffers::Vector<uint8_t>*, + const flatbuffers::Vector<::flatbuffers::Offset<void>>*>& + rhs) { + if (!Eq(lhs.first, rhs.first)) return false; + if (lhs.second == nullptr && rhs.second == nullptr) return true; + if (lhs.second == nullptr || rhs.second == nullptr) return false; + if (lhs.first->size() != lhs.second->size()) return false; + if (lhs.second->size() != rhs.second->size()) return false; + + for (int i = 0; i < lhs.second->size(); ++i) { + if (!Eq(std::pair(lhs.first->Get(i), lhs.second->Get(i)), + std::pair(rhs.first->Get(i), rhs.second->Get(i)))) { + return false; + } + } + return true; +} + template <> inline bool Eq<DefaultTable>(const DefaultTable& lhs, const DefaultTable& rhs) { const bool eq_b = lhs.b() == rhs.b(); @@ -107,14 +267,111 @@ 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_u = Eq(std::pair(static_cast<uint8_t>(lhs.u_type()), lhs.u()), + std::pair(static_cast<uint8_t>(rhs.u_type()), rhs.u())); + const bool eq_s = Eq(lhs.s(), rhs.s()); + const bool eq_v_b = Eq(lhs.v_b(), rhs.v_b()); + const bool eq_v_i8 = Eq(lhs.v_i8(), rhs.v_i8()); + const bool eq_v_i16 = Eq(lhs.v_i16(), rhs.v_i16()); + const bool eq_v_i32 = Eq(lhs.v_i32(), rhs.v_i32()); + const bool eq_v_i64 = Eq(lhs.v_i64(), rhs.v_i64()); + const bool eq_v_u8 = Eq(lhs.v_u8(), rhs.v_u8()); + const bool eq_v_u16 = Eq(lhs.v_u16(), rhs.v_u16()); + const bool eq_v_u32 = Eq(lhs.v_u32(), rhs.v_u32()); + const bool eq_v_u64 = Eq(lhs.v_u64(), rhs.v_u64()); + const bool eq_v_f = Eq(lhs.v_f(), rhs.v_f()); + const bool eq_v_d = Eq(lhs.v_d(), rhs.v_d()); + const bool eq_v_str = Eq(lhs.v_str(), rhs.v_str()); + const bool eq_v_ei8 = Eq(lhs.v_ei8(), rhs.v_ei8()); + const bool eq_v_ei16 = Eq(lhs.v_ei16(), rhs.v_ei16()); + const bool eq_v_ei32 = Eq(lhs.v_ei32(), rhs.v_ei32()); + const bool eq_v_ei64 = Eq(lhs.v_ei64(), rhs.v_ei64()); + const bool eq_v_eu8 = Eq(lhs.v_eu8(), rhs.v_eu8()); + const bool eq_v_eu16 = Eq(lhs.v_eu16(), rhs.v_eu16()); + const bool eq_v_eu32 = Eq(lhs.v_eu32(), rhs.v_eu32()); + const bool eq_v_eu64 = Eq(lhs.v_eu64(), rhs.v_eu64()); + const bool eq_v_t = Eq(lhs.v_t(), rhs.v_t()); + const bool eq_v_u_type = Eq(lhs.v_u_type(), rhs.v_u_type()); + const bool eq_v_u = Eq(std::make_pair(lhs.v_u_type(), lhs.v_u()), + std::make_pair(rhs.v_u_type(), rhs.v_u())); + const bool eq_v_s = Eq(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_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} // 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, @@ -137,7 +394,34 @@ /*eu16=*/internal::UShortEnum_Second, /*eu32=*/internal::UIntEnum_Second, /*eu64=*/internal::ULongEnum_Second, - /*t=*/bool_table_offset); + /*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()); } @@ -263,6 +547,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); @@ -274,6 +559,159 @@ 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); + ASSERT_THAT(new_table->u(), NotNull()); + 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); + 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); + ASSERT_THAT(new_table->v_u_type(), NotNull()); + ASSERT_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()); + ASSERT_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); } TEST(FlatbuffersTableDomainImplTest, InitGeneratesSeeds) { @@ -293,12 +731,22 @@ 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}, + {"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*>(); @@ -334,6 +782,36 @@ 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"] |= !Eq(mut->v_b(), init->v_b()); + mutated_fields["v_i8"] |= !Eq(mut->v_i8(), init->v_i8()); + mutated_fields["v_i16"] |= !Eq(mut->v_i16(), init->v_i16()); + mutated_fields["v_i32"] |= !Eq(mut->v_i32(), init->v_i32()); + mutated_fields["v_i64"] |= !Eq(mut->v_i64(), init->v_i64()); + mutated_fields["v_u8"] |= !Eq(mut->v_u8(), init->v_u8()); + mutated_fields["v_u16"] |= !Eq(mut->v_u16(), init->v_u16()); + mutated_fields["v_u32"] |= !Eq(mut->v_u32(), init->v_u32()); + mutated_fields["v_u64"] |= !Eq(mut->v_u64(), init->v_u64()); + mutated_fields["v_f"] |= !Eq(mut->v_f(), init->v_f()); + mutated_fields["v_d"] |= !Eq(mut->v_d(), init->v_d()); + mutated_fields["v_str"] |= !Eq(mut->v_str(), init->v_str()); + mutated_fields["v_ei8"] |= !Eq(mut->v_ei8(), init->v_ei8()); + mutated_fields["v_ei16"] |= !Eq(mut->v_ei16(), init->v_ei16()); + mutated_fields["v_ei32"] |= !Eq(mut->v_ei32(), init->v_ei32()); + mutated_fields["v_ei64"] |= !Eq(mut->v_ei64(), init->v_ei64()); + mutated_fields["v_eu8"] |= !Eq(mut->v_eu8(), init->v_eu8()); + mutated_fields["v_eu16"] |= !Eq(mut->v_eu16(), init->v_eu16()); + mutated_fields["v_eu32"] |= !Eq(mut->v_eu32(), init->v_eu32()); + mutated_fields["v_eu64"] |= !Eq(mut->v_eu64(), init->v_eu64()); + mutated_fields["v_t"] |= !Eq(mut->v_t(), init->v_t()); + mutated_fields["v_u_type"] |= !Eq(mut->v_u_type(), init->v_u_type()); + mutated_fields["v_u"] |= !Eq(std::make_pair(mut->v_u_type(), mut->v_u()), + std::make_pair(init->v_u_type(), init->v_u())); + mutated_fields["v_s"] |= !Eq(mut->v_s(), init->v_s()); if (std::all_of(mutated_fields.begin(), mutated_fields.end(), [](const auto& p) { return p.second; })) { @@ -347,6 +825,32 @@ 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 @@ -369,7 +873,34 @@ internal::UShortEnum_Second, // eu16 internal::UIntEnum_Second, // eu32 internal::ULongEnum_Second, // eu64 - bool_table_offset // t + 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()); @@ -379,12 +910,22 @@ 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}, + {"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}, }; // Optional fields are mutated to null with probability 1/100. @@ -415,6 +956,33 @@ 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; })) { @@ -453,83 +1021,164 @@ 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("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}})"), // 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}}})") // v_s + )); } -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}}; +TEST(FlatbuffersTableDomainImplTest, MutateSelectedField) { + flatbuffers::FlatBufferBuilder fbb; + auto table = CreateDefaultTable(fbb); + auto domain = Arbitrary<const DefaultTable*>(); + absl::BitGen prng; - auto domain = Arbitrary<const UnsupportedTypesTable*>(); - - absl::BitGen bitgen; - for (size_t i = 0; - i < IterationsToHitAll(null_fields.size(), 1.0 / null_fields.size()); - ++i) { - Value val(domain, bitgen); - val.Mutate(domain, bitgen, {}, false); - const auto& mut = val.user_value; - - 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; - - if (std::any_of(null_fields.begin(), null_fields.end(), - [](const auto& p) { return !p.second; })) { - break; + { + // Mutate scalar field (b). + auto corpus = domain.FromValue(table); + domain.MutateSelectedField(*corpus, prng, {}, false, 1); + EXPECT_NE(domain.GetValue(*corpus)->b(), table->b()); + } + { + // Mutate nested table field (t.b). + auto corpus = domain.FromValue(table); + domain.MutateSelectedField(*corpus, prng, {}, false, 22); + EXPECT_NE(domain.GetValue(*corpus)->t()->b(), table->t()->b()); + } + { + // Mutate union type field (u_type). + auto corpus = domain.FromValue(table); + domain.MutateSelectedField(*corpus, prng, {}, false, 24); + EXPECT_NE(domain.GetValue(*corpus)->u_type(), table->u_type()); + } + { + // Mutate union field (u.*) content. + auto corpus = domain.FromValue(table); + domain.MutateSelectedField(*corpus, prng, {}, false, 25); + switch (domain.GetValue(*corpus)->u_type()) { + case internal::Union_BoolTable: + EXPECT_NE(domain.GetValue(*corpus)->u_as_BoolTable()->b(), + table->u_as_BoolTable()->b()); + break; + case internal::Union_StringTable: + EXPECT_NE( + domain.GetValue(*corpus)->u_as_StringTable()->str()->string_view(), + table->u_as_StringTable()->str()->string_view()); + break; + default: + FAIL() << "Unexpected union type: " + << domain.GetValue(*corpus)->u_type(); } } - - EXPECT_THAT(null_fields, Each(Pair(_, true))); + { + // Mutate vector of tables field (v_t). + auto corpus = domain.FromValue(table); + domain.MutateSelectedField(*corpus, prng, {}, false, 68); + auto user_value = domain.GetValue(*corpus); + EXPECT_FALSE(Eq(user_value->v_t(), table->v_t())); + } + { + // Mutate vector of tables field (v_t[0].b). + auto corpus = domain.FromValue(table); + domain.MutateSelectedField(*corpus, prng, {}, false, 69); + EXPECT_NE(domain.GetValue(*corpus)->v_t()->Get(0)->b(), + table->v_t()->Get(0)->b()); + } + { + // Mutate vector of unions field type (v_u_type[0]). + auto corpus = domain.FromValue(table); + domain.MutateSelectedField(*corpus, prng, {}, false, 71); + EXPECT_NE(domain.GetValue(*corpus)->v_u_type()->Get(0), + table->v_u_type()->Get(0)); + } + { + // Mutate vector of unions field (v_u[0].*). + auto corpus = domain.FromValue(table); + domain.MutateSelectedField(*corpus, prng, {}, false, 72); + EXPECT_EQ(domain.GetValue(*corpus)->v_u_type()->size(), + table->v_u_type()->size()); + EXPECT_EQ(domain.GetValue(*corpus)->v_u_type()->Get(0), + table->v_u_type()->Get(0)); + if (!domain.GetValue(*corpus)->v_u_type()->empty()) { + switch (domain.GetValue(*corpus)->v_u_type()->Get(0)) { + case internal::Union_BoolTable: + EXPECT_NE( + static_cast<const internal::BoolTable*>( + domain.GetValue(*corpus)->v_u()->Get(0)) + ->b(), + static_cast<const internal::BoolTable*>(table->v_u()->Get(0)) + ->b()); + break; + case internal::Union_StringTable: + EXPECT_NE( + static_cast<const internal::StringTable*>( + domain.GetValue(*corpus)->v_u()->Get(0)) + ->str() + ->string_view(), + static_cast<const internal::StringTable*>(table->v_u()->Get(0)) + ->str() + ->string_view()); + break; + default: + FAIL() << "Unexpected union type: " + << domain.GetValue(*corpus)->v_u_type()->Get(0); + } + } + } } TEST(FlatbuffersTableDomainImplTest, MutateAlwaysChangesValues) { @@ -540,23 +1189,28 @@ schema->objects()->LookupByKey(DefaultTable::GetFullyQualifiedName()); absl::BitGen bitgen; - size_t iterations = IterationsToHitAll(object->fields()->size(), - 1.0 / object->fields()->size()); + const uint32_t field_count = object->fields()->size(); + int iterations = IterationsToHitAll(field_count, 1.0 / field_count); typename decltype(domain)::corpus_type corpus = domain.Init(bitgen); - for (size_t i = 0; i < iterations; ++i) { + for (int i = 0; i < iterations; ++i) { auto mutated_corpus = corpus; domain.Mutate(mutated_corpus, bitgen, {}, false); - EXPECT_FALSE(Eq(domain.GetValue(mutated_corpus), domain.GetValue(corpus))); + if (Eq(domain.GetValue(mutated_corpus), domain.GetValue(corpus))) { + auto printer = domain.GetPrinter(); + std::string corpus_str; + printer.PrintCorpusValue(corpus, &corpus_str, + domain_implementor::PrintMode::kHumanReadable); + std::string mutated_corpus_str; + printer.PrintCorpusValue(mutated_corpus, &mutated_corpus_str, + domain_implementor::PrintMode::kHumanReadable); + FAIL() << "Mutated corpus is equal to the original corpus." + << "\nOriginal: " << corpus_str + << "\nMutated: " << mutated_corpus_str; + } corpus = mutated_corpus; } } -TEST(FlatbuffersTableDomainImplTest, UnsupportedFieldsCountIsZero) { - auto domain = Arbitrary<const UnsupportedTypesTable*>(); - auto corpus = domain.Init(absl::BitGen()); - EXPECT_EQ(domain.CountNumberOfFields(corpus), 0); -} - TEST(FlatbuffersTableDomainImplTest, CountNumberOfFieldsWithNull) { flatbuffers::FlatBufferBuilder fbb; auto table_offset = internal::CreateOptionalTableDirect(fbb); @@ -566,7 +1220,72 @@ 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()), 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()); + } } TEST(FlatbuffersTableDomainImplTest, RecursiveTable) { @@ -597,7 +1316,10 @@ TEST(FlatbuffersTableDomainImplTest, DefaultTable64ValueRoundTrip) { flatbuffers::FlatBufferBuilder64 fbb; auto str_offset = fbb.CreateString<flatbuffers::Offset64>("foo bar baz"); - auto table_offset = internal::CreateDefaultTable64(fbb, str_offset); + 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()); @@ -616,6 +1338,11 @@ 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
diff --git a/fuzztest/internal/domains/BUILD b/fuzztest/internal/domains/BUILD index 545fef3..30790fc 100644 --- a/fuzztest/internal/domains/BUILD +++ b/fuzztest/internal/domains/BUILD
@@ -187,9 +187,9 @@ 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:btree", "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/container:flat_hash_set", "@abseil-cpp//absl/random:bit_gen_ref", @@ -204,6 +204,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/CMakeLists.txt b/fuzztest/internal/domains/CMakeLists.txt index e9702ff..d8c3de2 100644 --- a/fuzztest/internal/domains/CMakeLists.txt +++ b/fuzztest/internal/domains/CMakeLists.txt
@@ -173,6 +173,7 @@ fuzztest::core_domains_impl absl::core_headers absl::no_destructor + absl::btree absl::flat_hash_map absl::flat_hash_set absl::random_random
diff --git a/fuzztest/internal/domains/flatbuffers_domain_impl.cc b/fuzztest/internal/domains/flatbuffers_domain_impl.cc index 9031344..866c4bd 100644 --- a/fuzztest/internal/domains/flatbuffers_domain_impl.cc +++ b/fuzztest/internal/domains/flatbuffers_domain_impl.cc
@@ -11,170 +11,470 @@ // 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 "absl/synchronization/mutex.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 "./common/logging.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 { -FlatbuffersTableUntypedDomainImpl::FlatbuffersTableUntypedDomainImpl( - const reflection::Schema* absl_nonnull schema, - const reflection::Object* absl_nonnull table_object) - : schema_(schema), table_object_(table_object) {} +// 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}); +} -FlatbuffersTableUntypedDomainImpl::FlatbuffersTableUntypedDomainImpl( - const FlatbuffersTableUntypedDomainImpl& other) - : DomainBase(other), - schema_(other.schema_), - table_object_(other.table_object_) { +FlatbuffersUnionDomainImpl::FlatbuffersUnionDomainImpl( + const reflection::Schema* schema, const reflection::Enum* union_def) + : schema_(schema), union_def_(union_def), type_domain_(union_def) { + type_domain_.WithExcludedValues({0 /* NONE */}); +} + +FlatbuffersUnionDomainImpl::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_); - absl::MutexLock l_this(mutex_); domains_ = other.domains_; } -FlatbuffersTableUntypedDomainImpl& FlatbuffersTableUntypedDomainImpl::operator=( - const FlatbuffersTableUntypedDomainImpl& other) { - DomainBase::operator=(other); - schema_ = other.schema_; - table_object_ = other.table_object_; +FlatbuffersUnionDomainImpl::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_); - absl::MutexLock l_this(mutex_); - domains_ = other.domains_; - return *this; -} - -FlatbuffersTableUntypedDomainImpl::FlatbuffersTableUntypedDomainImpl( - FlatbuffersTableUntypedDomainImpl&& other) - : schema_(other.schema_), table_object_(other.table_object_) { - absl::MutexLock l_other(other.mutex_); - absl::MutexLock l_this(mutex_); domains_ = std::move(other.domains_); - DomainBase::operator=(std::move(other)); } -FlatbuffersTableUntypedDomainImpl& FlatbuffersTableUntypedDomainImpl::operator=( - FlatbuffersTableUntypedDomainImpl&& other) { - schema_ = other.schema_; - table_object_ = other.table_object_; - absl::MutexLock l_other(other.mutex_); - absl::MutexLock l_this(mutex_); - domains_ = std::move(other.domains_); - DomainBase::operator=(std::move(other)); - return *this; +// 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}); } -FlatbuffersTableUntypedDomainImpl::corpus_type -FlatbuffersTableUntypedDomainImpl::Init(absl::BitGenRef prng) { +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; - for (const auto* field : *table_object_->fields()) { - VisitFlatbufferField(schema_, field, InitializeVisitor{*this, prng, val}); + + val.type = type_domain_.Init(prng); + auto type_value = type_domain_.GetValue(val.type); + + auto type_enumval = union_def_->values()->LookupByKey(type_value); + if (type_enumval == nullptr) { + 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 FlatbuffersTableUntypedDomainImpl::Mutate( - corpus_type& val, absl::BitGenRef prng, +void FlatbuffersUnionDomainImpl::Mutate( + corpus_type& corpus_value, absl::BitGenRef prng, const domain_implementor::MutationMetadata& metadata, bool only_shrink) { - uint64_t field_count = 0; - for (const auto* field : *table_object_->fields()) { - VisitFlatbufferField(schema_, field, - CountNumberOfMutableFieldsVisitor{*this, field_count, - val, only_shrink}); - } - if (field_count == 0) return; - auto selected_field_index = absl::Uniform(prng, 1ul, field_count + 1); + auto type_value = type_domain_.GetValue(corpus_value.type); - MutateSelectedField(val, prng, metadata, only_shrink, selected_field_index); + // Mutate the type with probability 1%. + if (absl::Bernoulli(prng, 0.01)) { + // Mutate the type. + type_domain_.Mutate(corpus_value.type, prng, metadata, only_shrink); + type_value = type_domain_.GetValue(corpus_value.type); + + // If the union is set after type mutation, init the value corpus value. + 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()) { + corpus_value.value = + GetCachedDomain<FlatbuffersStructTag>(*type_enumval).Init(prng); + } else { + corpus_value.value = + GetCachedDomain<FlatbuffersTableTag>(*type_enumval).Init(prng); + } + return; + } + + // Mutate the value if the union is set. + 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.Mutate(corpus_value.value, prng, metadata, only_shrink); + } else { + GetCachedDomain<FlatbuffersTableTag>(*type_enumval) + .Mutate(corpus_value.value, prng, metadata, only_shrink); + } } -uint64_t FlatbuffersTableUntypedDomainImpl::CountNumberOfFields( - corpus_type& val) { +uint64_t FlatbuffersUnionDomainImpl::CountNumberOfFields( + corpus_type& corpus_value) { uint64_t field_count = 0; - for (const auto* field : *table_object_->fields()) { - VisitFlatbufferField( - schema_, field, - CountNumberOfMutableFieldsVisitor{*this, field_count, val}); + + // If the union has only one type (besides NONE), the type is not counted + // as mutable field. + if (union_def_->values()->size() <= 2) { + return field_count; + } + + // The first field is the union type. + ++field_count; + + auto type_value = type_domain_.GetValue(corpus_value.type); + auto type_enumval = union_def_->values()->LookupByKey(type_value); + if (type_enumval == nullptr) { + return field_count; + } + const reflection::Object* object = + schema_->objects()->Get(type_enumval->union_type()->index()); + if (object->is_struct()) { + auto domain = GetCachedDomain<FlatbuffersStructTag>(*type_enumval); + field_count += domain.CountNumberOfFields(corpus_value.value); + } else { + auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval); + field_count += domain.CountNumberOfFields(corpus_value.value); } return field_count; } -uint64_t FlatbuffersTableUntypedDomainImpl::MutateSelectedField( - corpus_type& val, absl::BitGenRef prng, +uint64_t FlatbuffersUnionDomainImpl::MutateSelectedField( + corpus_type& corpus_value, absl::BitGenRef prng, const domain_implementor::MutationMetadata& metadata, bool only_shrink, uint64_t selected_field_index) { - uint64_t field_counter = 0; - uint64_t fields_count = CountNumberOfFields(val); - if (fields_count < selected_field_index) { - return fields_count; + uint64_t field_count = 0; + + // If the union has only one type (besides NONE), the type is not counted + // as mutable field. + if (union_def_->values()->size() <= 2) { + return field_count; } - for (const auto* field : *table_object_->fields()) { - if (!IsSupportedField(field)) { - if (only_shrink && !val.contains(field->id())) continue; - } + // The first field is the union type. + ++field_count; + if (selected_field_index == field_count) { + type_domain_.Mutate(corpus_value.type, prng, metadata, only_shrink); + auto type_value = type_domain_.GetValue(corpus_value.type); + auto type_enumval = union_def_->values()->LookupByKey(type_value); + if (type_enumval == nullptr) return selected_field_index; - ++field_counter; - if (field_counter == selected_field_index) { - VisitFlatbufferField( - schema_, field, - MutateVisitor{*this, prng, metadata, only_shrink, val}); - return field_counter; + const reflection::Object* object = + schema_->objects()->Get(type_enumval->union_type()->index()); + if (object->is_struct()) { + corpus_value.value = + GetCachedDomain<FlatbuffersStructTag>(*type_enumval).Init(prng); + } else { + corpus_value.value = + GetCachedDomain<FlatbuffersTableTag>(*type_enumval).Init(prng); } - - if (field->type()->base_type() == reflection::BaseType::Obj) { - auto sub_object = schema_->objects()->Get(field->type()->index()); - if (!sub_object->is_struct()) { - field_counter += - GetCachedDomain<FlatbuffersTableTag>(field).MutateSelectedField( - val[field->id()], prng, metadata, only_shrink, - selected_field_index - field_counter); - } - // TODO: Add support for structs. - } - - if (field_counter >= selected_field_index) { - return field_counter; - } + return field_count; } - return field_counter; + + auto type_value = type_domain_.GetValue(corpus_value.type); + + auto type_enumval = union_def_->values()->LookupByKey(type_value); + if (type_enumval == nullptr) { + return 0; + } + const reflection::Object* object = + schema_->objects()->Get(type_enumval->union_type()->index()); + if (object->is_struct()) { + auto domain = GetCachedDomain<FlatbuffersStructTag>(*type_enumval); + field_count += domain.MutateSelectedField( + corpus_value.value, prng, metadata, only_shrink, + selected_field_index - field_count); + } else { + auto domain = GetCachedDomain<FlatbuffersTableTag>(*type_enumval); + field_count += domain.MutateSelectedField( + corpus_value.value, prng, metadata, only_shrink, + selected_field_index - field_count); + } + return field_count; } -absl::Status FlatbuffersTableUntypedDomainImpl::ValidateCorpusValue( +absl::Status FlatbuffersUnionDomainImpl::ValidateCorpusValue( const corpus_type& corpus_value) const { - for (const auto* field : *table_object_->fields()) { - absl::Status result; - GenericDomainCorpusType field_corpus; - if (auto it = corpus_value.find(field->id()); it != corpus_value.end()) { - field_corpus = it->second; - } - if (!field_corpus.has_value()) continue; - VisitFlatbufferField(schema_, field, - ValidateVisitor{*this, field_corpus, result}); - if (!result.ok()) return result; + // 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); + + auto type_enumval = union_def_->values()->LookupByKey(type_value); + if (type_enumval == nullptr) { + return absl::InvalidArgumentError( + absl::StrCat("Invalid union type: ", type_value)); } - return absl::OkStatus(); + + // 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_corpus = type_domain_.FromValue(value.type); + if (type_corpus.has_value()) { + out->type = *type_corpus; + } + 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()) { + return std::nullopt; + } + if (auto status = type_domain_.ValidateCorpusValue(*type_corpus); + !status.ok()) { + FUZZTEST_LOG(ERROR) << "Failed to validate type corpus: " + << status.message(); + 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); + + auto type_enumval = union_def_->values()->LookupByKey(type_value); + if (type_enumval == nullptr) { + 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)); + + // 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::FlatBufferBuilder64& 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 || !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()); + + 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 val; + for (const auto& [_, field] : fields_by_id_) { + VisitFlatbufferField(schema_, field, FromValueVisitor{*this, value, val}); + } + return val; +} + +std::optional<flatbuffers::uoffset_t> +FlatbuffersStructUntypedDomainImpl::BuildValue( + const corpus_type& value, flatbuffers::FlatBufferBuilder64& 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] : fields_by_id_) { + VisitFlatbufferField(schema_, field, BuildValueVisitor{*this, value, buf}); + } } std::optional<FlatbuffersTableUntypedDomainImpl::corpus_type> @@ -183,98 +483,12 @@ return std::nullopt; } corpus_type ret; - for (const auto* field : *table_object_->fields()) { + for (const auto& [_, field] : fields_by_id_) { VisitFlatbufferField(schema_, field, FromValueVisitor{*this, value, ret}); } return ret; } -std::optional<FlatbuffersTableUntypedDomainImpl::corpus_type> -FlatbuffersTableUntypedDomainImpl::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.has_value() || 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); - if (field == nullptr) { - return std::nullopt; - } - - // 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; -} - -IRObject FlatbuffersTableUntypedDomainImpl::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; -} - -bool FlatbuffersTableUntypedDomainImpl::IsSupportedField( - const reflection::Field* absl_nonnull field) const { - auto base_type = field->type()->base_type(); - if (base_type == reflection::BaseType::UType) return false; - if (flatbuffers::IsScalar(base_type)) return true; - if (base_type == reflection::BaseType::String) return true; - if (base_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::FlatBufferBuilder64& builder) const { // Add all the fields to the builder.
diff --git a/fuzztest/internal/domains/flatbuffers_domain_impl.h b/fuzztest/internal/domains/flatbuffers_domain_impl.h index 328b2e7..0d5e4b0 100644 --- a/fuzztest/internal/domains/flatbuffers_domain_impl.h +++ b/fuzztest/internal/domains/flatbuffers_domain_impl.h
@@ -16,9 +16,11 @@ #define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_FLATBUFFERS_DOMAIN_IMPL_H_ #include <algorithm> +#include <cstddef> #include <cstdint> #include <initializer_list> #include <limits> +#include <list> #include <optional> #include <string> #include <type_traits> @@ -26,9 +28,9 @@ #include <variant> #include <vector> -#include "absl/algorithm/container.h" #include "absl/base/nullability.h" #include "absl/base/thread_annotations.h" +#include "absl/container/btree_map.h" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/random/bit_gen_ref.h" @@ -39,20 +41,25 @@ #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/struct.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 { @@ -69,120 +76,221 @@ template <typename T> struct is_flatbuffers_enum_tag : std::false_type {}; -template <typename Underlying> -struct is_flatbuffers_enum_tag<FlatbuffersEnumTag<Underlying>> +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; + +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; + +template <typename T> +using IdentityWrapper = T; // Dynamic to static dispatch visitor pattern. -template <typename Visitor> -auto VisitFlatbufferField(const reflection::Schema* absl_nonnull schema, +template <template <typename> typename Wrapper = IdentityWrapper, + bool in_container = false, typename Visitor> +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); + std::forward<Visitor>(visitor).template Visit<Wrapper<bool>>(field); break; case reflection::BaseType::Byte: - if (field_index >= 0) { - visitor.template Visit<FlatbuffersEnumTag<int8_t>>(field); + if (is_enum) { + std::forward<Visitor>(visitor) + .template Visit<Wrapper<FlatbuffersEnumTag<int8_t>>>(field); } else { - visitor.template Visit<int8_t>(field); + std::forward<Visitor>(visitor).template Visit<Wrapper<int8_t>>(field); } break; case reflection::BaseType::Short: - if (field_index >= 0) { - visitor.template Visit<FlatbuffersEnumTag<int16_t>>(field); + if (is_enum) { + std::forward<Visitor>(visitor) + .template Visit<Wrapper<FlatbuffersEnumTag<int16_t>>>(field); } else { - visitor.template Visit<int16_t>(field); + std::forward<Visitor>(visitor).template Visit<Wrapper<int16_t>>(field); } break; case reflection::BaseType::Int: - if (field_index >= 0) { - visitor.template Visit<FlatbuffersEnumTag<int32_t>>(field); + if (is_enum) { + std::forward<Visitor>(visitor) + .template Visit<Wrapper<FlatbuffersEnumTag<int32_t>>>(field); } else { - visitor.template Visit<int32_t>(field); + std::forward<Visitor>(visitor).template Visit<Wrapper<int32_t>>(field); } break; case reflection::BaseType::Long: - if (field_index >= 0) { - visitor.template Visit<FlatbuffersEnumTag<int64_t>>(field); + if (is_enum) { + std::forward<Visitor>(visitor) + .template Visit<Wrapper<FlatbuffersEnumTag<int64_t>>>(field); } else { - visitor.template Visit<int64_t>(field); + std::forward<Visitor>(visitor).template Visit<Wrapper<int64_t>>(field); } break; case reflection::BaseType::UByte: - if (field_index >= 0) { - visitor.template Visit<FlatbuffersEnumTag<uint8_t>>(field); + if (is_enum) { + std::forward<Visitor>(visitor) + .template Visit<Wrapper<FlatbuffersEnumTag<uint8_t>>>(field); } else { - visitor.template Visit<uint8_t>(field); + std::forward<Visitor>(visitor).template Visit<Wrapper<uint8_t>>(field); } break; case reflection::BaseType::UShort: - if (field_index >= 0) { - visitor.template Visit<FlatbuffersEnumTag<uint16_t>>(field); + if (is_enum) { + std::forward<Visitor>(visitor) + .template Visit<Wrapper<FlatbuffersEnumTag<uint16_t>>>(field); } else { - visitor.template Visit<uint16_t>(field); + std::forward<Visitor>(visitor).template Visit<Wrapper<uint16_t>>(field); } break; case reflection::BaseType::UInt: - if (field_index >= 0) { - visitor.template Visit<FlatbuffersEnumTag<uint32_t>>(field); + if (is_enum) { + std::forward<Visitor>(visitor) + .template Visit<Wrapper<FlatbuffersEnumTag<uint32_t>>>(field); } else { - visitor.template Visit<uint32_t>(field); + std::forward<Visitor>(visitor).template Visit<Wrapper<uint32_t>>(field); } break; case reflection::BaseType::ULong: - if (field_index >= 0) { - visitor.template Visit<FlatbuffersEnumTag<uint64_t>>(field); + if (is_enum) { + std::forward<Visitor>(visitor) + .template Visit<Wrapper<FlatbuffersEnumTag<uint64_t>>>(field); } else { - visitor.template Visit<uint64_t>(field); + std::forward<Visitor>(visitor).template Visit<Wrapper<uint64_t>>(field); } break; case reflection::BaseType::Float: - visitor.template Visit<float>(field); + std::forward<Visitor>(visitor).template Visit<Wrapper<float>>(field); break; case reflection::BaseType::Double: - visitor.template Visit<double>(field); + std::forward<Visitor>(visitor).template Visit<Wrapper<double>>(field); break; case reflection::BaseType::String: - visitor.template Visit<std::string>(field); + std::forward<Visitor>(visitor).template Visit<Wrapper<std::string>>( + field); break; case reflection::BaseType::Vector: - case reflection::BaseType::Vector64: - visitor.template Visit<FlatbuffersVectorTag>(field); - break; - case reflection::BaseType::Array: - visitor.template Visit<FlatbuffersArrayTag>(field); - break; - case reflection::BaseType::Obj: { - auto sub_object = schema->objects()->Get(field->type()->index()); - if (sub_object->is_struct()) { - visitor.template Visit<FlatbuffersStructTag>(field); + if constexpr (in_container) { + FUZZTEST_LOG(FATAL) << "Nested containers are not supported."; } else { - visitor.template Visit<FlatbuffersTableTag>(field); + VisitFlatbufferField<FlatbuffersVectorTag, /*in_container=*/true>( + schema, field, std::forward<Visitor>(visitor)); } break; - } + case reflection::BaseType::Vector64: + if constexpr (in_container) { + FUZZTEST_LOG(FATAL) << "Nested containers are not supported."; + } else { + VisitFlatbufferField<FlatbuffersVector64Tag, /*in_container=*/true>( + schema, field, std::forward<Visitor>(visitor)); + } + break; + case reflection::BaseType::Array: + if constexpr (in_container) { + FUZZTEST_LOG(FATAL) << "Nested containers are not supported."; + } else { + std::forward<Visitor>(visitor) + .template Visit<Wrapper<FlatbuffersArrayTag>>(field); + } + break; + case reflection::BaseType::Obj: + if (schema->objects()->Get(field_index)->is_struct()) { + std::forward<Visitor>(visitor) + .template Visit<Wrapper<FlatbuffersStructTag>>(field); + } else { + std::forward<Visitor>(visitor) + .template Visit<Wrapper<FlatbuffersTableTag>>(field); + } + break; case reflection::BaseType::Union: - visitor.template Visit<FlatbuffersUnionTag>(field); + std::forward<Visitor>(visitor) + .template Visit<Wrapper<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); } } @@ -285,98 +393,795 @@ auto GetDefaultDomain(const reflection::Schema* absl_nonnull schema, const reflection::Field* absl_nonnull field); -// Domain implementation for flatbuffers untyped tables. +// Base class for flatbuffers struct and table domain implementations. // The corpus type is a map of field ids to field values. -class FlatbuffersTableUntypedDomainImpl - : public fuzztest::domain_implementor::DomainBase< - /*Derived=*/FlatbuffersTableUntypedDomainImpl, - /*ValueType=*/const flatbuffers::Table* absl_nonnull, +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: - template <typename T> - friend class FlatbuffersTableDomainImpl; + 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; - using typename FlatbuffersTableUntypedDomainImpl::DomainBase::corpus_type; - using typename FlatbuffersTableUntypedDomainImpl::DomainBase::value_type; - - explicit FlatbuffersTableUntypedDomainImpl( + FlatbuffersUntypedObjectDomainBase( const reflection::Schema* absl_nonnull schema, - const reflection::Object* absl_nonnull table_object); + const reflection::Object* absl_nonnull object) + : schema_(schema), object_(object) { + for (const auto* field : *object_->fields()) { + fields_by_id_[field->id()] = field; + } + } - FlatbuffersTableUntypedDomainImpl( - const FlatbuffersTableUntypedDomainImpl& other); + virtual ~FlatbuffersUntypedObjectDomainBase() = default; - FlatbuffersTableUntypedDomainImpl& operator=( - const FlatbuffersTableUntypedDomainImpl& other); + FlatbuffersUntypedObjectDomainBase( + const FlatbuffersUntypedObjectDomainBase& other) + : DomainBase(other), + schema_(other.schema_), + object_(other.object_), + fields_by_id_(other.fields_by_id_) { + absl::MutexLock l(mutex_); + absl::MutexLock l_other(other.mutex_); + domains_ = other.domains_; + } - FlatbuffersTableUntypedDomainImpl(FlatbuffersTableUntypedDomainImpl&& other); + FlatbuffersUntypedObjectDomainBase& operator=( + const FlatbuffersUntypedObjectDomainBase& other) { + schema_ = other.schema_; + object_ = other.object_; + fields_by_id_ = other.fields_by_id_; + absl::MutexLock l(mutex_); + absl::MutexLock l_other(other.mutex_); + domains_ = other.domains_; + DomainBase::operator=(other); + return *this; + } - FlatbuffersTableUntypedDomainImpl& operator=( - FlatbuffersTableUntypedDomainImpl&& other); + FlatbuffersUntypedObjectDomainBase(FlatbuffersUntypedObjectDomainBase&& other) + : schema_(other.schema_), + object_(other.object_), + fields_by_id_(std::move(other.fields_by_id_)) { + absl::MutexLock l(mutex_); + absl::MutexLock l_other(other.mutex_); + domains_ = std::move(other.domains_); + DomainBase::operator=(other); + } + + FlatbuffersUntypedObjectDomainBase& operator=( + FlatbuffersUntypedObjectDomainBase&& other) { + schema_ = other.schema_; + object_ = other.object_; + fields_by_id_ = std::move(other.fields_by_id_); + absl::MutexLock l(mutex_); + absl::MutexLock l_other(other.mutex_); + domains_ = std::move(other.domains_); + DomainBase::operator=(std::move(other)); + return *this; + } // Initializes the corpus value. - corpus_type Init(absl::BitGenRef prng); + corpus_type Init(absl::BitGenRef prng) { + if (auto seed = this->MaybeGetRandomSeed(prng)) { + return *seed; + } + corpus_type val; + for (const auto& [_, field] : fields_by_id_) { + VisitFlatbufferField(schema_, field, + InitializeVisitor{Self(), prng, val}); + } + return val; + } // Mutates the corpus value. void Mutate(corpus_type& val, absl::BitGenRef prng, const domain_implementor::MutationMetadata& metadata, - bool only_shrink); + bool only_shrink) { + auto field_count = CountNumberOfFields(val); + if (field_count == 0) return; + auto selected_field_index = absl::Uniform(prng, 1ul, field_count + 1); + + MutateSelectedField(val, prng, metadata, only_shrink, selected_field_index); + } // Counts the number of fields that can be mutated. - uint64_t CountNumberOfFields(corpus_type& val); + // 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] : fields_by_id_) { + VisitFlatbufferField( + schema_, field, + CountNumberOfMutableFieldsVisitor{Self(), 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; + uint64_t fields_count = CountNumberOfFields(val); + if (fields_count < selected_field_index) { + return fields_count; + } + for (const auto& [_, field] : fields_by_id_) { + VisitFlatbufferField(schema_, field, + MutateSelectedFieldVisitor{ + Self(), prng, metadata, selected_field_index, + only_shrink, val, field_counter}); + if (field_counter >= selected_field_index) { + return field_counter; + } + } + return field_counter; + } + + auto GetPrinter() const { return Printer{Self()}; } + + 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{Self(), field_corpus, result}); + if (!result.ok()) return result; + } + return absl::OkStatus(); + } + + value_type GetValue(const corpus_type& value) const { + FUZZTEST_LOG(FATAL) + << "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{Self(), (*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{Self(), field_corpus, pair_subs.emplace_back()}); + } + return out; + } + + protected: + const reflection::Schema* schema_; + const reflection::Object* object_; + absl::btree_map<typename corpus_type::key_type, const reflection::Field*> + fields_by_id_; + 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& Self() { return static_cast<Derived&>(*this); } + const Derived& Self() const { return static_cast<const Derived&>(*this); } + + bool IsSupportedField(const reflection::Field* absl_nonnull field) const { + auto base_type = field->type()->base_type(); + // Union types are handled as part of the union field. + if (base_type == reflection::BaseType::UType) return false; + 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; + } + // TODO: Support Array fields. + return false; + } + + const reflection::Field* absl_nullable GetFieldById( + typename corpus_type::key_type id) const { + if (auto it = fields_by_id_.find(id); it != fields_by_id_.end()) { + return it->second; + } + return 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 Derived& derived; + const GenericDomainCorpusType& field_corpus; + domain_implementor::RawSink sink; + domain_implementor::PrintMode mode; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + auto& domain = derived.template GetCachedDomain<T>(field); + absl::Format(sink, "%s: ", field->name()->c_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 = + derived.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 Derived& derived; + + void PrintCorpusValue(const corpus_type& value, + 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); + } + // 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 = derived.GetFieldById(id); + if (field == nullptr) { + absl::Format(out, "<unknown field: %d>", id); + } else { + VisitFlatbufferField( + derived.schema_, field, + PrinterVisitor{derived, value.at(id), out, mode}); + } + first = false; + } + absl::Format(out, "}"); + } + }; + + struct InitializeVisitor { + const Derived& derived; + absl::BitGenRef prng; + corpus_type& corpus; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + auto& domain = derived.template GetCachedDomain<T>(field); + corpus[field->id()] = domain.Init(prng); + } + }; + + struct CountNumberOfMutableFieldsVisitor { + const Derived& derived; + uint64_t& field_count; + corpus_type& corpus_value; + const bool only_shrink = false; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + if (!derived.IsSupportedField(field)) return; + auto it = corpus_value.find(field->id()); + if (only_shrink && it == corpus_value.end()) return; + + field_count++; + + if constexpr (std::is_same_v<T, FlatbuffersTableTag> || + std::is_same_v<T, FlatbuffersStructTag> || + std::is_same_v<T, FlatbuffersUnionTag> || + is_flatbuffers_container_of_v<T, FlatbuffersTableTag> || + is_flatbuffers_container_of_v<T, FlatbuffersStructTag> || + is_flatbuffers_container_of_v<T, FlatbuffersUnionTag>) { + if (it == corpus_value.end()) return; + auto& domain = derived.template GetCachedDomain<T>(field); + // Count the fields in the corpus for domains that support it. + field_count += domain.CountNumberOfFields(it->second); + } + } + }; + + struct MutateSelectedFieldVisitor { + const Derived& derived; + absl::BitGenRef prng; + const domain_implementor::MutationMetadata& metadata; + const uint64_t selected_field_index; + const bool only_shrink; + corpus_type& corpus_value; + uint64_t& field_counter; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + if (!derived.IsSupportedField(field)) return; + auto it = corpus_value.find(field->id()); + if (only_shrink && it == corpus_value.end()) return; + + field_counter++; + auto& domain = derived.template GetCachedDomain<T>(field); + if (field_counter == selected_field_index) { + if (it == corpus_value.end()) { + it = corpus_value.try_emplace(field->id(), domain.Init(prng)).first; + } + domain.Mutate(it->second, prng, metadata, only_shrink); + return; + } + + if constexpr (std::is_same_v<T, FlatbuffersTableTag> || + std::is_same_v<T, FlatbuffersStructTag> || + std::is_same_v<T, FlatbuffersUnionTag> || + is_flatbuffers_container_of_v<T, FlatbuffersTableTag> || + is_flatbuffers_container_of_v<T, FlatbuffersStructTag> || + is_flatbuffers_container_of_v<T, FlatbuffersUnionTag>) { + if (it == corpus_value.end()) return; + field_counter += + domain.MutateSelectedField(it->second, prng, metadata, only_shrink, + selected_field_index - field_counter); + } + } + }; + + struct ParseVisitor { + const Derived& derived; + const IRObject& ir_object; + std::optional<GenericDomainCorpusType>& corpus; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + auto& domain = derived.template GetCachedDomain<T>(field); + corpus = domain.ParseCorpus(ir_object); + } + }; + + struct SerializeVisitor { + const Derived& derived; + const GenericDomainCorpusType& corpus; + IRObject& ir_object; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + auto& domain = derived.template GetCachedDomain<T>(field); + ir_object = domain.SerializeCorpus(corpus); + } + }; + + struct ValidateVisitor { + const Derived& derived; + const GenericDomainCorpusType& inner_corpus; + absl::Status& status; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + auto& domain = derived.template GetCachedDomain<T>(field); + status = domain.ValidateCorpusValue(inner_corpus); + if (!status.ok()) { + status = Prefix(status, absl::StrCat("Invalid value for field ", + field->name()->c_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_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::FlatBufferBuilder64& 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) { + [[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>) { + 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>) { + auto inner_value = value->GetField<T>(field->offset()); + inner_corpus = domain.FromValue(inner_value); + } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) { + auto inner_value = + value->GetStruct<const flatbuffers::Struct*>(field->offset()); + inner_corpus = domain.FromValue(inner_value); + } + + if (inner_corpus.has_value()) { + out[field->id()] = std::move(*inner_corpus); + } + }; + }; + + struct BuildValueVisitor { + const FlatbuffersStructUntypedDomainImpl& self; + const corpus_type& corpus_value; + uint8_t* struct_ptr; + + template <typename T> + void Visit(const reflection::Field* absl_nonnull field) { + [[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>) { + 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()); + 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_domain.fields_by_id_) { + VisitFlatbufferField(sub_domain.schema_, nested_field, + BuildValueVisitor{sub_domain, inner_corpus_value, + struct_ptr + field->offset()}); + } + } else if constexpr (std::is_same_v<T, FlatbuffersArrayTag>) { + // TODO (b/405938558): Implement array support. + } + } + }; +}; + +// 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>; + +// 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); + + FlatbuffersUnionDomainImpl(const FlatbuffersUnionDomainImpl& other); + FlatbuffersUnionDomainImpl(FlatbuffersUnionDomainImpl&& other); + FlatbuffersUnionDomainImpl& operator=( + const FlatbuffersUnionDomainImpl& other); + FlatbuffersUnionDomainImpl& operator=(FlatbuffersUnionDomainImpl&& other); + + // 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); + + uint64_t MutateSelectedField( + corpus_type& corpus_value, absl::BitGenRef prng, + const domain_implementor::MutationMetadata& metadata, bool only_shrink, uint64_t selected_field_index); auto GetPrinter() const { return Printer{*this}; } absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const; - value_type GetValue(const corpus_type& value) const { - FUZZTEST_LOG(FATAL) - << "GetValue is not supported for the untyped Flatbuffers domain."; - // Untyped domain does not support GetValue since if it is a nested table it - // would need the top level table corpus value to be able to build it. - return nullptr; + // UNSUPPORTED: Flatbuffers unions user values are not supported. + value_type GetValue(const corpus_type& corpus_value) const { + FUZZTEST_LOG(FATAL) << "GetValue is not supported for unions."; } - // Converts the table pointer to a corpus value. + // 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& value) const; + IRObject SerializeCorpus(const corpus_type& corpus_value) const; private: - const reflection::Schema* absl_nonnull schema_; - const reflection::Object* absl_nonnull table_object_; + 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 corpus_type::key_type, CopyableAny> + mutable absl::flat_hash_map< + typename FlatbuffersUnionTypeDomainImpl::value_type, CopyableAny> domains_ ABSL_GUARDED_BY(mutex_); - bool IsSupportedField(const reflection::Field* absl_nonnull field) const; + // Creates flatbuffer from the corpus value. + std::optional<flatbuffers::uoffset_t> BuildValue( + const corpus_type& corpus_value, + flatbuffers::FlatBufferBuilder64& 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, inserted] = + domains_.try_emplace(enum_value.value(), std::in_place_type<DomainT>, + GetDefaultDomainForType<T>(enum_value)); + return it->second.template 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::FlatBufferBuilder64& builder) const; // Returns the domain for the given field. - // The domain is cached, and the same instance is returned for the same field. + // The domain is cached, and the same instance is returned for the same + // field. template <typename T> - auto& GetCachedDomain(const reflection::Field* field) const { + auto& GetCachedDomain(const reflection::Field* absl_nonnull field) const { auto get_optional_domain = [this, field]() { - auto optional_domain = OptionalOf(GetDefaultDomain<T>(schema_, field)); - if (!field->optional()) { + auto inner_domain = GetDefaultDomain<T>(schema_, field); + auto optional_domain = OptionalOf(inner_domain); + if (!field->optional() || field->required()) { optional_domain.SetWithoutNull(); } + if constexpr (std::is_same_v<T, FlatbuffersUnionTag>) { + auto union_type = schema_->enums()->Get(field->type()->index()); + // If the union has only one type (NONE), we can always return null. + if (union_type->values()->size() == 1) { + optional_domain.SetAlwaysNull(); + } + } return Domain<value_type_t<decltype(optional_domain)>>{optional_domain}; }; @@ -394,36 +1199,18 @@ return it->second.template GetAs<DomainT>(); } - const reflection::Field* absl_nullable GetFieldById( - typename corpus_type::key_type id) const { - const auto it = - absl::c_find_if(*table_object_->fields(), - [id](const auto* field) { return field->id() == id; }); - return it != table_object_->fields()->end() ? *it : nullptr; - } - - struct SerializeVisitor { - const FlatbuffersTableUntypedDomainImpl& self; - const GenericDomainCorpusType& corpus_value; - IRObject& out; - - template <typename T> - void Visit(const reflection::Field* absl_nonnull field) { - out = self.GetCachedDomain<T>(field).SerializeCorpus(corpus_value); - } - }; - 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 { + void Visit(const reflection::Field* absl_nonnull field) { [[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())) { @@ -459,13 +1246,139 @@ << "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>) { + inner_value = + user_value->GetStruct<const flatbuffers::Struct*>(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); + } + } 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( + absl::StrCat(field->name()->c_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); + 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 if constexpr (std::is_same_v<Element, FlatbuffersTableTag>) { + return static_cast<flatbuffers::Vector< + flatbuffers::Offset<flatbuffers::Table>, Offset>*>(nullptr); + } else if constexpr (std::is_same_v<Element, FlatbuffersStructTag>) { + // Struct vector are serialized inline and accessed as bytes. + return static_cast<const flatbuffers::Vector<uint8_t>*>(nullptr); + } else if constexpr (std::is_same_v<Element, FlatbuffersUnionTag>) { + return static_cast<flatbuffers::Vector<flatbuffers::Offset<void>>*>( + 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; + 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()); + if constexpr (std::is_same_v<Element, FlatbuffersUnionTag>) { + constexpr char kUnionTypeFieldSuffix[] = "_type"; + auto type_field = self.object_->fields()->LookupByKey( + absl::StrCat(field->name()->c_str(), kUnionTypeFieldSuffix) + .c_str()); + FUZZTEST_CHECK(type_field != nullptr) << "Union type field not found."; + const auto* type_vec = + user_value->GetPointer<const flatbuffers::Vector<uint8_t>*>( + type_field->offset()); + for (decltype(vec->size()) i = 0; i < vec->size(); ++i) { + vector_corpus->push_back({type_vec->Get(i), vec->Get(i)}); + } + } else { + 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 if constexpr (std::is_same_v<Element, FlatbuffersStructTag>) { + const reflection::Object* object_def = + self.schema_->objects()->Get(field->type()->index()); + // Struct vector are serialized inline. + const uint8_t* struct_data_ptr = + vec->Data() + i * object_def->bytesize(); + vector_corpus->push_back( + reinterpret_cast<const flatbuffers::Struct*>(struct_data_ptr)); + } else { + vector_corpus->push_back(vec->Get(i)); + } + } + } + } }; // Create out-of-line table fields, see `BuildTable` for details. @@ -477,7 +1390,7 @@ const typename corpus_type::mapped_type& corpus_value; template <typename T> - void Visit(const reflection::Field* absl_nonnull field) const { + 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); @@ -497,24 +1410,162 @@ } 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)); + } 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 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) { + if (field->optional()) { + return; + } + // Handle case where value is std::nullopt but field is required. + // Create an empty vector of the appropriate type. + value = typename decltype(value)::value_type{}; + } + 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> || + std::is_same_v<Element, FlatbuffersStructTag>) { + 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) + .GetAs<std::list<corpus_type>>(); + auto sub_object = self.schema_->objects()->Get(field->type()->index()); + if constexpr (std::is_same_v<Element, FlatbuffersTableTag>) { + std::vector<flatbuffers::Offset<flatbuffers::Table>> vec_offsets; + FlatbuffersTableUntypedDomainImpl domain(self.schema_, sub_object); + vec_offsets.reserve(container_corpus.size()); + 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 { + uint8_t* vec_ptr = nullptr; + FlatbuffersStructUntypedDomainImpl inner_domain(self.schema_, + sub_object); + auto vec_offset = builder.CreateUninitializedVector( + container_corpus.size(), sub_object->bytesize(), + sub_object->minalign(), &vec_ptr); + size_t i = 0; + for (const auto& inner_corpus : container_corpus) { + uint8_t* current_struct_ptr = vec_ptr + i * sub_object->bytesize(); + inner_domain.BuildValue(inner_corpus, current_struct_ptr); + ++i; + } + offsets.insert({field->id(), vec_offset}); + } + } 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; + 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}); + } else if constexpr (std::is_same_v<Element, FlatbuffersUnionTag>) { + const reflection::Enum* union_type = + self.schema_->enums()->Get(field->type()->index()); + FlatbuffersUnionDomainImpl domain{self.schema_, union_type}; + constexpr char kUnionTypeFieldSuffix[] = "_type"; + const reflection::Field* type_field = + self.object_->fields()->LookupByKey( + absl::StrCat(field->name()->c_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. + // 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::FlatBufferBuilder64& builder; @@ -523,7 +1574,7 @@ const typename corpus_type::value_type::second_type& corpus_value; template <typename T> - void Visit(const reflection::Field* absl_nonnull field) const { + void Visit(const reflection::Field* absl_nonnull field) { 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); @@ -533,17 +1584,27 @@ } // 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 constexpr (is_flatbuffers_container_of_v<T, FlatbuffersUnionTag>) { + constexpr char kUnionTypeFieldSuffix[] = "_type"; + const reflection::Field* type_field = + self.object_->fields()->LookupByKey( + absl::StrCat(field->name()->c_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()) { if (field->offset64()) { - builder.AddOffset( - field->offset(), - flatbuffers::Offset64<flatbuffers::String>(it->second)); + builder.AddOffset(field->offset(), + flatbuffers::Offset64<>(it->second)); } else { - builder.AddOffset( - field->offset(), - flatbuffers::Offset<flatbuffers::String>(it->second)); + builder.AddOffset(field->offset(), + flatbuffers::Offset<>(it->second)); } } } else if constexpr (std::is_same_v<T, FlatbuffersTableTag>) { @@ -553,134 +1614,49 @@ field->offset(), flatbuffers::Offset<flatbuffers::Table>(it->second)); } - } - } - }; - - struct ParseVisitor { - const FlatbuffersTableUntypedDomainImpl& self; - const IRObject& obj; - std::optional<GenericDomainCorpusType>& out; - - template <typename T> - void Visit(const reflection::Field* absl_nonnull field) { - out = self.GetCachedDomain<T>(field).ParseCorpus(obj); - } - }; - - struct ValidateVisitor { - const FlatbuffersTableUntypedDomainImpl& self; - const GenericDomainCorpusType& corpus_value; - absl::Status& out; - - template <typename T> - void Visit(const reflection::Field* absl_nonnull field) { - auto& domain = self.GetCachedDomain<T>(field); - out = domain.ValidateCorpusValue(corpus_value); - if (!out.ok()) { - out = Prefix(out, absl::StrCat("Invalid value for field ", - field->name()->str())); - } - } - }; - - struct InitializeVisitor { - FlatbuffersTableUntypedDomainImpl& self; - absl::BitGenRef prng; - corpus_type& val; - - template <typename T> - void Visit(const reflection::Field* absl_nonnull field) { - auto& domain = self.GetCachedDomain<T>(field); - val[field->id()] = domain.Init(prng); - } - }; - - struct CountNumberOfMutableFieldsVisitor { - const FlatbuffersTableUntypedDomainImpl& self; - uint64_t& total_weight; - corpus_type& val; - bool only_shrink = false; - - template <typename T> - void Visit(const reflection::Field* absl_nonnull field) const { - if (!self.IsSupportedField(field)) return; - if (only_shrink && !val.contains(field->id())) return; - - // Add the weight of the field itself. - total_weight += 1; - - auto& domain = self.GetCachedDomain<T>(field); - if (auto it = val.find(field->id()); it != val.end()) { - // Add the weight of the field corpus. - total_weight += domain.CountNumberOfFields(it->second); - } - } - }; - - struct MutateVisitor { - FlatbuffersTableUntypedDomainImpl& self; - absl::BitGenRef prng; - const domain_implementor::MutationMetadata& metadata; - bool only_shrink; - corpus_type& corpus_value; - - template <typename T> - void Visit(const reflection::Field* absl_nonnull field) { - auto& domain = self.GetCachedDomain<T>(field); - auto it = corpus_value.find(field->id()); - if (it == corpus_value.end()) { - if (only_shrink) return; - it = corpus_value.try_emplace(field->id(), domain.Init(prng)).first; - } - domain.Mutate(it->second, prng, metadata, only_shrink); - } - }; - - struct Printer { - const FlatbuffersTableUntypedDomainImpl& 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; - field_ids.reserve(value.size()); - 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, ", "); + } 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; } - 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}); + 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()); } - first = false; + } 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( + absl::StrCat(field->name()->c_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)>>(); + uint8_t type_value = domain.GetType(inner_corpus); + builder.AddElement<uint8_t>(type_field->offset(), type_value, 0); + } } - absl::Format(out, "}"); - } - }; - - struct PrinterVisitor { - const FlatbuffersTableUntypedDomainImpl& self; - const GenericDomainCorpusType& val; - domain_implementor::RawSink out; - domain_implementor::PrintMode mode; - - template <typename T> - void Visit(const reflection::Field* absl_nonnull field) const { - auto& domain = self.GetCachedDomain<T>(field); - absl::Format(out, "%s: ", field->name()->str()); - domain_implementor::PrintValue(domain, val, out, mode); } }; }; @@ -702,14 +1678,14 @@ auto table_object = schema->objects()->Get(field->type()->index()); return FlatbuffersTableUntypedDomainImpl{schema, table_object}; } else if constexpr (std::is_same_v<T, FlatbuffersStructTag>) { - // TODO: support structs. - return placeholder; + auto struct_object = schema->objects()->Get(field->type()->index()); + return FlatbuffersStructUntypedDomainImpl{schema, struct_object}; } 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; + auto union_type = schema->enums()->Get(field->type()->index()); + return FlatbuffersUnionDomainImpl{schema, union_type}; + } else if constexpr (is_any_flatbuffers_vector_tag_v<T>) { + auto elem_domain = GetDefaultDomain<typename T::value_type>(schema, field); + return VectorOf(elem_domain); } else { return Arbitrary<T>(); } @@ -729,7 +1705,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> { @@ -764,6 +1740,14 @@ return inner_->CountNumberOfFields(val.untyped_corpus); } + uint64_t MutateSelectedField( + corpus_type& val, absl::BitGenRef prng, + const domain_implementor::MutationMetadata& metadata, bool only_shrink, + uint64_t selected_field_index) { + return inner_->MutateSelectedField(val.untyped_corpus, prng, metadata, + only_shrink, selected_field_index); + } + // Mutates the given corpus value. void Mutate(corpus_type& val, absl::BitGenRef prng, const domain_implementor::MutationMetadata& metadata,
diff --git a/fuzztest/internal/test_flatbuffers.fbs b/fuzztest/internal/test_flatbuffers.fbs index 02177c0..4811d9b 100644 --- a/fuzztest/internal/test_flatbuffers.fbs +++ b/fuzztest/internal/test_flatbuffers.fbs
@@ -1,53 +1,92 @@ +// 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 { +enum ByteEnum:byte { First, - Second -} -enum ShortEnum: short { - First, - Second + Second, } -enum IntEnum: int { +enum ShortEnum:short { First, - Second + Second, } -enum LongEnum: long { +enum IntEnum:int { First, - Second + Second, } -enum UByteEnum: ubyte { +enum LongEnum:long { First, - Second + Second, } -enum UShortEnum: ushort { +enum UByteEnum:ubyte { First, - Second + Second, } -enum UIntEnum: uint { + +enum UShortEnum:ushort { First, - Second + Second, } -enum ULongEnum: ulong { + +enum UIntEnum:uint { First, - Second + Second, +} + +enum ULongEnum:ulong { + First, + Second, } struct BoolStruct { - b: bool; - a_b: [bool:2]; + b:bool; +} + +struct DefaultStruct { + b:bool; + i8:byte; + i16:short; + i32:int; + i64:long; + u8:ubyte; + u16:ushort; + u32:uint; + u64:ulong; + f:float; + d:double; + ei8:ByteEnum; + ei16:ShortEnum; + ei32:IntEnum; + ei64:LongEnum; + eu8:UByteEnum; + eu16:UShortEnum; + eu32:UIntEnum; + eu64:ULongEnum; + s:BoolStruct; } table BoolTable { - b: bool; + b:bool; } table StringTable { - str: string; + str:string; } union Union { @@ -56,93 +95,144 @@ 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; + 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; + 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); + 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); } table RecursiveTable { - t: NestedRecursiveTable; + t:NestedRecursiveTable; } table NestedRecursiveTable { - t: RecursiveTable; -} - -table UnsupportedTypesTable { - u: Union; - s: BoolStruct; - 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: [BoolStruct]; + t:RecursiveTable; } root_type DefaultTable;
diff --git a/fuzztest/internal/test_flatbuffers_64bits.fbs b/fuzztest/internal/test_flatbuffers_64bits.fbs index b893c88..68e98b3 100644 --- a/fuzztest/internal/test_flatbuffers_64bits.fbs +++ b/fuzztest/internal/test_flatbuffers_64bits.fbs
@@ -16,6 +16,7 @@ table DefaultTable64 { str:string (offset64); + v_u8:[ubyte] (vector64); } root_type DefaultTable64;