diff --git a/domain_tests/BUILD b/domain_tests/BUILD index 623ca0fb..e65d621e 100644 --- a/domain_tests/BUILD +++ b/domain_tests/BUILD @@ -73,6 +73,7 @@ cc_test( "@abseil-cpp//absl/time", "@com_google_fuzztest//fuzztest:domain", "@com_google_fuzztest//fuzztest/internal:test_protobuf_cc_proto", + "@com_google_fuzztest//fuzztest/internal/domains:core_domains_impl", "@googletest//:gtest_main", "@protobuf", ], diff --git a/domain_tests/arbitrary_domains_protobuf_test.cc b/domain_tests/arbitrary_domains_protobuf_test.cc index 3028ac84..affb9130 100644 --- a/domain_tests/arbitrary_domains_protobuf_test.cc +++ b/domain_tests/arbitrary_domains_protobuf_test.cc @@ -36,6 +36,7 @@ #include "absl/time/time.h" #include "./fuzztest/domain.h" // IWYU pragma: keep #include "./domain_tests/domain_testing.h" +#include "./fuzztest/internal/domains/container_of_impl.h" #include "./fuzztest/internal/test_protobuf.pb.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/message.h" @@ -782,5 +783,46 @@ TEST(ProtocolBuffer, MutationInParallelIsEfficient) { std::cout << "ratio: " << multi_thread_time / single_thread_time << "\n"; } +TEST(ProtobufDomainTest, + WithRepeatedFieldsMaxSizeIgnoredMutationRespectsMaxSize) { + auto domain = Arbitrary().WithRepeatedFieldsMaxSizeIgnored(); + TestProtobuf message; + for (int i = 0; i < internal::kDefaultContainerMaxSize + 1; ++i) { + message.add_rep_i32(i); + } + auto corpus = domain.FromValue(message); + ASSERT_TRUE(corpus.has_value()); + EXPECT_OK(domain.ValidateCorpusValue(*corpus)); + + absl::BitGen bitgen; + for (int i = 0; i < 1000; ++i) { + domain.Mutate(*corpus, bitgen, {}, /*only_shrink=*/false); + auto mutated_message = domain.GetValue(*corpus); + EXPECT_LE(mutated_message.rep_i32_size(), + internal::kDefaultContainerMaxSize + 1); + } +} + +TEST(ProtobufDomainTest, + WithRepeatedFieldsMaxSizeIgnoredMutationRespectsMaxSizeForNested) { + auto domain = Arbitrary().WithRepeatedFieldsMaxSizeIgnored(); + TestProtobuf message; + auto* subproto = message.add_rep_subproto(); + for (int i = 0; i < internal::kDefaultContainerMaxSize + 1; ++i) { + subproto->add_subproto_rep_i32(i); + } + auto corpus = domain.FromValue(message); + ASSERT_TRUE(corpus.has_value()); + absl::BitGen bitgen; + for (int i = 0; i < 1000; ++i) { + domain.Mutate(*corpus, bitgen, {}, /*only_shrink=*/false); + auto mutated_message = domain.GetValue(*corpus); + if (mutated_message.rep_subproto_size() > 0) { + EXPECT_LE(mutated_message.rep_subproto(0).subproto_rep_i32_size(), + internal::kDefaultContainerMaxSize + 1); + } + } +} + } // namespace } // namespace fuzztest diff --git a/fuzztest/internal/domains/container_of_impl.h b/fuzztest/internal/domains/container_of_impl.h index d4202b81..102e025b 100644 --- a/fuzztest/internal/domains/container_of_impl.h +++ b/fuzztest/internal/domains/container_of_impl.h @@ -114,9 +114,15 @@ class ContainerOfImplBase const domain_implementor::MutationMetadata& metadata, bool only_shrink) { permanent_dict_candidate_ = std::nullopt; - FUZZTEST_CHECK(min_size() <= val.size() && val.size() <= max_size()) - << "Size " << val.size() << " is not between " << min_size() << " and " - << max_size(); + if (skip_max_size_validation()) { + FUZZTEST_CHECK(min_size() <= val.size()) + << "Size " << val.size() << " is smaller than min size " + << min_size(); + } else { + FUZZTEST_CHECK(min_size() <= val.size() && val.size() <= max_size()) + << "Size " << val.size() << " is not between " << min_size() + << " and " << max_size(); + } const bool can_shrink = val.size() > min_size(); const bool can_grow = !only_shrink && val.size() < max_size(); @@ -250,6 +256,10 @@ class ContainerOfImplBase manual_dict_provider_ = std::move(manual_dict_provider); return Self(); } + Derived& SkipMaxSizeValidation() { + skip_max_size_validation_ = true; + return Self(); + } auto GetPrinter() const { if constexpr (std::is_same_v || @@ -327,7 +337,7 @@ class ContainerOfImplBase return absl::InvalidArgumentError(absl::StrCat( "Invalid size: ", corpus_value.size(), ". Min size: ", min_size())); } - if (corpus_value.size() > max_size()) { + if (!skip_max_size_validation() && corpus_value.size() > max_size()) { return absl::InvalidArgumentError(absl::StrCat( "Invalid size: ", corpus_value.size(), ". Max size: ", max_size())); } @@ -355,6 +365,7 @@ class ContainerOfImplBase OtherInnerDomain>& other) { min_size_ = other.min_size_; max_size_ = other.max_size_; + skip_max_size_validation_ = other.skip_max_size_validation_; } protected: @@ -379,6 +390,7 @@ class ContainerOfImplBase size_t max_size() const { return max_size_.value_or(std::max(min_size_, kDefaultContainerMaxSize)); } + bool skip_max_size_validation() const { return skip_max_size_validation_; } private: Derived& Self() { return static_cast(*this); } @@ -395,6 +407,7 @@ class ContainerOfImplBase // DO NOT use directly. Use min_size() and max_size() instead. size_t min_size_ = 0; std::optional max_size_ = std::nullopt; + bool skip_max_size_validation_ = false; // Temporary memory dictionary. Collected from tracing the program // execution. It will always be empty if no execution_coverage_ is found, diff --git a/fuzztest/internal/domains/protobuf_domain_impl.h b/fuzztest/internal/domains/protobuf_domain_impl.h index 789ff79a..326ae0a7 100644 --- a/fuzztest/internal/domains/protobuf_domain_impl.h +++ b/fuzztest/internal/domains/protobuf_domain_impl.h @@ -283,6 +283,11 @@ class ProtoPolicy { {/*filter=*/std::move(filter), /*value=*/max_size}); } + void SetSkipMaxSizeValidationForRepeatedFields(Filter filter, bool skip) { + skip_max_size_validation_for_repeated_fields_.push_back( + {/*filter=*/std::move(filter), /*value=*/skip}); + } + OptionalPolicy GetOptionalPolicy(const FieldDescriptor* field) const { FUZZTEST_CHECK(!field->is_required() && !field->is_repeated()) << "GetOptionalPolicy should apply to optional fields only!"; @@ -318,6 +323,15 @@ class ProtoPolicy { return max; } + bool GetSkipMaxSizeValidationForRepeatedFields( + const FieldDescriptor* field) const { + FUZZTEST_CHECK(field->is_repeated()) + << "GetSkipMaxSizeValidationForRepeatedFields should apply to repeated " + "fields only!"; + return GetPolicyValue(skip_max_size_validation_for_repeated_fields_, field) + .value_or(false); + } + std::optional IsFieldFinitelyRecursive(const FieldDescriptor* field) { return caches_->IsFieldFinitelyRecursive(field); } @@ -490,6 +504,8 @@ class ProtoPolicy { std::vector> optional_policies_; std::vector> min_repeated_fields_sizes_; std::vector> max_repeated_fields_sizes_; + std::vector> + skip_max_size_validation_for_repeated_fields_; #define FUZZTEST_INTERNAL_POLICY_MEMBERS(Camel, cpp) \ private: \ @@ -918,6 +934,13 @@ class ProtobufDomainUntypedImpl return std::move(*this); } + ProtobufDomainUntypedImpl&& WithRepeatedFieldsMaxSizeIgnored( + std::function filter = + IncludeAll()) && { + policy_.SetSkipMaxSizeValidationForRepeatedFields(std::move(filter), true); + return std::move(*this); + } + #define FUZZTEST_INTERNAL_WITH_FIELD(Camel, cpp, TAG) \ using Camel##type = MakeDependentType; \ ProtobufDomainUntypedImpl&& With##Camel##Fields( \ @@ -1626,7 +1649,9 @@ class ProtobufDomainUntypedImpl return ModifyDomainForRepeatedFieldRule( std::move(domain), use_policy ? policy_.GetMinRepeatedFieldSize(field) : std::nullopt, - use_policy ? policy_.GetMaxRepeatedFieldSize(field) : std::nullopt); + use_policy ? policy_.GetMaxRepeatedFieldSize(field) : std::nullopt, + use_policy ? policy_.GetSkipMaxSizeValidationForRepeatedFields(field) + : false); } else if (IsRequired(field)) { return ModifyDomainForRequiredFieldRule(std::move(domain)); } else { @@ -1687,9 +1712,10 @@ class ProtobufDomainUntypedImpl // Simple wrapper that converts a Domain into a Domain>. template - static auto ModifyDomainForRepeatedFieldRule( - const Domain& d, std::optional min_size, - std::optional max_size) { + static auto ModifyDomainForRepeatedFieldRule(const Domain& d, + std::optional min_size, + std::optional max_size, + bool skip_max_size_validation) { auto result = ContainerOfImpl, Domain>(d); if (min_size.has_value()) { result.WithMinSize(*min_size); @@ -1697,6 +1723,9 @@ class ProtobufDomainUntypedImpl if (max_size.has_value()) { result.WithMaxSize(*max_size); } + if (skip_max_size_validation) { + result.SkipMaxSizeValidation(); + } return result; } @@ -2159,6 +2188,14 @@ class ProtobufDomainImpl return std::move(*this); } + ProtobufDomainImpl&& WithRepeatedFieldsMaxSizeIgnored( + std::function filter = + IncludeAll()) && { + inner_.GetPolicy().SetSkipMaxSizeValidationForRepeatedFields( + std::move(filter), true); + return std::move(*this); + } + ProtobufDomainImpl&& WithFieldUnset(absl::string_view field) && { inner_.WithFieldNullness(field, OptionalPolicy::kAlwaysNull); return std::move(*this);