Add full support for known extension fields. Serialization and parsing extension fields were supported before. Other missing capabilities including corpus validation, domain initialization, domain mutation, domain customization, and recursion detection are added. PiperOrigin-RevId: 551832440
diff --git a/doc/domains-reference.md b/doc/domains-reference.md index f284cfa..504ddcb 100644 --- a/doc/domains-reference.md +++ b/doc/domains-reference.md
@@ -314,7 +314,8 @@ .WithProtobufField("address", Arbitrary<Address>() .WithInt32Field("zipcode", InRange(10000, 99999)) - .WithStringField("state", String().WithSize(2)))); + .WithStringField("state", String().WithSize(2))) + .WithInt32Field("my.pkg.PersonExtender.id", InRange(100000, 999999))); ``` The inner domain is as follows: @@ -335,7 +336,7 @@ The field domains are indexed by field name and will be verified at startup. A mismatch between the field names and the inner domains will cause a runtime -failure. +failure. For extension fields, the full name should be used. IMPORTANT: Note that *optional* fields are not always set by the fuzzer.
diff --git a/domain_tests/arbitrary_domains_test.cc b/domain_tests/arbitrary_domains_test.cc index 82aac87..12bb285 100644 --- a/domain_tests/arbitrary_domains_test.cc +++ b/domain_tests/arbitrary_domains_test.cc
@@ -48,11 +48,18 @@ namespace fuzztest { namespace { +using ::fuzztest::internal::ProtoExtender; +using ::fuzztest::internal::TestProtobuf; +using ::fuzztest::internal::TestProtobuf_Enum; +using ::fuzztest::internal::TestProtobufWithExtension; +using ::fuzztest::internal::TestProtobufWithRequired; +using ::fuzztest::internal::TestSubProtobuf; using ::google::protobuf::FieldDescriptor; using ::testing::Contains; using ::testing::Each; using ::testing::ElementsAre; using ::testing::Ge; +using ::testing::Gt; using ::testing::IsEmpty; using ::testing::IsTrue; using ::testing::ResultOf; @@ -274,12 +281,12 @@ } TEST(ArbitraryProtocolBufferTest, InitGeneratesSeeds) { - internal::TestProtobuf seed; + TestProtobuf seed; seed.set_i32(42); seed.set_str("Hello"); - EXPECT_THAT(GenerateInitialValues( - Arbitrary<internal::TestProtobuf>().WithSeeds({seed}), 1000), + EXPECT_THAT(GenerateInitialValues(Arbitrary<TestProtobuf>().WithSeeds({seed}), + 1000), Contains(ResultOf( [&seed](const auto& val) { return google::protobuf::util::MessageDifferencer::Equals( @@ -291,7 +298,7 @@ // TODO(b/246448769): Rewrite the test to decrease the chance of failure. TEST(ProtocolBuffer, RepeatedMutationEventuallyMutatesAllFieldsOfArbitraryProtobuf) { - Domain<internal::TestProtobuf> domain = Arbitrary<internal::TestProtobuf>(); + Domain<TestProtobuf> domain = Arbitrary<TestProtobuf>(); absl::BitGen bitgen; Value val(domain, bitgen); @@ -338,10 +345,26 @@ VerifyRoundTripThroughConversion(val, domain); } +TEST(ProtocolBuffer, RepeatedMutationEventuallyMutatesExtensionFields) { + auto has_ext = ResultOf( + [](const auto& val) { + return val.user_value.HasExtension(internal::ProtoExtender::ext); + }, + IsTrue()); + auto has_rep_ext = ResultOf( + [](const auto& val) { + return val.user_value.ExtensionSize(internal::ProtoExtender::rep_ext); + }, + Gt(0)); + EXPECT_THAT( + GenerateNonUniqueValues(Arbitrary<TestProtobufWithExtension>(), 1, 5000), + AllOf(Contains(has_ext), Contains(has_rep_ext))); +} + // TODO(b/246652379): Re-enable after b/231212420 is fixed. TEST(ProtocolBuffer, DISABLED_ShrinkingEventuallyUnsetsAndEmptiesAllFieldsOfArbitraryProtobuf) { - Domain<internal::TestProtobuf> domain = Arbitrary<internal::TestProtobuf>(); + Domain<TestProtobuf> domain = Arbitrary<TestProtobuf>(); absl::BitGen bitgen; Value val(domain, bitgen); @@ -367,7 +390,7 @@ } TEST(ProtocolBufferWithRequiredFields, OptionalFieldIsEventuallySet) { - auto domain = Arbitrary<internal::TestProtobufWithRequired>() + auto domain = Arbitrary<TestProtobufWithRequired>() .WithRepeatedFieldsMaxSize(0) .WithProtobufFieldUnset("sub_req"); absl::BitGen bitgen; @@ -385,7 +408,7 @@ } TEST(ProtocolBufferWithRequiredFields, OptionalFieldIsEventuallyUnset) { - auto domain = Arbitrary<internal::TestProtobufWithRequired>() + auto domain = Arbitrary<TestProtobufWithRequired>() .WithRepeatedFieldsMaxSize(0) .WithProtobufFieldUnset("sub_req"); absl::BitGen bitgen; @@ -406,7 +429,7 @@ } TEST(ProtocolBufferWithRequiredFields, OptionalFieldInSubprotoIsEventuallySet) { - auto domain = Arbitrary<internal::TestProtobufWithRequired>() + auto domain = Arbitrary<TestProtobufWithRequired>() .WithRepeatedFieldsMaxSize(0) .WithProtobufFieldUnset("sub_req"); absl::BitGen bitgen; @@ -428,7 +451,7 @@ TEST(ProtocolBufferWithRequiredFields, OptionalFieldInSubprotoIsEventuallyUnset) { - auto domain = Arbitrary<internal::TestProtobufWithRequired>() + auto domain = Arbitrary<TestProtobufWithRequired>() .WithRepeatedFieldsMaxSize(0) .WithProtobufFieldUnset("sub_req"); absl::BitGen bitgen; @@ -459,10 +482,10 @@ TEST(ProtocolBufferWithRequiredFields, OptionalFieldWithRequiredFieldsIsEventuallySet) { auto domain = - Arbitrary<internal::TestProtobufWithRequired>() + Arbitrary<TestProtobufWithRequired>() .WithRepeatedFieldsMaxSize(0) .WithProtobufFields(IsTestProtobufWithRequired, - Arbitrary<internal::TestProtobufWithRequired>() + Arbitrary<TestProtobufWithRequired>() .WithRepeatedFieldsMaxSize(0) // Disallow recursive nesting beyond depth 1. .WithProtobufFieldUnset("sub_req")); @@ -486,10 +509,10 @@ TEST(ProtocolBufferWithRequiredFields, MapFieldIsEventuallyPopulated) { auto domain = - Arbitrary<internal::TestProtobufWithRequired>() + Arbitrary<TestProtobufWithRequired>() .WithRepeatedFieldsMaxSize(1) .WithProtobufFields(IsTestProtobufWithRequired, - Arbitrary<internal::TestProtobufWithRequired>() + Arbitrary<TestProtobufWithRequired>() .WithRepeatedFieldsMaxSize(0) // Disallow recursive nesting beyond depth 1. .WithProtobufFieldUnset("sub_req")); @@ -513,10 +536,10 @@ TEST(ProtocolBufferWithRequiredFields, ShrinkingNeverRemovesRequiredFields) { auto domain = - Arbitrary<internal::TestProtobufWithRequired>() + Arbitrary<TestProtobufWithRequired>() .WithRepeatedFieldsMaxSize(1) .WithProtobufFields(IsTestProtobufWithRequired, - Arbitrary<internal::TestProtobufWithRequired>() + Arbitrary<TestProtobufWithRequired>() .WithRepeatedFieldsMaxSize(0) // Disallow recursive nesting beyond depth 1. .WithProtobufFieldUnset("sub_req")); @@ -543,17 +566,16 @@ } TEST(ProtocolBuffer, CanUsePerFieldDomains) { - using internal::TestProtobuf; Domain<TestProtobuf> domain = - Arbitrary<internal::TestProtobuf>() + Arbitrary<TestProtobuf>() .WithInt32Field("i32", InRange(1, 4)) .WithStringField("str", PrintableAsciiString().WithSize(4)) .WithEnumField( "e", ElementOf<int>({TestProtobuf::Label2, TestProtobuf::Label4})) .WithRepeatedBoolField("rep_b", VectorOf(Just(true)).WithSize(2)) - .WithProtobufField( - "subproto", Arbitrary<internal::TestSubProtobuf>().WithInt32Field( - "subproto_i32", Just(-1))); + .WithProtobufField("subproto", + Arbitrary<TestSubProtobuf>().WithInt32Field( + "subproto_i32", Just(-1))); absl::BitGen bitgen; Value val(domain, bitgen); @@ -607,12 +629,12 @@ TEST(ProtocolBuffer, InvalidInputReportsError) { EXPECT_DEATH_IF_SUPPORTED( - Arbitrary<internal::TestProtobuf>().WithStringField( - "i32", Arbitrary<std::string>()), + Arbitrary<TestProtobuf>().WithStringField("i32", + Arbitrary<std::string>()), "Failed precondition.*" "does not match field `fuzztest.internal.TestProtobuf.i32`"); EXPECT_DEATH_IF_SUPPORTED( - Arbitrary<internal::TestProtobuf>() + Arbitrary<TestProtobuf>() .WithInt32Field("i32", Just(0)) .WithInt32Field("i32", Just(0)), "Failed precondition.*" @@ -620,9 +642,9 @@ } TEST(ProtocolBuffer, ValidationRejectsUnexpectedOptionalField) { - internal::TestSubProtobuf user_value; + TestSubProtobuf user_value; auto domain_with_optional_always_set = - Arbitrary<internal::TestSubProtobuf>().WithOptionalFieldsAlwaysSet(); + Arbitrary<TestSubProtobuf>().WithOptionalFieldsAlwaysSet(); auto corpus_value = domain_with_optional_always_set.FromValue(user_value); EXPECT_THAT( domain_with_optional_always_set.ValidateCorpusValue(*corpus_value), @@ -630,7 +652,7 @@ "be set")); auto domain_with_repeated_always_set = - Arbitrary<internal::TestSubProtobuf>().WithRepeatedFieldsAlwaysSet(); + Arbitrary<TestSubProtobuf>().WithRepeatedFieldsAlwaysSet(); EXPECT_THAT(domain_with_repeated_always_set.ValidateCorpusValue( *domain_with_optional_always_set.FromValue(user_value)), IsInvalid("Invalid value for field subproto_rep_i32 >> Invalid " @@ -638,22 +660,20 @@ } TEST(ProtocolBuffer, SerializeAndParseCanHandleExtensions) { - auto domain = Arbitrary<internal::TestProtobufWithExtension>(); - internal::TestProtobufWithExtension user_value; - user_value.SetExtension(internal::ProtoExtender::ext, "Hello?!?!"); + auto domain = Arbitrary<TestProtobufWithExtension>(); + TestProtobufWithExtension user_value; + user_value.SetExtension(ProtoExtender::ext, "Hello?!?!"); auto corpus_value = domain.FromValue(user_value); EXPECT_TRUE(corpus_value != std::nullopt); auto serialized = domain.SerializeCorpus(corpus_value.value()); auto parsed = domain.ParseCorpus(serialized); EXPECT_TRUE(parsed != std::nullopt); auto user_value_after_serialize_parse = domain.GetValue(parsed.value()); - EXPECT_EQ("Hello?!?!", user_value_after_serialize_parse.GetExtension( - internal::ProtoExtender::ext)); + EXPECT_EQ("Hello?!?!", + user_value_after_serialize_parse.GetExtension(ProtoExtender::ext)); } TEST(ProtocolBuffer, ValidationRejectsUnexpectedSingularField) { - using internal::TestProtobuf; - absl::BitGen bitgen; Domain<TestProtobuf> domain_a = @@ -675,9 +695,31 @@ IsInvalid("Invalid value for field i32 >> Optional value must be null")); } -TEST(ProtocolBuffer, ValidationRejectsUnexpectedRepeatedField) { - using internal::TestProtobuf; +TEST(ProtocolBuffer, ValidationRejectsUnexpectedSingularExtensionField) { + absl::BitGen bitgen; + Domain<TestProtobufWithExtension> domain_a = + Arbitrary<TestProtobufWithExtension>().WithFieldAlwaysSet( + "fuzztest.internal.ProtoExtender.ext"); + Domain<TestProtobufWithExtension> domain_b = + Arbitrary<TestProtobufWithExtension>().WithStringFieldUnset( + "fuzztest.internal.ProtoExtender.ext"); + + Value value_a(domain_a, bitgen); + Value value_b(domain_b, bitgen); + + ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value)); + ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value)); + + EXPECT_THAT(domain_a.ValidateCorpusValue(value_b.corpus_value), + IsInvalid(testing::MatchesRegex( + R"(.* field ext .* Optional value must be set)"))); + EXPECT_THAT(domain_b.ValidateCorpusValue(value_a.corpus_value), + IsInvalid(testing::MatchesRegex( + R"(.* field ext .* Optional value must be null)"))); +} + +TEST(ProtocolBuffer, ValidationRejectsUnexpectedRepeatedField) { absl::BitGen bitgen; Domain<TestProtobuf> domain_a = @@ -703,12 +745,36 @@ R"(Invalid value for field rep_i32 >> Invalid size: .+. Max size: 0)"))); } +TEST(ProtocolBuffer, ValidationRejectsUnexpectedRepeatedExtensionField) { + absl::BitGen bitgen; + + Domain<TestProtobufWithExtension> domain_a = + Arbitrary<TestProtobufWithExtension>().WithRepeatedFieldMinSize( + "fuzztest.internal.ProtoExtender.rep_ext", 1); + Domain<TestProtobufWithExtension> domain_b = + Arbitrary<TestProtobufWithExtension>().WithRepeatedFieldMaxSize( + "fuzztest.internal.ProtoExtender.rep_ext", 0); + + Value value_a(domain_a, bitgen); + Value value_b(domain_b, bitgen); + + ASSERT_OK(domain_a.ValidateCorpusValue(value_a.corpus_value)); + ASSERT_OK(domain_b.ValidateCorpusValue(value_b.corpus_value)); + + EXPECT_THAT(domain_a.ValidateCorpusValue(value_b.corpus_value), + IsInvalid(testing::MatchesRegex( + R"(.* field rep_ext .* Invalid size: 0. Min size: 1)"))); + EXPECT_THAT(domain_b.ValidateCorpusValue(value_a.corpus_value), + IsInvalid(testing::MatchesRegex( + R"(.* field rep_ext .* Invalid size: .+. Max size: 0)"))); +} + TEST(ProtocolBufferEnum, Arbitrary) { - auto domain = Arbitrary<internal::TestProtobuf_Enum>(); + auto domain = Arbitrary<TestProtobuf_Enum>(); absl::BitGen bitgen; Value val(domain, bitgen); - Set<internal::TestProtobuf_Enum> s; + Set<TestProtobuf_Enum> s; while (s.size() < internal::TestProtobuf_Enum_descriptor()->value_count()) { s.insert(val.user_value); val.Mutate(domain, bitgen, false); @@ -717,13 +783,12 @@ } TEST(ArbitraryProtocolBufferEnum, InitGeneratesSeeds) { - auto domain = Arbitrary<internal::TestProtobuf_Enum>().WithSeeds( - {internal::TestProtobuf_Enum::TestProtobuf_Enum_Label5}); + auto domain = Arbitrary<TestProtobuf_Enum>().WithSeeds( + {TestProtobuf_Enum::TestProtobuf_Enum_Label5}); EXPECT_THAT( GenerateInitialValues(domain, 1000), - Contains( - Value(domain, internal::TestProtobuf_Enum::TestProtobuf_Enum_Label5)) + Contains(Value(domain, TestProtobuf_Enum::TestProtobuf_Enum_Label5)) // Since there are only 5 enum elements, the seed will surely appear // at least once. To make the test meaningful, we expect to see it at // least half the time, unlike the other 4 elements. @@ -731,8 +796,8 @@ } TEST(ProtocolBuffer, CountNumberOfFieldsCorrect) { - using T = internal::TestProtobuf; - using SubT = internal::TestSubProtobuf; + using T = TestProtobuf; + using SubT = TestSubProtobuf; auto domain = Arbitrary<T>(); T v; auto corpus_v_uninitialized = domain.FromValue(v);
diff --git a/domain_tests/domain_testing.h b/domain_tests/domain_testing.h index 32006ff..8a51e1a 100644 --- a/domain_tests/domain_testing.h +++ b/domain_tests/domain_testing.h
@@ -289,6 +289,31 @@ } template <typename Domain> +auto GenerateNonUniqueValues(Domain domain, int num_seeds = 10, + int num_mutations = 100) { + absl::BitGen bitgen; + + std::vector<Value<Domain>> seeds; + while (seeds.size() < num_seeds) { + seeds.push_back(Value(domain, bitgen)); + } + + auto values = seeds; + + for (const auto& seed : seeds) { + auto value = seed; + std::vector<Value<Domain>> mutations = {value}; + while (mutations.size() < num_mutations) { + value.Mutate(domain, bitgen, false); + mutations.push_back(value); + } + values.insert(values.end(), mutations.begin(), mutations.end()); + } + + return values; +} + +template <typename Domain> auto GenerateInitialValues(Domain domain, int n) { std::vector<Value<Domain>> values; absl::BitGen bitgen;
diff --git a/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc b/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc index 15d4ee8..1286248 100644 --- a/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc +++ b/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc
@@ -433,6 +433,7 @@ .WithOptionalFieldsAlwaysSet() .WithOptionalFieldsUnset(IsInt32) .WithOneofAlwaysSet("type") + .WithFieldUnset("ext") .WithProtobufField( "child", Arbitrary<TestProtobufWithRecursion::ChildProto>()
diff --git a/fuzztest/internal/domains/protobuf_domain_impl.h b/fuzztest/internal/domains/protobuf_domain_impl.h index 1365114..a4cb422 100644 --- a/fuzztest/internal/domains/protobuf_domain_impl.h +++ b/fuzztest/internal/domains/protobuf_domain_impl.h
@@ -185,15 +185,6 @@ } } -template <typename Message> -auto GetProtobufField(const Message* prototype, int number) { - auto* field = prototype->GetDescriptor()->FindFieldByNumber(number); - if (field == nullptr) { - field = prototype->GetReflection()->FindKnownExtensionByNumber(number); - } - return field; -} - template <typename T> using Predicate = std::function<bool(const T*)>; @@ -513,8 +504,7 @@ absl::flat_hash_map<int, int> oneof_to_field; // TODO(b/241124202): Use a valid proto with minimum size. - for (int i = 0; i < descriptor->field_count(); ++i) { - const auto* field = descriptor->field(i); + for (const FieldDescriptor* field : GetProtobufFields(descriptor)) { if (auto* oneof = field->containing_oneof()) { if (!oneof_to_field.contains(oneof->index())) { oneof_to_field[oneof->index()] = SelectAFieldIndexInOneof( @@ -607,11 +597,10 @@ uint64_t CountNumberOfFields(const corpus_type& val) { uint64_t total_weight = 0; - auto* descriptor = prototype_.Get()->GetDescriptor(); - if (descriptor->field_count() == 0) return total_weight; + auto descriptor = prototype_.Get()->GetDescriptor(); + if (GetFieldCount(descriptor) == 0) return total_weight; - for (int i = 0; i < descriptor->field_count(); ++i) { - FieldDescriptor* field = descriptor->field(i); + for (const FieldDescriptor* field : GetProtobufFields(descriptor)) { if (field->containing_oneof() && GetOneofFieldPolicy(field) == OptionalPolicy::kAlwaysNull) { continue; @@ -639,11 +628,10 @@ bool only_shrink, uint64_t selected_field_index) { uint64_t field_counter = 0; - auto* descriptor = prototype_.Get()->GetDescriptor(); - if (descriptor->field_count() == 0) return field_counter; + auto descriptor = prototype_.Get()->GetDescriptor(); + if (GetFieldCount(descriptor) == 0) return field_counter; - for (int i = 0; i < descriptor->field_count(); ++i) { - FieldDescriptor* field = descriptor->field(i); + for (const FieldDescriptor* field : GetProtobufFields(descriptor)) { if (field->containing_oneof() && GetOneofFieldPolicy(field) == OptionalPolicy::kAlwaysNull) { continue; @@ -675,8 +663,7 @@ } void Mutate(corpus_type& val, absl::BitGenRef prng, bool only_shrink) { - auto* descriptor = prototype_.Get()->GetDescriptor(); - if (descriptor->field_count() == 0) return; + if (GetFieldCount(prototype_.Get()->GetDescriptor()) == 0) return; // TODO(JunyangShao): Maybe make CountNumberOfFields static. uint64_t total_weight = CountNumberOfFields(val); uint64_t selected_weight = absl::Uniform(absl::IntervalClosedClosed, prng, @@ -696,7 +683,7 @@ if (!value.has_value()) { FUZZTEST_INTERNAL_CHECK_PRECONDITION( !field->is_required(), "required field '", - std::string(field->name()), "' cannot have null values."); + std::string(field->full_name()), "' cannot have null values."); message.GetReflection()->ClearField(&message, field); return; } @@ -734,7 +721,7 @@ value_type out(prototype_.Get()->New()); for (auto& [number, data] : value) { - auto* field = GetProtobufField(prototype_.Get(), number); + auto* field = GetField(number); VisitProtobufField(field, GetValueVisitor{*out, *this, data}); } @@ -840,7 +827,7 @@ if (!pair_subs || pair_subs->size() != 2) return std::nullopt; auto number = (*pair_subs)[0].GetScalar<int>(); if (!number) return std::nullopt; - auto* field = GetProtobufField(prototype_.Get(), *number); + auto* field = GetField(*number); if (!field) return std::nullopt; present_fields.insert(field->number()); std::optional<GenericDomainCorpusType> inner_parsed; @@ -849,11 +836,8 @@ if (!inner_parsed) return std::nullopt; out[*number] = *std::move(inner_parsed); } - for (int field_index = 0; - field_index < prototype_.Get()->GetDescriptor()->field_count(); - ++field_index) { - const FieldDescriptor* field = - prototype_.Get()->GetDescriptor()->field(field_index); + for (const FieldDescriptor* field : + GetProtobufFields(prototype_.Get()->GetDescriptor())) { if (present_fields.contains(field->number())) continue; std::optional<GenericDomainCorpusType> inner_parsed; IRObject unset_value; @@ -887,7 +871,7 @@ IRObject out; auto& subs = out.MutableSubs(); for (auto& [number, inner] : v) { - auto* field = GetProtobufField(prototype_.Get(), number); + auto* field = GetField(number); FUZZTEST_INTERNAL_CHECK(field, "Field not found by number: ", number); IRObject& pair = subs.emplace_back(); auto& pair_subs = pair.MutableSubs(); @@ -945,11 +929,8 @@ }; absl::Status ValidateCorpusValue(const corpus_type& corpus_value) const { - for (int field_index = 0; - field_index < prototype_.Get()->GetDescriptor()->field_count(); - ++field_index) { - const FieldDescriptor* field = - prototype_.Get()->GetDescriptor()->field(field_index); + for (const FieldDescriptor* field : + GetProtobufFields(prototype_.Get()->GetDescriptor())) { auto field_number_value = corpus_value.find(field->number()); auto inner_corpus_value = (field_number_value != corpus_value.end()) ? std::optional(field_number_value->second) @@ -1040,15 +1021,48 @@ customized_fields_.insert(field->index()); } - const FieldDescriptor* GetField(absl::string_view field_name) const { + auto GetField(absl::string_view field_name) const { auto* field = prototype_.Get()->GetDescriptor()->FindFieldByName( std::string(field_name)); + if (field == nullptr) { + field = prototype_.Get()->GetReflection()->FindKnownExtensionByName( + std::string(field_name)); + } FUZZTEST_INTERNAL_CHECK_PRECONDITION(field != nullptr, "Invalid field name '", std::string(field_name), "'."); return field; } + auto GetField(int number) const { + auto* field = prototype_.Get()->GetDescriptor()->FindFieldByNumber(number); + if (field == nullptr) { + field = + prototype_.Get()->GetReflection()->FindKnownExtensionByNumber(number); + } + return field; + } + + static auto GetFieldCount(const Descriptor* descriptor) { + std::vector<const FieldDescriptor*> extensions; + descriptor->file()->pool()->FindAllExtensions(descriptor, &extensions); + return descriptor->field_count() + extensions.size(); + } + + static auto GetProtobufFields(const Descriptor* descriptor) { + std::vector<const FieldDescriptor*> fields; + fields.reserve(descriptor->field_count()); + for (int i = 0; i < descriptor->field_count(); ++i) { + fields.push_back(descriptor->field(i)); + } + descriptor->file()->pool()->FindAllExtensions(descriptor, &fields); + return fields; + } + + static auto GetFieldName(const FieldDescriptor* field) { + return field->is_extension() ? field->full_name() : field->name(); + } + void WithOneofField(absl::string_view field_name, OptionalPolicy policy) { const FieldDescriptor* field = GetField(field_name); if (!field->containing_oneof()) return; @@ -1107,7 +1121,7 @@ } else if (policy == OptionalPolicy::kWithoutNull) { domain.SetWithoutNull(); } - self.WithField(field->name(), domain); + self.WithField(self.GetFieldName(field), domain); } template <typename T> @@ -1121,7 +1135,7 @@ } else if (policy == OptionalPolicy::kWithoutNull) { domain.WithMinSize(1); } - self.WithField(field->name(), domain); + self.WithField(self.GetFieldName(field), domain); } }; @@ -1142,7 +1156,7 @@ false, "Customizing repeated field size is not applicable to non-repeated " "field ", - field->name(), "."); + field->full_name(), "."); } template <typename T> @@ -1157,7 +1171,7 @@ if (max_size.has_value()) { domain.WithMaxSize(*max_size); } - self.WithField(field->name(), domain); + self.WithField(self.GetFieldName(field), domain); } }; @@ -1211,7 +1225,7 @@ FUZZTEST_INTERNAL_CHECK( field->containing_oneof(), "GetOneofFieldPolicy should apply to oneof fields only! ", - field->name()); + field->full_name()); auto result = oneof_fields_policies_.find(field->index()); if (result != oneof_fields_policies_.end()) { return result->second; @@ -1425,8 +1439,7 @@ return true; } } - for (int i = 0; i < descriptor->field_count(); ++i) { - const auto* field = descriptor->field(i); + for (const FieldDescriptor* field : GetProtobufFields(descriptor)) { if (field->containing_oneof()) continue; const auto* child = field->message_type(); if (!child) continue;
diff --git a/fuzztest/internal/test_protobuf.proto b/fuzztest/internal/test_protobuf.proto index c279036..4430140 100644 --- a/fuzztest/internal/test_protobuf.proto +++ b/fuzztest/internal/test_protobuf.proto
@@ -101,6 +101,13 @@ message ProtoExtender { extend TestProtobufWithExtension { optional string ext = 1001; + repeated string rep_ext = 1002; + } +} + +message RecursiveExtender { + extend TestProtobufWithExtension { + optional TestProtobufWithRecursion parent = 2001; } } @@ -116,4 +123,5 @@ ChildProto child = 2; int32 child_id = 3; } + optional TestProtobufWithExtension ext = 4; }