Skip to content

Commit 9ccb309

Browse files
FuzzTest Teamcopybara-github
FuzzTest Team
authored andcommitted
Add Flatbuffers table nested support.
PiperOrigin-RevId: 736908111
1 parent 09422aa commit 9ccb309

File tree

8 files changed

+1359
-2
lines changed

8 files changed

+1359
-2
lines changed

MODULE.bazel

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ bazel_dep(
4242
name = "platforms",
4343
version = "0.0.10",
4444
)
45+
bazel_dep(
46+
name = "flatbuffers",
47+
version = "25.2.10"
48+
)
4549
# GoogleTest is not a dev dependency, because it's needed when FuzzTest is used
4650
# with GoogleTest integration (e.g., googletest_adaptor). Note that the FuzzTest
4751
# framework can be used without GoogleTest integration as well.
@@ -55,8 +59,6 @@ bazel_dep(
5559
name = "protobuf",
5660
version = "30.2",
5761
)
58-
# TODO(lszekeres): Make this a dev dependency, as the protobuf library is only
59-
# required for testing.
6062
bazel_dep(
6163
name = "rules_proto",
6264
version = "7.1.0",

domain_tests/BUILD

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ cc_test(
3333
],
3434
)
3535

36+
cc_test(
37+
name = "arbitrary_domains_flatbuffers_test",
38+
srcs = ["arbitrary_domains_flatbuffers_test.cc"],
39+
deps = [
40+
":domain_testing",
41+
"@abseil-cpp//absl/random",
42+
"@com_google_fuzztest//fuzztest:domain",
43+
"@com_google_fuzztest//fuzztest:flatbuffers",
44+
"@com_google_fuzztest//fuzztest:meta",
45+
"@com_google_fuzztest//fuzztest:test_flatbuffers_cc_fbs",
46+
"@flatbuffers//:runtime_cc",
47+
"@googletest//:gtest_main",
48+
],
49+
)
50+
3651
cc_test(
3752
name = "arbitrary_domains_protobuf_test",
3853
srcs = ["arbitrary_domains_protobuf_test.cc"],
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <optional>
16+
#include <string_view>
17+
#include <utility>
18+
#include <vector>
19+
20+
#include "gmock/gmock.h"
21+
#include "gtest/gtest.h"
22+
#include "absl/random/random.h"
23+
#include "flatbuffers/base.h"
24+
#include "flatbuffers/buffer.h"
25+
#include "flatbuffers/flatbuffer_builder.h"
26+
#include "flatbuffers/string.h"
27+
#include "./fuzztest/domain.h"
28+
#include "./domain_tests/domain_testing.h"
29+
#include "./fuzztest/flatbuffers.h"
30+
#include "./fuzztest/internal/meta.h"
31+
#include "./fuzztest/test_flatbuffers_generated.h"
32+
33+
namespace fuzztest {
34+
namespace {
35+
36+
using ::fuzztest::internal::NestedTestFbsTable;
37+
using ::fuzztest::internal::OptionalRequiredTestFbsTable;
38+
using ::fuzztest::internal::SimpleTestFbsTable;
39+
using ::testing::Contains;
40+
using ::testing::IsTrue;
41+
using ::testing::ResultOf;
42+
43+
TEST(FlatbuffersMetaTest, IsFlatbuffersTable) {
44+
static_assert(internal::is_flatbuffers_table_v<SimpleTestFbsTable>);
45+
static_assert(!internal::is_flatbuffers_table_v<int>);
46+
}
47+
48+
TEST(FlatbuffersTableDomainImplTest, SimpleTestFbsTableValueRoundTrip) {
49+
auto domain = Arbitrary<SimpleTestFbsTable>();
50+
51+
flatbuffers::FlatBufferBuilder fbb;
52+
auto table_offset = internal::CreateSimpleTestFbsTableDirect(
53+
fbb, true, 1.0, "foo bar baz", internal::TestFbsEnum_Second);
54+
fbb.Finish(table_offset);
55+
auto table = flatbuffers::GetRoot<SimpleTestFbsTable>(fbb.GetBufferPointer());
56+
57+
auto corpus = domain.FromValue(table);
58+
ASSERT_TRUE(corpus.has_value());
59+
ASSERT_OK(domain.ValidateCorpusValue(*corpus));
60+
61+
auto ir = domain.SerializeCorpus(corpus.value());
62+
63+
auto new_corpus = domain.ParseCorpus(ir);
64+
ASSERT_TRUE(new_corpus.has_value());
65+
ASSERT_OK(domain.ValidateCorpusValue(*new_corpus));
66+
67+
auto new_table = domain.GetValue(*new_corpus);
68+
EXPECT_EQ(new_table->b(), true);
69+
EXPECT_EQ(new_table->f(), 1.0);
70+
EXPECT_EQ(new_table->str()->str(), "foo bar baz");
71+
EXPECT_TRUE(new_table->e() == internal::TestFbsEnum_Second);
72+
}
73+
74+
TEST(FlatbuffersTableDomainImplTest, InitGeneratesSeeds) {
75+
auto domain = Arbitrary<SimpleTestFbsTable>();
76+
77+
flatbuffers::FlatBufferBuilder fbb;
78+
auto table_offset = internal::CreateSimpleTestFbsTableDirect(
79+
fbb, true, 1.0, "foo bar baz", internal::TestFbsEnum_Second);
80+
fbb.Finish(table_offset);
81+
auto table = flatbuffers::GetRoot<SimpleTestFbsTable>(fbb.GetBufferPointer());
82+
83+
domain.WithSeeds({table});
84+
85+
std::vector<Value<decltype(domain)>> values;
86+
absl::BitGen bitgen;
87+
values.reserve(1000);
88+
for (int i = 0; i < 1000; ++i) {
89+
Value value(domain, bitgen);
90+
values.push_back(std::move(value));
91+
}
92+
93+
EXPECT_THAT(
94+
values,
95+
Contains(ResultOf(
96+
[table](const auto& val) {
97+
bool has_same_str =
98+
val.user_value->str() == nullptr && table->str() == nullptr;
99+
if (val.user_value->str() != nullptr && table->str() != nullptr) {
100+
has_same_str =
101+
val.user_value->str()->str() == table->str()->str();
102+
}
103+
return (val.user_value->b() == table->b() &&
104+
val.user_value->f() == table->f() &&
105+
val.user_value->e() == table->e() && has_same_str);
106+
},
107+
IsTrue())));
108+
}
109+
110+
TEST(FlatbuffersTableDomainImplTest, EventuallyMutatesAllTableFields) {
111+
auto domain = Arbitrary<SimpleTestFbsTable>();
112+
113+
absl::BitGen bitgen;
114+
Value val(domain, bitgen);
115+
116+
const auto verify_field_changes = [&](std::string_view name, auto get) {
117+
Set<decltype(get(val.user_value))> values;
118+
119+
int iterations = 10'000;
120+
while (--iterations > 0 && values.size() < 2) {
121+
values.insert(get(val.user_value));
122+
val.Mutate(domain, bitgen, {}, false);
123+
}
124+
EXPECT_GT(iterations, 0)
125+
<< "Field: " << name << " -- " << testing::PrintToString(values);
126+
};
127+
128+
verify_field_changes("b", [](auto v) { return v->b(); });
129+
verify_field_changes("f", [](auto v) { return v->f(); });
130+
verify_field_changes("str",
131+
[](auto v) { return v->str() ? v->str()->str() : ""; });
132+
verify_field_changes("e", [](auto v) { return v->e(); });
133+
}
134+
135+
TEST(FlatbuffersTableDomainImplTest, OptionalFieldsEventuallyBecomeEmpty) {
136+
auto domain = Arbitrary<OptionalRequiredTestFbsTable>();
137+
138+
absl::BitGen bitgen;
139+
Value val(domain, bitgen);
140+
141+
const auto verify_field_becomes_null = [&](std::string_view name, auto has) {
142+
for (int i = 0; i < 10'000; ++i) {
143+
val.Mutate(domain, bitgen, {}, false);
144+
if (!has(val.user_value)) {
145+
break;
146+
}
147+
}
148+
EXPECT_FALSE(has(val.user_value)) << "Field never became unset: " << name;
149+
};
150+
151+
verify_field_becomes_null("opt_scalar",
152+
[](auto v) { return v->opt_scalar().has_value(); });
153+
verify_field_becomes_null("opt_str",
154+
[](auto v) { return v->opt_str() != nullptr; });
155+
}
156+
157+
TEST(FlatbuffersTableDomainImplTest, DefaultAndRequiredFieldsAlwaysSet) {
158+
auto domain = Arbitrary<OptionalRequiredTestFbsTable>();
159+
160+
absl::BitGen bitgen;
161+
Value val(domain, bitgen);
162+
163+
const auto verify_field_always_set = [&](std::string_view name, auto has) {
164+
for (int i = 0; i < 10'000; ++i) {
165+
val.Mutate(domain, bitgen, {}, false);
166+
if (!has(val.user_value)) {
167+
break;
168+
}
169+
}
170+
EXPECT_TRUE(has(val.user_value)) << "Field is not set: " << name;
171+
};
172+
173+
verify_field_always_set("def_scalar", [](auto v) { return true; });
174+
verify_field_always_set("req_str",
175+
[](auto v) { return v->req_str() != nullptr; });
176+
}
177+
178+
TEST(FlatbuffersTableDomainImplTest, NestedTableValueRoundTrip) {
179+
auto domain = Arbitrary<NestedTestFbsTable>();
180+
absl::BitGen bitgen;
181+
Value val(domain, bitgen);
182+
183+
flatbuffers::FlatBufferBuilder fbb;
184+
auto child_offset = internal::CreateSimpleTestFbsTableDirect(
185+
fbb, true, 1.0, "foo bar baz", internal::TestFbsEnum_Second);
186+
auto parent_offset = internal::CreateNestedTestFbsTable(fbb, child_offset);
187+
fbb.Finish(parent_offset);
188+
auto table = flatbuffers::GetRoot<NestedTestFbsTable>(fbb.GetBufferPointer());
189+
190+
auto parent_corpus = domain.FromValue(table);
191+
ASSERT_TRUE(parent_corpus.has_value());
192+
193+
auto ir = domain.SerializeCorpus(parent_corpus.value());
194+
195+
auto new_corpus = domain.ParseCorpus(ir);
196+
ASSERT_TRUE(new_corpus.has_value());
197+
ASSERT_OK(domain.ValidateCorpusValue(*new_corpus));
198+
199+
auto new_table = domain.GetValue(parent_corpus.value());
200+
EXPECT_NE(new_table->t(), nullptr);
201+
EXPECT_EQ(new_table->t()->b(), true);
202+
EXPECT_EQ(new_table->t()->f(), 1.0);
203+
EXPECT_NE(new_table->t()->str(), nullptr);
204+
EXPECT_EQ(new_table->t()->str()->str(), "foo bar baz");
205+
EXPECT_TRUE(new_table->t()->e() == internal::TestFbsEnum_Second);
206+
}
207+
208+
TEST(FlatbuffersTableDomainImplTest, EventuallyMutatesAllNestedTableFields) {
209+
auto domain = Arbitrary<NestedTestFbsTable>();
210+
absl::BitGen bitgen;
211+
Value val(domain, bitgen);
212+
213+
const auto verify_field_changes = [&](std::string_view name, auto get) {
214+
Set<std::optional<decltype(get(val.user_value))>> values;
215+
216+
int iterations = 10'000;
217+
while (--iterations > 0 && values.size() < 2) {
218+
auto value = get(val.user_value);
219+
values.insert(value);
220+
val.Mutate(domain, bitgen, {}, false);
221+
}
222+
EXPECT_GT(iterations, 0)
223+
<< "Field: " << name << " -- " << testing::PrintToString(values);
224+
};
225+
226+
verify_field_changes("t.b", [](auto v) {
227+
return v->t() ? std::make_optional(v->t()->b()) : std::nullopt;
228+
});
229+
verify_field_changes("t.f", [](auto v) {
230+
return v->t() ? std::make_optional(v->t()->f()) : std::nullopt;
231+
});
232+
verify_field_changes("t.str", [](auto v) {
233+
return v->t() ? v->t()->str() ? std::make_optional(v->t()->str()->str())
234+
: std::nullopt
235+
: std::nullopt;
236+
});
237+
verify_field_changes("t.e", [](auto v) {
238+
return v->t() ? std::make_optional(v->t()->e()) : std::nullopt;
239+
});
240+
}
241+
242+
} // namespace
243+
} // namespace fuzztest

fuzztest/BUILD

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# FuzzTest: a coverage-guided fuzzing / property-based testing framework.
1616

1717
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
18+
load("@flatbuffers//:build_defs.bzl", "flatbuffer_library_public")
1819
load("@rules_proto//proto:defs.bzl", "proto_library")
1920

2021
package(default_visibility = ["//visibility:public"])
@@ -345,6 +346,7 @@ cc_library(
345346
":serialization",
346347
":status",
347348
":type_support",
349+
"@abseil-cpp//absl/algorithm:container",
348350
"@abseil-cpp//absl/base:core_headers",
349351
"@abseil-cpp//absl/base:no_destructor",
350352
"@abseil-cpp//absl/container:flat_hash_map",
@@ -422,6 +424,34 @@ cc_library(
422424
],
423425
)
424426

427+
cc_library(
428+
name = "flatbuffers",
429+
srcs = ["internal/domains/flatbuffers_domain_impl.h"],
430+
hdrs = ["flatbuffers.h"],
431+
deps = [
432+
":any",
433+
":domain_core",
434+
":logging",
435+
":meta",
436+
":serialization",
437+
":status",
438+
":type_support",
439+
"@abseil-cpp//absl/algorithm:container",
440+
"@abseil-cpp//absl/base:core_headers",
441+
"@abseil-cpp//absl/container:flat_hash_map",
442+
"@abseil-cpp//absl/container:flat_hash_set",
443+
"@abseil-cpp//absl/random",
444+
"@abseil-cpp//absl/random:bit_gen_ref",
445+
"@abseil-cpp//absl/random:distributions",
446+
"@abseil-cpp//absl/status",
447+
"@abseil-cpp//absl/status:statusor",
448+
"@abseil-cpp//absl/strings",
449+
"@abseil-cpp//absl/strings:str_format",
450+
"@abseil-cpp//absl/synchronization",
451+
"@flatbuffers//:runtime_cc",
452+
],
453+
)
454+
425455
cc_library(
426456
name = "fixture_driver",
427457
srcs = ["internal/fixture_driver.cc"],
@@ -799,6 +829,28 @@ cc_proto_library(
799829
deps = [":test_protobuf"],
800830
)
801831

832+
flatbuffer_library_public(
833+
name = "test_flatbuffers_fbs",
834+
srcs = ["internal/test_flatbuffers.fbs"],
835+
outs = [
836+
"test_flatbuffers_bfbs_generated.h",
837+
"test_flatbuffers_generated.h",
838+
],
839+
flatc_args = [
840+
"--bfbs-gen-embed",
841+
"--gen-name-strings",
842+
],
843+
language_flag = "-c",
844+
)
845+
846+
cc_library(
847+
name = "test_flatbuffers_cc_fbs",
848+
srcs = [":test_flatbuffers_fbs"],
849+
hdrs = [":test_flatbuffers_fbs"],
850+
features = ["-parse_headers"],
851+
deps = ["@flatbuffers//:runtime_cc"],
852+
)
853+
802854
cc_library(
803855
name = "type_support",
804856
srcs = ["internal/type_support.cc"],

fuzztest/flatbuffers.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef FUZZTEST_FUZZTEST_FLATBUFFERS_H_
16+
#define FUZZTEST_FUZZTEST_FLATBUFFERS_H_
17+
18+
#include "./fuzztest/internal/domains/flatbuffers_domain_impl.h" // IWYU pragma: export
19+
#endif // FUZZTEST_FUZZTEST_FLATBUFFERS_H_

0 commit comments

Comments
 (0)