aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-02-08 21:39:52 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-02-08 21:39:52 +0000
commit829b4113d970aeec2fcb6acecd3631624c48acd4 (patch)
treeda62a9021e5c1b332c9de5180c71ec72187dfb71
parent38b5d2e9832fd7e0a8128a34b7eac197478ab1a9 (diff)
parente99bc4344f69cd73f5cef63598291710ac10c42c (diff)
downloadlibprotobuf-mutator-android11-mainline-media-swcodec-release.tar.gz
Upgrade libprotobuf-mutator to 3521f47a2828da9ace403e4ecc4aece1a84feb36 am: 9857127c52 am: 42dad0bfc8 am: e99bc4344fr_aml_301500702android-mainline-12.0.0_r55android-mainline-11.0.0_r9android-mainline-11.0.0_r8android-mainline-11.0.0_r7android-mainline-11.0.0_r6android-mainline-11.0.0_r5android-mainline-11.0.0_r45android-mainline-11.0.0_r44android-mainline-11.0.0_r43android-mainline-11.0.0_r42android-mainline-11.0.0_r41android-mainline-11.0.0_r40android-mainline-11.0.0_r4android-mainline-11.0.0_r39android-mainline-11.0.0_r38android-mainline-11.0.0_r37android-mainline-11.0.0_r36android-mainline-11.0.0_r35android-mainline-11.0.0_r34android-mainline-11.0.0_r33android-mainline-11.0.0_r32android-mainline-11.0.0_r31android-mainline-11.0.0_r30android-mainline-11.0.0_r3android-mainline-11.0.0_r29android-mainline-11.0.0_r28android-mainline-11.0.0_r27android-mainline-11.0.0_r26android-mainline-11.0.0_r25android-mainline-11.0.0_r24android-mainline-11.0.0_r23android-mainline-11.0.0_r22android-mainline-11.0.0_r21android-mainline-11.0.0_r20android-mainline-11.0.0_r2android-mainline-11.0.0_r19android-mainline-11.0.0_r18android-mainline-11.0.0_r17android-mainline-11.0.0_r16android-mainline-11.0.0_r15android-mainline-11.0.0_r14android-mainline-11.0.0_r13android-mainline-11.0.0_r12android-mainline-11.0.0_r10android-mainline-11.0.0_r1android-11.0.0_r48android-11.0.0_r47android-11.0.0_r46android-11.0.0_r45android-11.0.0_r44android-11.0.0_r43android-11.0.0_r42android-11.0.0_r41android-11.0.0_r40android-11.0.0_r39android-11.0.0_r38android-11.0.0_r37android-11.0.0_r36android-11.0.0_r35android-11.0.0_r34android-11.0.0_r33android-11.0.0_r32android-11.0.0_r31android-11.0.0_r30android-11.0.0_r29android-11.0.0_r28android-11.0.0_r27android-11.0.0_r26android-11.0.0_r24android-11.0.0_r23android-11.0.0_r22android-11.0.0_r21android-11.0.0_r20android-11.0.0_r19android-11.0.0_r18android-11.0.0_r16android11-qpr3-s1-releaseandroid11-qpr3-releaseandroid11-qpr2-releaseandroid11-qpr1-s2-releaseandroid11-qpr1-s1-releaseandroid11-qpr1-releaseandroid11-qpr1-d-s1-releaseandroid11-qpr1-d-releaseandroid11-qpr1-c-releaseandroid11-mainline-tethering-releaseandroid11-mainline-sparse-2021-jan-releaseandroid11-mainline-sparse-2020-dec-releaseandroid11-mainline-releaseandroid11-mainline-permission-releaseandroid11-mainline-os-statsd-releaseandroid11-mainline-networkstack-releaseandroid11-mainline-media-swcodec-releaseandroid11-mainline-media-releaseandroid11-mainline-extservices-releaseandroid11-mainline-documentsui-releaseandroid11-mainline-conscrypt-releaseandroid11-mainline-cellbroadcast-releaseandroid11-mainline-captiveportallogin-releaseandroid11-devandroid11-d2-releaseandroid11-d1-b-release
Change-Id: I5993d4a2d3af9c145f6b8d9b728dae815fd274eb
-rw-r--r--METADATA6
-rw-r--r--README.md30
-rw-r--r--examples/libfuzzer/libfuzzer_bin_example.cc23
-rw-r--r--examples/libfuzzer/libfuzzer_example.cc23
-rw-r--r--examples/libfuzzer/libfuzzer_example.proto3
-rw-r--r--examples/libfuzzer/libfuzzer_example_test.cc4
-rw-r--r--port/protobuf.h1
-rw-r--r--src/libfuzzer/libfuzzer_macro.cc17
-rw-r--r--src/libfuzzer/libfuzzer_mutator.cc6
-rw-r--r--src/libfuzzer/libfuzzer_mutator.h2
-rw-r--r--src/mutator.cc609
-rw-r--r--src/mutator.h32
-rw-r--r--src/mutator_test.cc377
-rw-r--r--src/mutator_test_proto2.proto8
-rw-r--r--src/mutator_test_proto3.proto11
15 files changed, 691 insertions, 461 deletions
diff --git a/METADATA b/METADATA
index 26788ce..358d07c 100644
--- a/METADATA
+++ b/METADATA
@@ -5,11 +5,11 @@ third_party {
type: GIT
value: "https://github.com/google/libprotobuf-mutator"
}
- version: "69d93082200bb657f6db9c6b07b71bb94ea7bb47"
+ version: "3521f47a2828da9ace403e4ecc4aece1a84feb36"
license_type: NOTICE
last_upgrade_date {
year: 2020
- month: 1
- day: 8
+ month: 2
+ day: 7
}
}
diff --git a/README.md b/README.md
index 842214e..4e16fb6 100644
--- a/README.md
+++ b/README.md
@@ -62,11 +62,11 @@ To apply one mutation to a protobuf object do the following:
```
class MyProtobufMutator : public protobuf_mutator::Mutator {
public:
- MyProtobufMutator(uint32_t seed) : protobuf_mutator::Mutator(seed) {}
// Optionally redefine the Mutate* methods to perform more sophisticated mutations.
}
void Mutate(MyMessage* message) {
- MyProtobufMutator mutator(my_random_seed);
+ MyProtobufMutator mutator;
+ mutator.Seed(my_random_seed);
mutator.Mutate(message, 200);
}
```
@@ -94,23 +94,43 @@ is going to be rejected by fuzzed code. E.g. code may expect consistency between
or it may use some fields as checksums. Such constraints are going to be significant bottleneck
for fuzzer even if it's capable of inserting acceptable values with time.
-PostProcessorRegistration can be used to avoid such issue and guide your fuzzer towards interesing
+PostProcessorRegistration can be used to avoid such issue and guide your fuzzer towards interesting
code. It registers callback which will be called for each message of particular type after each mutation.
```
DEFINE_PROTO_FUZZER(const MyMessageType& input) {
static PostProcessorRegistration reg = {
[](MyMessageType* message, unsigned int seed) {
- TweakMyMessageType(message, seed);
+ TweakMyMessage(message, seed);
}};
// Code which needs to be fuzzed.
ConsumeMyMessageType(input);
}
```
-
Optional: Use seed if callback uses random numbers. It may help later with debugging.
+Note: You can add callback for any nested message and you can add multiple callbacks for
+the same message type.
+```
+DEFINE_PROTO_FUZZER(const MyMessageType& input) {
+ static PostProcessorRegistration reg1 = {
+ [](MyMessageType* message, unsigned int seed) {
+ TweakMyMessage(message, seed);
+ }};
+ static PostProcessorRegistration reg2 = {
+ [](MyMessageType* message, unsigned int seed) {
+ DifferentTweakMyMessage(message, seed);
+ }};
+ static PostProcessorRegistration reg_nested = {
+ [](MyMessageType::Nested* message, unsigned int seed) {
+ TweakMyNestedMessage(message, seed);
+ }};
+
+ // Code which needs to be fuzzed.
+ ConsumeMyMessageType(input);
+}
+```
## UTF-8 strings
"proto2" and "proto3" handle invalid UTF-8 strings differently. In both cases
string should be UTF-8, however only "proto3" enforces that. So if fuzzer is
diff --git a/examples/libfuzzer/libfuzzer_bin_example.cc b/examples/libfuzzer/libfuzzer_bin_example.cc
index dae46da..560bcb3 100644
--- a/examples/libfuzzer/libfuzzer_bin_example.cc
+++ b/examples/libfuzzer/libfuzzer_bin_example.cc
@@ -15,6 +15,7 @@
#include <cmath>
#include "examples/libfuzzer/libfuzzer_example.pb.h"
+#include "port/protobuf.h"
#include "src/libfuzzer/libfuzzer_macro.h"
protobuf_mutator::protobuf::LogSilencer log_silincer;
@@ -26,15 +27,35 @@ DEFINE_BINARY_PROTO_FUZZER(const libfuzzer_example::Msg& message) {
message->set_optional_uint64(
std::hash<std::string>{}(message->optional_string()));
}
+
+ if (message->has_any()) {
+ auto* any = message->mutable_any();
+
+ // Guide mutator to usefull 'Any' types.
+ static const char* const expected_types[] = {
+ "type.googleapis.com/google.protobuf.DescriptorProto",
+ "type.googleapis.com/google.protobuf.FileDescriptorProto",
+ };
+
+ if (!std::count(std::begin(expected_types), std::end(expected_types),
+ any->type_url())) {
+ const size_t num =
+ (std::end(expected_types) - std::begin(expected_types));
+ any->set_type_url(expected_types[seed % num]);
+ }
+ }
}};
+ protobuf_mutator::protobuf::FileDescriptorProto file;
+
// Emulate a bug.
if (message.optional_uint64() ==
std::hash<std::string>{}(message.optional_string()) &&
message.optional_string() == "abcdefghijklmnopqrstuvwxyz" &&
!std::isnan(message.optional_float()) &&
std::fabs(message.optional_float()) > 1000 &&
- std::fabs(message.optional_float()) < 1E10) {
+ message.any().UnpackTo(&file) && !file.name().empty()) {
+ std::cerr << message.DebugString() << "\n";
abort();
}
}
diff --git a/examples/libfuzzer/libfuzzer_example.cc b/examples/libfuzzer/libfuzzer_example.cc
index 1520840..6eab362 100644
--- a/examples/libfuzzer/libfuzzer_example.cc
+++ b/examples/libfuzzer/libfuzzer_example.cc
@@ -15,6 +15,7 @@
#include <cmath>
#include "examples/libfuzzer/libfuzzer_example.pb.h"
+#include "port/protobuf.h"
#include "src/libfuzzer/libfuzzer_macro.h"
protobuf_mutator::protobuf::LogSilencer log_silincer;
@@ -26,15 +27,35 @@ DEFINE_PROTO_FUZZER(const libfuzzer_example::Msg& message) {
message->set_optional_uint64(
std::hash<std::string>{}(message->optional_string()));
}
+
+ if (message->has_any()) {
+ auto* any = message->mutable_any();
+
+ // Guide mutator to usefull 'Any' types.
+ static const char* const expected_types[] = {
+ "type.googleapis.com/google.protobuf.DescriptorProto",
+ "type.googleapis.com/google.protobuf.FileDescriptorProto",
+ };
+
+ if (!std::count(std::begin(expected_types), std::end(expected_types),
+ any->type_url())) {
+ const size_t num =
+ (std::end(expected_types) - std::begin(expected_types));
+ any->set_type_url(expected_types[seed % num]);
+ }
+ }
}};
+ protobuf_mutator::protobuf::FileDescriptorProto file;
+
// Emulate a bug.
if (message.optional_uint64() ==
std::hash<std::string>{}(message.optional_string()) &&
message.optional_string() == "abcdefghijklmnopqrstuvwxyz" &&
!std::isnan(message.optional_float()) &&
std::fabs(message.optional_float()) > 1000 &&
- std::fabs(message.optional_float()) < 1E10) {
+ message.any().UnpackTo(&file) && !file.name().empty()) {
+ std::cerr << message.DebugString() << "\n";
abort();
}
}
diff --git a/examples/libfuzzer/libfuzzer_example.proto b/examples/libfuzzer/libfuzzer_example.proto
index 71e5fc9..cb25c9d 100644
--- a/examples/libfuzzer/libfuzzer_example.proto
+++ b/examples/libfuzzer/libfuzzer_example.proto
@@ -1,8 +1,11 @@
syntax = "proto2";
package libfuzzer_example;
+import "google/protobuf/any.proto";
+
message Msg {
optional float optional_float = 1;
optional uint64 optional_uint64 = 2;
optional string optional_string = 3;
+ optional google.protobuf.Any any = 4;
}
diff --git a/examples/libfuzzer/libfuzzer_example_test.cc b/examples/libfuzzer/libfuzzer_example_test.cc
index e871d59..2c6fd8c 100644
--- a/examples/libfuzzer/libfuzzer_example_test.cc
+++ b/examples/libfuzzer/libfuzzer_example_test.cc
@@ -25,12 +25,12 @@ int GetError(int exit_code) { return WSTOPSIG(exit_code); }
TEST_F(LibFuzzerExampleTest, Text) {
EXPECT_EQ(kDefaultLibFuzzerError,
- GetError(RunFuzzer("libfuzzer_example", 150, 10000000)));
+ GetError(RunFuzzer("libfuzzer_example", 1000, 10000000)));
}
TEST_F(LibFuzzerExampleTest, Binary) {
EXPECT_EQ(kDefaultLibFuzzerError,
- GetError(RunFuzzer("libfuzzer_bin_example", 150, 10000000)));
+ GetError(RunFuzzer("libfuzzer_bin_example", 1000, 10000000)));
}
} // namespace
diff --git a/port/protobuf.h b/port/protobuf.h
index e70d42d..04e0620 100644
--- a/port/protobuf.h
+++ b/port/protobuf.h
@@ -17,6 +17,7 @@
#include <string>
+#include "google/protobuf/any.pb.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/message.h"
#include "google/protobuf/text_format.h"
diff --git a/src/libfuzzer/libfuzzer_macro.cc b/src/libfuzzer/libfuzzer_macro.cc
index c37276d..b2a5302 100644
--- a/src/libfuzzer/libfuzzer_macro.cc
+++ b/src/libfuzzer/libfuzzer_macro.cc
@@ -14,6 +14,8 @@
#include "src/libfuzzer/libfuzzer_macro.h"
+#include <algorithm>
+
#include "src/binary_format.h"
#include "src/libfuzzer/libfuzzer_mutator.h"
#include "src/text_format.h"
@@ -94,13 +96,19 @@ Mutator* GetMutator() {
return &mutator;
}
+size_t GetMaxSize(const InputReader& input, const OutputWriter& output,
+ const protobuf::Message& message) {
+ size_t max_size = message.ByteSizeLong() + output.size();
+ max_size -= std::min(max_size, input.size());
+ return max_size;
+}
+
size_t MutateMessage(unsigned int seed, const InputReader& input,
OutputWriter* output, protobuf::Message* message) {
GetMutator()->Seed(seed);
input.Read(message);
- GetMutator()->Mutate(message, output->size() > input.size()
- ? (output->size() - input.size())
- : 0);
+ size_t max_size = GetMaxSize(input, *output, *message);
+ GetMutator()->Mutate(message, max_size);
if (size_t new_size = output->Write(*message)) {
assert(new_size <= output->size());
return new_size;
@@ -115,7 +123,8 @@ size_t CrossOverMessages(unsigned int seed, const InputReader& input1,
GetMutator()->Seed(seed);
input1.Read(message1);
input2.Read(message2);
- GetMutator()->CrossOver(*message2, message1);
+ size_t max_size = GetMaxSize(input1, *output, *message1);
+ GetMutator()->CrossOver(*message2, message1, max_size);
if (size_t new_size = output->Write(*message1)) {
assert(new_size <= output->size());
return new_size;
diff --git a/src/libfuzzer/libfuzzer_mutator.cc b/src/libfuzzer/libfuzzer_mutator.cc
index 979cebf..c8bca64 100644
--- a/src/libfuzzer/libfuzzer_mutator.cc
+++ b/src/libfuzzer/libfuzzer_mutator.cc
@@ -16,6 +16,7 @@
#include <string.h>
+#include <algorithm>
#include <cassert>
#include <memory>
#include <string>
@@ -82,13 +83,14 @@ float Mutator::MutateFloat(float value) { return MutateValue(value); }
double Mutator::MutateDouble(double value) { return MutateValue(value); }
std::string Mutator::MutateString(const std::string& value,
- size_t size_increase_hint) {
+ int size_increase_hint) {
// Randomly return empty strings as LLVMFuzzerMutate does not produce them.
// Use uint16_t because on Windows, uniform_int_distribution does not support
// any 8 bit types.
if (!std::uniform_int_distribution<uint16_t>(0, 20)(*random())) return {};
std::string result = value;
- result.resize(value.size() + size_increase_hint);
+ result.resize(value.size() +
+ std::max<int>(-value.size(), size_increase_hint));
if (result.empty()) result.push_back(0);
result.resize(LLVMFuzzerMutate(reinterpret_cast<uint8_t*>(&result[0]),
value.size(), result.size()));
diff --git a/src/libfuzzer/libfuzzer_mutator.h b/src/libfuzzer/libfuzzer_mutator.h
index 45ea908..04d6604 100644
--- a/src/libfuzzer/libfuzzer_mutator.h
+++ b/src/libfuzzer/libfuzzer_mutator.h
@@ -37,7 +37,7 @@ class Mutator : public protobuf_mutator::Mutator {
float MutateFloat(float value) override;
double MutateDouble(double value) override;
std::string MutateString(const std::string& value,
- size_t size_increase_hint) override;
+ int size_increase_hint) override;
};
} // namespace libfuzzer
diff --git a/src/mutator.cc b/src/mutator.cc
index e4e0305..45acd48 100644
--- a/src/mutator.cc
+++ b/src/mutator.cc
@@ -15,9 +15,12 @@
#include "src/mutator.h"
#include <algorithm>
+#include <bitset>
#include <map>
+#include <memory>
#include <random>
#include <string>
+#include <utility>
#include <vector>
#include "src/field_instance.h"
@@ -26,6 +29,7 @@
namespace protobuf_mutator {
+using google::protobuf::Any;
using protobuf::Descriptor;
using protobuf::FieldDescriptor;
using protobuf::FileDescriptor;
@@ -40,17 +44,22 @@ namespace {
const int kMaxInitializeDepth = 200;
const uint64_t kDefaultMutateWeight = 1000000;
-enum class Mutation {
+enum class Mutation : uint8_t {
None,
Add, // Adds new field with default value.
Mutate, // Mutates field contents.
Delete, // Deletes field.
Copy, // Copy values copied from another field.
+ Clone, // Create new field with value copied from another.
- // TODO(vitalybuka):
- // Clone, // Adds new field with value copied from another field.
+ Last = Clone,
};
+using MutationBitset = std::bitset<static_cast<size_t>(Mutation::Last)>;
+
+using Messages = std::vector<Message*>;
+using ConstMessages = std::vector<const Message*>;
+
// Return random integer from [0, count)
size_t GetRandomIndex(RandomEngine* random, size_t count) {
assert(count > 0);
@@ -124,14 +133,14 @@ class CanCopyAndDifferentField
: public FieldFunction<CanCopyAndDifferentField, bool> {
public:
template <class T>
- bool ForType(const ConstFieldInstance& src,
- const ConstFieldInstance& dst) const {
+ bool ForType(const ConstFieldInstance& src, const ConstFieldInstance& dst,
+ int size_increase_hint) const {
T s;
src.Load(&s);
if (!dst.CanStore(s)) return false;
T d;
dst.Load(&d);
- return !IsEqual(s, d);
+ return SizeDiff(s, d) <= size_increase_hint && !IsEqual(s, d);
}
private:
@@ -141,8 +150,8 @@ class CanCopyAndDifferentField
return a.index == b.index;
}
- bool IsEqual(const std::unique_ptr<protobuf::Message>& a,
- const std::unique_ptr<protobuf::Message>& b) const {
+ bool IsEqual(const std::unique_ptr<Message>& a,
+ const std::unique_ptr<Message>& b) const {
return MessageDifferencer::Equals(*a, *b);
}
@@ -150,17 +159,31 @@ class CanCopyAndDifferentField
bool IsEqual(const T& a, const T& b) const {
return a == b;
}
+
+ int64_t SizeDiff(const std::unique_ptr<Message>& src,
+ const std::unique_ptr<Message>& dst) const {
+ return src->ByteSizeLong() - dst->ByteSizeLong();
+ }
+
+ int64_t SizeDiff(const std::string& src, const std::string& dst) const {
+ return src.size() - dst.size();
+ }
+
+ template <class T>
+ int64_t SizeDiff(const T&, const T&) const {
+ return 0;
+ }
};
// Selects random field and mutation from the given proto message.
class MutationSampler {
public:
- MutationSampler(bool keep_initialized, RandomEngine* random, Message* message)
- : keep_initialized_(keep_initialized), random_(random), sampler_(random) {
- Sample(message);
- assert(mutation() != Mutation::None ||
- message->GetDescriptor()->field_count() == 0);
- }
+ MutationSampler(bool keep_initialized, MutationBitset allowed_mutations,
+ RandomEngine* random)
+ : keep_initialized_(keep_initialized),
+ allowed_mutations_(allowed_mutations),
+ random_(random),
+ sampler_(random) {}
// Returns selected field.
const FieldInstance& field() const { return sampler_.selected().field; }
@@ -168,8 +191,15 @@ class MutationSampler {
// Returns selected mutation.
Mutation mutation() const { return sampler_.selected().mutation; }
- private:
void Sample(Message* message) {
+ SampleImpl(message);
+ assert(mutation() != Mutation::None ||
+ !allowed_mutations_[static_cast<size_t>(Mutation::Mutate)] ||
+ message->GetDescriptor()->field_count() == 0);
+ }
+
+ private:
+ void SampleImpl(Message* message) {
const Descriptor* descriptor = message->GetDescriptor();
const Reflection* reflection = message->GetReflection();
@@ -186,59 +216,46 @@ class MutationSampler {
const FieldDescriptor* add_field =
oneof->field(GetRandomIndex(random_, oneof->field_count()));
if (add_field != current_field) {
- sampler_.Try(kDefaultMutateWeight,
- {{message, add_field}, Mutation::Add});
+ Try({message, add_field}, Mutation::Add);
+ Try({message, add_field}, Mutation::Clone);
break;
}
if (oneof->field_count() < 2) break;
}
if (current_field) {
- if (current_field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) {
- sampler_.Try(kDefaultMutateWeight,
- {{message, current_field}, Mutation::Mutate});
- }
- sampler_.Try(kDefaultMutateWeight,
- {{message, current_field}, Mutation::Delete});
- sampler_.Try(kDefaultMutateWeight,
- {{message, current_field}, Mutation::Copy});
+ if (current_field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE)
+ Try({message, current_field}, Mutation::Mutate);
+ Try({message, current_field}, Mutation::Delete);
+ Try({message, current_field}, Mutation::Copy);
}
}
} else {
if (field->is_repeated()) {
int field_size = reflection->FieldSize(*message, field);
- sampler_.Try(
- kDefaultMutateWeight,
- {{message, field, GetRandomIndex(random_, field_size + 1)},
- Mutation::Add});
+ size_t random_index = GetRandomIndex(random_, field_size + 1);
+ Try({message, field, random_index}, Mutation::Add);
+ Try({message, field, random_index}, Mutation::Clone);
if (field_size) {
size_t random_index = GetRandomIndex(random_, field_size);
- if (field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) {
- sampler_.Try(kDefaultMutateWeight,
- {{message, field, random_index}, Mutation::Mutate});
- }
- sampler_.Try(kDefaultMutateWeight,
- {{message, field, random_index}, Mutation::Delete});
- sampler_.Try(kDefaultMutateWeight,
- {{message, field, random_index}, Mutation::Copy});
+ if (field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE)
+ Try({message, field, random_index}, Mutation::Mutate);
+ Try({message, field, random_index}, Mutation::Delete);
+ Try({message, field, random_index}, Mutation::Copy);
}
} else {
if (reflection->HasField(*message, field) ||
IsProto3SimpleField(*field)) {
- if (field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) {
- sampler_.Try(kDefaultMutateWeight,
- {{message, field}, Mutation::Mutate});
- }
+ if (field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE)
+ Try({message, field}, Mutation::Mutate);
if (!IsProto3SimpleField(*field) &&
(!field->is_required() || !keep_initialized_)) {
- sampler_.Try(kDefaultMutateWeight,
- {{message, field}, Mutation::Delete});
+ Try({message, field}, Mutation::Delete);
}
- sampler_.Try(kDefaultMutateWeight,
- {{message, field}, Mutation::Copy});
+ Try({message, field}, Mutation::Copy);
} else {
- sampler_.Try(kDefaultMutateWeight,
- {{message, field}, Mutation::Add});
+ Try({message, field}, Mutation::Add);
+ Try({message, field}, Mutation::Clone);
}
}
}
@@ -247,15 +264,22 @@ class MutationSampler {
if (field->is_repeated()) {
const int field_size = reflection->FieldSize(*message, field);
for (int j = 0; j < field_size; ++j)
- Sample(reflection->MutableRepeatedMessage(message, field, j));
+ SampleImpl(reflection->MutableRepeatedMessage(message, field, j));
} else if (reflection->HasField(*message, field)) {
- Sample(reflection->MutableMessage(message, field));
+ SampleImpl(reflection->MutableMessage(message, field));
}
}
}
}
+ void Try(const FieldInstance& field, Mutation mutation) {
+ assert(mutation != Mutation::None);
+ if (!allowed_mutations_[static_cast<size_t>(mutation)]) return;
+ sampler_.Try(kDefaultMutateWeight, {field, mutation});
+ }
+
bool keep_initialized_ = false;
+ MutationBitset allowed_mutations_;
RandomEngine* random_;
@@ -273,10 +297,13 @@ class MutationSampler {
class DataSourceSampler {
public:
DataSourceSampler(const ConstFieldInstance& match, RandomEngine* random,
- Message* message)
- : match_(match), random_(random), sampler_(random) {
- Sample(message);
- }
+ int size_increase_hint)
+ : match_(match),
+ random_(random),
+ size_increase_hint_(size_increase_hint),
+ sampler_(random) {}
+
+ void Sample(const Message& message) { SampleImpl(message); }
// Returns selected field.
const ConstFieldInstance& field() const {
@@ -287,21 +314,21 @@ class DataSourceSampler {
bool IsEmpty() const { return sampler_.IsEmpty(); }
private:
- void Sample(Message* message) {
- const Descriptor* descriptor = message->GetDescriptor();
- const Reflection* reflection = message->GetReflection();
+ void SampleImpl(const Message& message) {
+ const Descriptor* descriptor = message.GetDescriptor();
+ const Reflection* reflection = message.GetReflection();
int field_count = descriptor->field_count();
for (int i = 0; i < field_count; ++i) {
const FieldDescriptor* field = descriptor->field(i);
if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
if (field->is_repeated()) {
- const int field_size = reflection->FieldSize(*message, field);
+ const int field_size = reflection->FieldSize(message, field);
for (int j = 0; j < field_size; ++j) {
- Sample(reflection->MutableRepeatedMessage(message, field, j));
+ SampleImpl(reflection->GetRepeatedMessage(message, field, j));
}
- } else if (reflection->HasField(*message, field)) {
- Sample(reflection->MutableMessage(message, field));
+ } else if (reflection->HasField(message, field)) {
+ SampleImpl(reflection->GetMessage(message, field));
}
}
@@ -313,16 +340,16 @@ class DataSourceSampler {
}
if (field->is_repeated()) {
- if (int field_size = reflection->FieldSize(*message, field)) {
- ConstFieldInstance source(message, field,
+ if (int field_size = reflection->FieldSize(message, field)) {
+ ConstFieldInstance source(&message, field,
GetRandomIndex(random_, field_size));
- if (CanCopyAndDifferentField()(source, match_))
+ if (CanCopyAndDifferentField()(source, match_, size_increase_hint_))
sampler_.Try(field_size, source);
}
} else {
- if (reflection->HasField(*message, field)) {
- ConstFieldInstance source(message, field);
- if (CanCopyAndDifferentField()(source, match_))
+ if (reflection->HasField(message, field)) {
+ ConstFieldInstance source(&message, field);
+ if (CanCopyAndDifferentField()(source, match_, size_increase_hint_))
sampler_.Try(1, source);
}
}
@@ -331,19 +358,162 @@ class DataSourceSampler {
ConstFieldInstance match_;
RandomEngine* random_;
+ int size_increase_hint_;
WeightedReservoirSampler<ConstFieldInstance, RandomEngine> sampler_;
};
+using UnpackedAny =
+ std::unordered_map<const Message*, std::unique_ptr<Message>>;
+
+const Descriptor* GetAnyTypeDescriptor(const Any& any) {
+ std::string type_name;
+ if (!Any::ParseAnyTypeUrl(std::string(any.type_url()), &type_name))
+ return nullptr;
+ return any.descriptor()->file()->pool()->FindMessageTypeByName(type_name);
+}
+
+std::unique_ptr<Message> UnpackAny(const Any& any) {
+ const Descriptor* desc = GetAnyTypeDescriptor(any);
+ if (!desc) return {};
+ std::unique_ptr<Message> message(
+ any.GetReflection()->GetMessageFactory()->GetPrototype(desc)->New());
+ message->ParsePartialFromString(std::string(any.value()));
+ return message;
+}
+
+const Any* CastToAny(const Message* message) {
+ return Any::GetDescriptor() == message->GetDescriptor()
+ ? static_cast<const Any*>(message)
+ : nullptr;
+}
+
+Any* CastToAny(Message* message) {
+ return Any::GetDescriptor() == message->GetDescriptor()
+ ? static_cast<Any*>(message)
+ : nullptr;
+}
+
+std::unique_ptr<Message> UnpackIfAny(const Message& message) {
+ if (const Any* any = CastToAny(&message)) return UnpackAny(*any);
+ return {};
+}
+
+void UnpackAny(const Message& message, UnpackedAny* result) {
+ if (std::unique_ptr<Message> any = UnpackIfAny(message)) {
+ UnpackAny(*any, result);
+ result->emplace(&message, std::move(any));
+ return;
+ }
+
+ const Descriptor* descriptor = message.GetDescriptor();
+ const Reflection* reflection = message.GetReflection();
+
+ for (int i = 0; i < descriptor->field_count(); ++i) {
+ const FieldDescriptor* field = descriptor->field(i);
+ if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
+ if (field->is_repeated()) {
+ const int field_size = reflection->FieldSize(message, field);
+ for (int j = 0; j < field_size; ++j) {
+ UnpackAny(reflection->GetRepeatedMessage(message, field, j), result);
+ }
+ } else if (reflection->HasField(message, field)) {
+ UnpackAny(reflection->GetMessage(message, field), result);
+ }
+ }
+ }
+}
+
+class PostProcessing {
+ public:
+ using PostProcessors =
+ std::unordered_multimap<const Descriptor*, Mutator::PostProcess>;
+
+ PostProcessing(bool keep_initialized, const PostProcessors& post_processors,
+ const UnpackedAny& any, RandomEngine* random)
+ : keep_initialized_(keep_initialized),
+ post_processors_(post_processors),
+ any_(any),
+ random_(random) {}
+
+ void Run(Message* message, int max_depth) {
+ --max_depth;
+ const Descriptor* descriptor = message->GetDescriptor();
+
+ // Apply custom mutators in nested messages before packing any.
+ const Reflection* reflection = message->GetReflection();
+ for (int i = 0; i < descriptor->field_count(); i++) {
+ const FieldDescriptor* field = descriptor->field(i);
+ if (keep_initialized_ &&
+ (field->is_required() || descriptor->options().map_entry()) &&
+ !reflection->HasField(*message, field)) {
+ CreateDefaultField()(FieldInstance(message, field));
+ }
+
+ if (field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) continue;
+
+ if (max_depth < 0 && !field->is_required()) {
+ // Clear deep optional fields to avoid stack overflow.
+ reflection->ClearField(message, field);
+ if (field->is_repeated())
+ assert(!reflection->FieldSize(*message, field));
+ else
+ assert(!reflection->HasField(*message, field));
+ continue;
+ }
+
+ if (field->is_repeated()) {
+ const int field_size = reflection->FieldSize(*message, field);
+ for (int j = 0; j < field_size; ++j) {
+ Message* nested_message =
+ reflection->MutableRepeatedMessage(message, field, j);
+ Run(nested_message, max_depth);
+ }
+ } else if (reflection->HasField(*message, field)) {
+ Message* nested_message = reflection->MutableMessage(message, field);
+ Run(nested_message, max_depth);
+ }
+ }
+
+ if (Any* any = CastToAny(message)) {
+ if (max_depth < 0) {
+ // Clear deep Any fields to avoid stack overflow.
+ any->Clear();
+ } else {
+ auto It = any_.find(message);
+ if (It != any_.end()) {
+ Run(It->second.get(), max_depth);
+ std::string value;
+ It->second->SerializePartialToString(&value);
+ *any->mutable_value() = value;
+ }
+ }
+ }
+
+ // Call user callback after message trimmed, initialized and packed.
+ auto range = post_processors_.equal_range(descriptor);
+ for (auto it = range.first; it != range.second; ++it)
+ it->second(message, (*random_)());
+ }
+
+ private:
+ bool keep_initialized_;
+ const PostProcessors& post_processors_;
+ const UnpackedAny& any_;
+ RandomEngine* random_;
+};
+
} // namespace
class FieldMutator {
public:
- FieldMutator(size_t size_increase_hint, bool enforce_changes,
- bool enforce_utf8_strings, Mutator* mutator)
+ FieldMutator(int size_increase_hint, bool enforce_changes,
+ bool enforce_utf8_strings, const ConstMessages& sources,
+ Mutator* mutator)
: size_increase_hint_(size_increase_hint),
enforce_changes_(enforce_changes),
enforce_utf8_strings_(enforce_utf8_strings),
+ sources_(sources),
mutator_(mutator) {}
void Mutate(int32_t* value) const {
@@ -395,7 +565,8 @@ class FieldMutator {
assert(*message);
if (GetRandomBool(mutator_->random(), mutator_->random_to_default_ratio_))
return;
- mutator_->MutateImpl(message->get(), size_increase_hint_);
+ mutator_->MutateImpl(sources_, {message->get()}, false,
+ size_increase_hint_);
}
private:
@@ -412,9 +583,10 @@ class FieldMutator {
}
}
- size_t size_increase_hint_;
+ int size_increase_hint_;
size_t enforce_changes_;
bool enforce_utf8_strings_;
+ const ConstMessages& sources_;
Mutator* mutator_;
};
@@ -422,11 +594,12 @@ namespace {
struct MutateField : public FieldFunction<MutateField> {
template <class T>
- void ForType(const FieldInstance& field, size_t size_increase_hint,
- Mutator* mutator) const {
+ void ForType(const FieldInstance& field, int size_increase_hint,
+ const ConstMessages& sources, Mutator* mutator) const {
T value;
field.Load(&value);
- FieldMutator(size_increase_hint, true, field.EnforceUtf8(), mutator)
+ FieldMutator(size_increase_hint, true, field.EnforceUtf8(), sources,
+ mutator)
.Mutate(&value);
field.Store(value);
}
@@ -435,13 +608,13 @@ struct MutateField : public FieldFunction<MutateField> {
struct CreateField : public FieldFunction<CreateField> {
public:
template <class T>
- void ForType(const FieldInstance& field, size_t size_increase_hint,
- Mutator* mutator) const {
+ void ForType(const FieldInstance& field, int size_increase_hint,
+ const ConstMessages& sources, Mutator* mutator) const {
T value;
field.GetDefault(&value);
FieldMutator field_mutator(size_increase_hint,
false /* defaults could be useful */,
- field.EnforceUtf8(), mutator);
+ field.EnforceUtf8(), sources, mutator);
field_mutator.Mutate(&value);
field.Create(value);
}
@@ -451,210 +624,116 @@ struct CreateField : public FieldFunction<CreateField> {
void Mutator::Seed(uint32_t value) { random_.seed(value); }
-void Mutator::Mutate(Message* message, size_t size_increase_hint) {
- MutateImpl(message, size_increase_hint);
+void Mutator::Mutate(Message* message, size_t max_size_hint) {
+ UnpackedAny any;
+ UnpackAny(*message, &any);
- InitializeAndTrim(message, kMaxInitializeDepth);
- assert(!keep_initialized_ || message->IsInitialized());
+ Messages messages;
+ messages.reserve(any.size() + 1);
+ messages.push_back(message);
+ for (const auto& kv : any) messages.push_back(kv.second.get());
- if (!post_processors_.empty()) {
- ApplyPostProcessing(message);
- }
-}
+ ConstMessages sources(messages.begin(), messages.end());
+ MutateImpl(sources, messages, false,
+ static_cast<int>(max_size_hint) -
+ static_cast<int>(message->ByteSizeLong()));
-void Mutator::RegisterPostProcessor(const protobuf::Descriptor* desc,
- PostProcess callback) {
- post_processors_.emplace(desc, callback);
+ PostProcessing(keep_initialized_, post_processors_, any, &random_)
+ .Run(message, kMaxInitializeDepth);
+ assert(IsInitialized(*message));
}
-void Mutator::ApplyPostProcessing(Message* message) {
- const Descriptor* descriptor = message->GetDescriptor();
+void Mutator::CrossOver(const Message& message1, Message* message2,
+ size_t max_size_hint) {
+ UnpackedAny any;
+ UnpackAny(*message2, &any);
- auto it = post_processors_.find(descriptor);
- if (it != post_processors_.end()) {
- it->second(message, random_());
- }
+ Messages messages;
+ messages.reserve(any.size() + 1);
+ messages.push_back(message2);
+ for (auto& kv : any) messages.push_back(kv.second.get());
- // Now recursively apply custom mutators.
- const Reflection* reflection = message->GetReflection();
- for (int i = 0; i < descriptor->field_count(); i++) {
- const FieldDescriptor* field = descriptor->field(i);
- if (field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) {
- continue;
- }
- if (field->is_repeated()) {
- const int field_size = reflection->FieldSize(*message, field);
- for (int j = 0; j < field_size; ++j) {
- Message* nested_message =
- reflection->MutableRepeatedMessage(message, field, j);
- ApplyPostProcessing(nested_message);
- }
- } else if (reflection->HasField(*message, field)) {
- Message* nested_message = reflection->MutableMessage(message, field);
- ApplyPostProcessing(nested_message);
- }
- }
+ UnpackAny(message1, &any);
+
+ ConstMessages sources;
+ sources.reserve(any.size() + 2);
+ sources.push_back(&message1);
+ sources.push_back(message2);
+ for (const auto& kv : any) sources.push_back(kv.second.get());
+
+ MutateImpl(sources, messages, true,
+ static_cast<int>(max_size_hint) -
+ static_cast<int>(message2->ByteSizeLong()));
+
+ PostProcessing(keep_initialized_, post_processors_, any, &random_)
+ .Run(message2, kMaxInitializeDepth);
+ assert(IsInitialized(*message2));
+}
+
+void Mutator::RegisterPostProcessor(const Descriptor* desc,
+ PostProcess callback) {
+ post_processors_.emplace(desc, callback);
}
-void Mutator::MutateImpl(Message* message, size_t size_increase_hint) {
- for (;;) {
- MutationSampler mutation(keep_initialized_, &random_, message);
+bool Mutator::MutateImpl(const ConstMessages& sources, const Messages& messages,
+ bool copy_clone_only, int size_increase_hint) {
+ MutationBitset mutations;
+ if (copy_clone_only) {
+ mutations[static_cast<size_t>(Mutation::Copy)] = true;
+ mutations[static_cast<size_t>(Mutation::Clone)] = true;
+ } else if (size_increase_hint <= 16) {
+ mutations[static_cast<size_t>(Mutation::Delete)] = true;
+ } else {
+ mutations.set();
+ mutations[static_cast<size_t>(Mutation::Copy)] = false;
+ mutations[static_cast<size_t>(Mutation::Clone)] = false;
+ }
+ while (mutations.any()) {
+ MutationSampler mutation(keep_initialized_, mutations, &random_);
+ for (Message* message : messages) mutation.Sample(message);
+
switch (mutation.mutation()) {
case Mutation::None:
- return;
+ return true;
case Mutation::Add:
- CreateField()(mutation.field(), size_increase_hint / 2, this);
- return;
+ CreateField()(mutation.field(), size_increase_hint, sources, this);
+ return true;
case Mutation::Mutate:
- MutateField()(mutation.field(), size_increase_hint / 2, this);
- return;
+ MutateField()(mutation.field(), size_increase_hint, sources, this);
+ return true;
case Mutation::Delete:
DeleteField()(mutation.field());
- return;
+ return true;
+ case Mutation::Clone: {
+ CreateDefaultField()(mutation.field());
+ DataSourceSampler source_sampler(mutation.field(), &random_,
+ size_increase_hint);
+ for (const Message* source : sources) source_sampler.Sample(*source);
+ if (source_sampler.IsEmpty()) {
+ if (!IsProto3SimpleField(*mutation.field().descriptor()))
+ return true; // CreateField is enough for proto2.
+ break;
+ }
+ CopyField()(source_sampler.field(), mutation.field());
+ return true;
+ }
case Mutation::Copy: {
- DataSourceSampler source(mutation.field(), &random_, message);
- if (source.IsEmpty()) break;
- CopyField()(source.field(), mutation.field());
- return;
+ DataSourceSampler source_sampler(mutation.field(), &random_,
+ size_increase_hint);
+ for (const Message* source : sources) source_sampler.Sample(*source);
+ if (source_sampler.IsEmpty()) break;
+ CopyField()(source_sampler.field(), mutation.field());
+ return true;
}
default:
assert(false && "unexpected mutation");
- return;
+ return false;
}
- }
-}
-void Mutator::CrossOver(const protobuf::Message& message1,
- protobuf::Message* message2) {
- // CrossOver can produce result which still equals to inputs. So we backup
- // message2 to later comparison. message1 is already constant.
- std::unique_ptr<protobuf::Message> message2_copy(message2->New());
- message2_copy->CopyFrom(*message2);
-
- CrossOverImpl(message1, message2);
-
- InitializeAndTrim(message2, kMaxInitializeDepth);
- assert(!keep_initialized_ || message2->IsInitialized());
-
- if (!post_processors_.empty()) {
- ApplyPostProcessing(message2);
- }
-
- // Can't call mutate from crossover because of a bug in libFuzzer.
- // if (MessageDifferencer::Equals(*message2_copy, *message2) ||
- // MessageDifferencer::Equals(message1, *message2)) {
- // Mutate(message2, 0);
- // }
-}
-
-void Mutator::CrossOverImpl(const protobuf::Message& message1,
- protobuf::Message* message2) {
- const Descriptor* descriptor = message2->GetDescriptor();
- const Reflection* reflection = message2->GetReflection();
- assert(message1.GetDescriptor() == descriptor);
- assert(message1.GetReflection() == reflection);
-
- for (int i = 0; i < descriptor->field_count(); ++i) {
- const FieldDescriptor* field = descriptor->field(i);
-
- if (field->is_repeated()) {
- const int field_size1 = reflection->FieldSize(message1, field);
- int field_size2 = reflection->FieldSize(*message2, field);
- for (int j = 0; j < field_size1; ++j) {
- ConstFieldInstance source(&message1, field, j);
- FieldInstance destination(message2, field, field_size2++);
- AppendField()(source, destination);
- }
-
- assert(field_size2 == reflection->FieldSize(*message2, field));
-
- // Shuffle
- for (int j = 0; j < field_size2; ++j) {
- if (int k = GetRandomIndex(&random_, field_size2 - j)) {
- reflection->SwapElements(message2, field, j, j + k);
- }
- }
-
- int keep = GetRandomIndex(&random_, field_size2 + 1);
-
- if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
- int remove = field_size2 - keep;
- // Cross some message to keep with messages to remove.
- int cross = GetRandomIndex(&random_, std::min(keep, remove) + 1);
- for (int j = 0; j < cross; ++j) {
- int k = GetRandomIndex(&random_, keep);
- int r = keep + GetRandomIndex(&random_, remove);
- assert(k != r);
- CrossOverImpl(reflection->GetRepeatedMessage(*message2, field, r),
- reflection->MutableRepeatedMessage(message2, field, k));
- }
- }
-
- for (int j = keep; j < field_size2; ++j)
- reflection->RemoveLast(message2, field);
- assert(keep == reflection->FieldSize(*message2, field));
-
- } else if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
- if (!reflection->HasField(message1, field)) {
- if (GetRandomBool(&random_))
- DeleteField()(FieldInstance(message2, field));
- } else if (!reflection->HasField(*message2, field)) {
- if (GetRandomBool(&random_)) {
- ConstFieldInstance source(&message1, field);
- CopyField()(source, FieldInstance(message2, field));
- }
- } else {
- CrossOverImpl(reflection->GetMessage(message1, field),
- reflection->MutableMessage(message2, field));
- }
- } else {
- if (GetRandomBool(&random_)) {
- if (reflection->HasField(message1, field)) {
- ConstFieldInstance source(&message1, field);
- CopyField()(source, FieldInstance(message2, field));
- } else {
- DeleteField()(FieldInstance(message2, field));
- }
- }
- }
- }
-}
-
-void Mutator::InitializeAndTrim(Message* message, int max_depth) {
- const Descriptor* descriptor = message->GetDescriptor();
- const Reflection* reflection = message->GetReflection();
- for (int i = 0; i < descriptor->field_count(); ++i) {
- const FieldDescriptor* field = descriptor->field(i);
- if (keep_initialized_ &&
- (field->is_required() || descriptor->options().map_entry()) &&
- !reflection->HasField(*message, field)) {
- CreateDefaultField()(FieldInstance(message, field));
- }
-
- if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
- if (max_depth <= 0 && !field->is_required()) {
- // Clear deep optional fields to avoid stack overflow.
- reflection->ClearField(message, field);
- if (field->is_repeated())
- assert(!reflection->FieldSize(*message, field));
- else
- assert(!reflection->HasField(*message, field));
- continue;
- }
-
- if (field->is_repeated()) {
- const int field_size = reflection->FieldSize(*message, field);
- for (int j = 0; j < field_size; ++j) {
- Message* nested_message =
- reflection->MutableRepeatedMessage(message, field, j);
- InitializeAndTrim(nested_message, max_depth - 1);
- }
- } else if (reflection->HasField(*message, field)) {
- Message* nested_message = reflection->MutableMessage(message, field);
- InitializeAndTrim(nested_message, max_depth - 1);
- }
- }
+ // Don't try same mutation next time.
+ mutations[static_cast<size_t>(mutation.mutation())] = false;
}
+ return false;
}
int32_t Mutator::MutateInt32(int32_t value) { return FlipBit(value, &random_); }
@@ -681,14 +760,16 @@ size_t Mutator::MutateEnum(size_t index, size_t item_count) {
}
std::string Mutator::MutateString(const std::string& value,
- size_t size_increase_hint) {
+ int size_increase_hint) {
std::string result = value;
while (!result.empty() && GetRandomBool(&random_)) {
result.erase(GetRandomIndex(&random_, result.size()), 1);
}
- while (result.size() < size_increase_hint && GetRandomBool(&random_)) {
+ while (size_increase_hint > 0 &&
+ result.size() < static_cast<size_t>(size_increase_hint) &&
+ GetRandomBool(&random_)) {
size_t index = GetRandomIndex(&random_, result.size() + 1);
result.insert(result.begin() + index, GetRandomIndex(&random_, 1 << 8));
}
@@ -706,10 +787,16 @@ std::string Mutator::MutateString(const std::string& value,
}
std::string Mutator::MutateUtf8String(const std::string& value,
- size_t size_increase_hint) {
+ int size_increase_hint) {
std::string str = MutateString(value, size_increase_hint);
FixUtf8String(&str, &random_);
return str;
}
+bool Mutator::IsInitialized(const Message& message) const {
+ if (!keep_initialized_ || message.IsInitialized()) return true;
+ std::cerr << "Uninitialized: " << message.DebugString() << "\n";
+ return false;
+}
+
} // namespace protobuf_mutator
diff --git a/src/mutator.h b/src/mutator.h
index 7af7b38..57c7c47 100644
--- a/src/mutator.h
+++ b/src/mutator.h
@@ -52,15 +52,13 @@ class Mutator {
void Seed(uint32_t value);
// message: message to mutate.
- // size_increase_hint: approximate number of bytes which can be added to the
- // message. Method does not guarantee that real result size increase will be
- // less than the value. It only changes probabilities of mutations which can
- // cause size increase. Caller could repeat mutation if result was larger than
- // requested.
- void Mutate(protobuf::Message* message, size_t size_increase_hint);
+ // max_size_hint: approximate max ByteSize() of resulting message. Method does
+ // not guarantee that real result will be strictly smaller than value. Caller
+ // could repeat mutation if result was larger than expected.
+ void Mutate(protobuf::Message* message, size_t max_size_hint);
- void CrossOver(const protobuf::Message& message1,
- protobuf::Message* message2);
+ void CrossOver(const protobuf::Message& message1, protobuf::Message* message2,
+ size_t max_size_hint);
// Callback to postprocess mutations.
// Implementation should use seed to initialize random number generators.
@@ -84,25 +82,25 @@ class Mutator {
virtual bool MutateBool(bool value);
virtual size_t MutateEnum(size_t index, size_t item_count);
virtual std::string MutateString(const std::string& value,
- size_t size_increase_hint);
-
- std::unordered_map<const protobuf::Descriptor*, PostProcess> post_processors_;
+ int size_increase_hint);
RandomEngine* random() { return &random_; }
private:
friend class FieldMutator;
friend class TestMutator;
- void InitializeAndTrim(protobuf::Message* message, int max_depth);
- void MutateImpl(protobuf::Message* message, size_t size_increase_hint);
- void CrossOverImpl(const protobuf::Message& message1,
- protobuf::Message* message2);
+ bool MutateImpl(const std::vector<const protobuf::Message*>& sources,
+ const std::vector<protobuf::Message*>& messages,
+ bool copy_clone_only, int size_increase_hint);
std::string MutateUtf8String(const std::string& value,
- size_t size_increase_hint);
- void ApplyPostProcessing(protobuf::Message* message);
+ int size_increase_hint);
+ bool IsInitialized(const protobuf::Message& message) const;
bool keep_initialized_ = true;
size_t random_to_default_ratio_ = 100;
RandomEngine random_;
+ using PostProcessors =
+ std::unordered_multimap<const protobuf::Descriptor*, PostProcess>;
+ PostProcessors post_processors_;
};
} // namespace protobuf_mutator
diff --git a/src/mutator_test.cc b/src/mutator_test.cc
index a86760a..fe93181 100644
--- a/src/mutator_test.cc
+++ b/src/mutator_test.cc
@@ -46,6 +46,17 @@ const char kMessages[] = R"(
repeated_msg { required_int32: 67 }
repeated_msg {}
}
+ any {
+ [type.googleapis.com/protobuf_mutator.Msg] {
+ optional_msg {}
+ repeated_msg {}
+ any {
+ [type.googleapis.com/protobuf_mutator.Msg3.SubMsg] {
+ optional_int64: -5
+ }
+ }
+ }
+ }
)";
const char kMessagesProto3[] = R"(
@@ -59,6 +70,17 @@ const char kMessagesProto3[] = R"(
repeated_msg { optional_int32: 67 }
repeated_msg {}
}
+ any {
+ [type.googleapis.com/protobuf_mutator.Msg] {
+ optional_msg {}
+ repeated_msg {}
+ any {
+ [type.googleapis.com/protobuf_mutator.Msg3.SubMsg] {
+ optional_int64: -5
+ }
+ }
+ }
+ }
)";
const char kRequiredFields[] = R"(
@@ -154,6 +176,18 @@ const char kRequiredNestedFields[] = R"(
}
)";
+const char kRequiredInAnyFields[] = R"(
+ any {
+ [type.googleapis.com/protobuf_mutator.Msg] {
+ required_uint32: 14486213
+ required_uint64: 520229415
+ required_sint64: -6057486163525532641
+ required_string: "qwert"
+ required_bytes: "asdf"
+ }
+ }
+)";
+
const char kOptionalNestedFields[] = R"(
optional_int32: 123
optional_msg {
@@ -176,6 +210,18 @@ const char kOptionalNestedFields[] = R"(
}
)";
+const char kOptionalInAnyFields[] = R"(
+ any {
+ [type.googleapis.com/protobuf_mutator.Msg] {
+ optional_uint32: 440
+ optional_uint64: 1559
+ optional_sint32: 440615
+ optional_string: "XYZ"
+ optional_enum: ENUM_4
+ }
+ }
+)";
+
const char kRepeatedNestedFields[] = R"(
optional_int32: 123
optional_msg {
@@ -214,6 +260,39 @@ const char kRepeatedNestedFields[] = R"(
}
)";
+const char kRepeatedInAnyFields[] = R"(
+ any {
+ [type.googleapis.com/protobuf_mutator.Msg] {
+ repeated_double: 1.931778501556e-31
+ repeated_double: 1.26685288449177e-31
+ repeated_float: 4.739759e-41
+ repeated_float: 5.98038e-39
+ repeated_int32: 400201
+ repeated_int32: 673
+ repeated_int64: 104
+ repeated_int64: 52850685
+ }
+ }
+)";
+
+const char kOptionalInDeepAnyFields[] = R"(
+ any {
+ [type.googleapis.com/protobuf_mutator.Msg] {
+ any {
+ [type.googleapis.com/protobuf_mutator.Msg] {
+ any {
+ [type.googleapis.com/protobuf_mutator.Msg] {
+ optional_double: 1.9317850152856e-314
+ optional_sint64: 1743625000076
+ optional_string: "XYZ"
+ }
+ }
+ }
+ }
+ }
+ }
+)";
+
class TestMutator : public Mutator {
public:
explicit TestMutator(bool keep_initialized,
@@ -224,12 +303,6 @@ class TestMutator : public Mutator {
keep_initialized_ = keep_initialized;
}
- // Avoids dedup logic for some tests.
- void NoDeDupCrossOver(const protobuf::Message& message1,
- protobuf::Message* message2) {
- CrossOverImpl(message1, message2);
- }
-
private:
RandomEngine random_;
};
@@ -258,7 +331,7 @@ class ReducedTestMutator : public TestMutator {
float MutateFloat(float value) override { return GetRandomValue(); }
double MutateDouble(double value) override { return GetRandomValue(); }
std::string MutateString(const std::string& value,
- size_t size_increase_hint) override {
+ int size_increase_hint) override {
return strings_[std::uniform_int_distribution<>(
0, strings_.size() - 1)(*random())];
}
@@ -282,7 +355,8 @@ std::vector<std::string> Split(const std::string& str) {
return result;
}
-using TestParams = std::tuple<const protobuf::Message*, const char*, size_t>;
+using TestParams =
+ std::tuple<const protobuf::Message*, const char*, size_t, std::string>;
template <class T>
std::vector<TestParams> GetFieldTestParams(
@@ -292,7 +366,8 @@ std::vector<TestParams> GetFieldTestParams(
auto lines = Split(t);
for (size_t i = 0; i != lines.size(); ++i) {
if (lines[i].find(':') != std::string::npos)
- results.push_back(std::make_tuple(&T::default_instance(), t, i));
+ results.push_back(
+ std::make_tuple(&T::default_instance(), t, i, lines[i]));
}
}
return results;
@@ -306,26 +381,42 @@ std::vector<TestParams> GetMessageTestParams(
auto lines = Split(t);
for (size_t i = 0; i != lines.size(); ++i) {
if (lines[i].find("{}") != std::string::npos)
- results.push_back(std::make_tuple(&T::default_instance(), t, i));
+ results.push_back(
+ std::make_tuple(&T::default_instance(), t, i, lines[i]));
}
}
return results;
}
-bool Mutate(const protobuf::Message& from, const protobuf::Message& to) {
+bool Mutate(const protobuf::Message& from, const protobuf::Message& to,
+ int iterations = 100000) {
EXPECT_FALSE(MessageDifferencer::Equals(from, to));
ReducedTestMutator mutator;
std::unique_ptr<protobuf::Message> message(from.New());
EXPECT_FALSE(MessageDifferencer::Equals(from, to));
- for (int j = 0; j < 1000000; ++j) {
+ for (int j = 0; j < iterations; ++j) {
message->CopyFrom(from);
- mutator.Mutate(message.get(), 1000);
+ mutator.Mutate(message.get(), 1500);
if (MessageDifferencer::Equals(*message, to)) return true;
}
ADD_FAILURE() << "Failed to get from:\n"
- << SaveMessageAsText(from) << "\nto:\n"
- << SaveMessageAsText(to);
+ << from.DebugString() << "\nto:\n"
+ << to.DebugString();
+ return false;
+}
+
+bool CrossOver(const protobuf::Message& from, const protobuf::Message& with,
+ const protobuf::Message& to, int iterations = 100000) {
+ EXPECT_FALSE(MessageDifferencer::Equals(from, to));
+ ReducedTestMutator mutator;
+ std::unique_ptr<protobuf::Message> message(from.New());
+ EXPECT_FALSE(MessageDifferencer::Equals(from, to));
+ for (int j = 0; j < iterations; ++j) {
+ message->CopyFrom(from);
+ mutator.CrossOver(with, message.get(), 1000);
+ if (MessageDifferencer::Equals(*message, to)) return true;
+ }
return false;
}
@@ -389,8 +480,10 @@ class MutatorFieldInsDelTest : public MutatorTest {};
INSTANTIATE_TEST_SUITE_P(Proto2, MutatorFieldInsDelTest,
ValuesIn(GetFieldTestParams<Msg>(
{kRequiredFields, kOptionalFields, kRepeatedFields,
- kRequiredNestedFields, kOptionalNestedFields,
- kRepeatedNestedFields})));
+ kRequiredNestedFields, kRequiredInAnyFields,
+ kOptionalNestedFields, kOptionalInAnyFields,
+ kRepeatedNestedFields, kRepeatedInAnyFields,
+ kOptionalInDeepAnyFields})));
TEST_P(MutatorFieldInsDelTest, DeleteField) {
LoadMessage(m1_.get());
@@ -398,106 +491,85 @@ TEST_P(MutatorFieldInsDelTest, DeleteField) {
EXPECT_TRUE(Mutate(*m1_, *m2_));
}
-TEST_P(MutatorFieldInsDelTest, InsertField) {
- LoadWithoutLine(m1_.get());
- LoadWithChangedLine(m2_.get(), 0);
- EXPECT_TRUE(Mutate(*m1_, *m2_));
-}
-
-class MutatorFieldTest : public MutatorTest {
- public:
- template <class Msg>
- void TestCopyField();
-};
-INSTANTIATE_TEST_SUITE_P(Proto2, MutatorFieldTest,
+INSTANTIATE_TEST_SUITE_P(Proto2, MutatorTest,
ValuesIn(GetFieldTestParams<Msg>(
{kRequiredFields, kOptionalFields, kRepeatedFields,
- kRequiredNestedFields, kOptionalNestedFields,
- kRepeatedNestedFields})));
-INSTANTIATE_TEST_SUITE_P(Proto3, MutatorFieldTest,
+ kRequiredNestedFields, kRequiredInAnyFields,
+ kOptionalNestedFields, kOptionalInAnyFields,
+ kRepeatedNestedFields, kRepeatedInAnyFields,
+ kOptionalInDeepAnyFields})));
+INSTANTIATE_TEST_SUITE_P(Proto3, MutatorTest,
ValuesIn(GetFieldTestParams<Msg3>(
{kOptionalFields, kRepeatedFields,
- kOptionalNestedFields, kRepeatedNestedFields})));
+ kOptionalNestedFields, kOptionalInAnyFields,
+ kRepeatedNestedFields, kRepeatedInAnyFields,
+ kOptionalInDeepAnyFields})));
-TEST_P(MutatorFieldTest, Initialized) {
+TEST_P(MutatorTest, Initialized) {
LoadWithoutLine(m1_.get());
TestMutator mutator(true);
mutator.Mutate(m1_.get(), 1000);
EXPECT_TRUE(m1_->IsInitialized());
}
-TEST_P(MutatorFieldTest, ChangeField) {
- LoadWithChangedLine(m1_.get(), 0);
+TEST_P(MutatorTest, InsertField) {
+ LoadWithoutLine(m1_.get());
LoadWithChangedLine(m2_.get(), 1);
EXPECT_TRUE(Mutate(*m1_, *m2_));
- EXPECT_TRUE(Mutate(*m2_, *m1_));
}
-template <class Msg>
-void MutatorFieldTest::TestCopyField() {
- LoadWithChangedLine(m1_.get(), 7);
- LoadWithChangedLine(m2_.get(), 0);
+TEST_P(MutatorTest, ChangeField) {
+ LoadWithChangedLine(m1_.get(), 0);
+ LoadWithChangedLine(m2_.get(), 1);
+ EXPECT_TRUE(Mutate(*m1_, *m2_, 1000000));
+ EXPECT_TRUE(Mutate(*m2_, *m1_, 1000000));
+}
+TEST_P(MutatorTest, CrossOver) {
+ LoadWithoutLine(m1_.get());
+ LoadMessage(m2_.get());
+
+ EXPECT_FALSE(MessageDifferencer::Equals(*m1_, *m2_));
+ TestMutator mutator(false);
+
+ EXPECT_TRUE(CrossOver(*m1_, *m2_, *m2_));
+}
+
+template <class Msg>
+void RunCrossOver(const protobuf::Message& m1, const protobuf::Message& m2) {
Msg from;
- from.add_repeated_msg()->CopyFrom(*m1_);
- from.add_repeated_msg()->CopyFrom(*m2_);
+ from.add_repeated_msg()->CopyFrom(m1);
+ from.add_repeated_msg()->CopyFrom(m2);
+ from.mutable_repeated_msg(1)->add_repeated_string("repeated_string");
Msg to;
- to.add_repeated_msg()->CopyFrom(*m1_);
- to.add_repeated_msg()->CopyFrom(*m1_);
- EXPECT_TRUE(Mutate(from, to));
-
- to.Clear();
- to.add_repeated_msg()->CopyFrom(*m2_);
- to.add_repeated_msg()->CopyFrom(*m2_);
- EXPECT_TRUE(Mutate(from, to));
+ to.add_repeated_msg()->CopyFrom(m1);
+ to.add_repeated_msg()->CopyFrom(m1);
+ to.mutable_repeated_msg(1)->add_repeated_string("repeated_string");
+ EXPECT_TRUE(CrossOver(from, from, to));
}
-TEST_P(MutatorFieldTest, CopyField) {
+TEST_P(MutatorTest, CopyField) {
+ LoadWithChangedLine(m1_.get(), 7);
+ LoadWithChangedLine(m2_.get(), 0);
+
if (m1_->GetDescriptor() == Msg::descriptor())
- TestCopyField<Msg>();
+ RunCrossOver<Msg>(*m1_, *m2_);
else
- TestCopyField<Msg3>();
+ RunCrossOver<Msg3>(*m1_, *m2_);
}
-class MutatorSingleFieldTest : public MutatorTest {};
-INSTANTIATE_TEST_SUITE_P(Proto2, MutatorSingleFieldTest,
- ValuesIn(GetFieldTestParams<Msg>({
- kRequiredFields,
- kOptionalFields,
- kRequiredNestedFields,
- kOptionalNestedFields,
- })));
-INSTANTIATE_TEST_SUITE_P(Proto3, MutatorSingleFieldTest,
- ValuesIn(GetFieldTestParams<Msg3>({
- kOptionalFields,
- kOptionalNestedFields,
- })));
-
-TEST_P(MutatorSingleFieldTest, CrossOver) {
- LoadWithoutLine(m1_.get());
- LoadMessage(m2_.get());
-
- EXPECT_FALSE(MessageDifferencer::Equals(*m1_, *m2_));
- TestMutator mutator(false);
-
- int match_m1_ = 0;
- int match_m2_ = 0;
- int iterations = 1000;
- std::unique_ptr<protobuf::Message> message(m1_->New());
- for (int j = 0; j < iterations; ++j) {
- message->CopyFrom(*m1_);
- mutator.NoDeDupCrossOver(*m2_, message.get());
- if (MessageDifferencer::Equals(*message, *m2_)) ++match_m2_;
- if (MessageDifferencer::Equals(*message, *m1_)) ++match_m1_;
- }
+TEST_P(MutatorTest, CloneField) {
+ LoadWithChangedLine(m1_.get(), 7);
+ LoadWithoutLine(m2_.get());
- EXPECT_LT(iterations * .4, match_m1_);
- EXPECT_GE(iterations * .6, match_m1_);
- EXPECT_LT(iterations * .4, match_m2_);
- EXPECT_GE(iterations * .6, match_m2_);
+ if (m1_->GetDescriptor() == Msg::descriptor())
+ RunCrossOver<Msg>(*m1_, *m2_);
+ else
+ RunCrossOver<Msg3>(*m1_, *m2_);
}
+class MutatorSingleFieldTest : public MutatorTest {};
template <typename T>
class MutatorTypedTest : public ::testing::Test {
public:
@@ -507,58 +579,6 @@ class MutatorTypedTest : public ::testing::Test {
using MutatorTypedTestTypes = testing::Types<Msg, Msg3>;
TYPED_TEST_SUITE(MutatorTypedTest, MutatorTypedTestTypes);
-TYPED_TEST(MutatorTypedTest, CrossOverRepeated) {
- typename TestFixture::Message m1;
- m1.add_repeated_int32(1);
- m1.add_repeated_int32(2);
- m1.add_repeated_int32(3);
-
- typename TestFixture::Message m2;
- m2.add_repeated_int32(4);
- m2.add_repeated_int32(5);
- m2.add_repeated_int32(6);
-
- int iterations = 10000;
- std::set<std::set<int>> sets;
- TestMutator mutator(false);
- for (int j = 0; j < iterations; ++j) {
- typename TestFixture::Message message;
- message.CopyFrom(m1);
- mutator.NoDeDupCrossOver(m2, &message);
- sets.insert(
- {message.repeated_int32().begin(), message.repeated_int32().end()});
- }
-
- EXPECT_EQ(1u << 6, sets.size());
-}
-
-TYPED_TEST(MutatorTypedTest, CrossOverRepeatedMessages) {
- typename TestFixture::Message m1;
- auto* rm1 = m1.add_repeated_msg();
- rm1->add_repeated_int32(1);
- rm1->add_repeated_int32(2);
-
- typename TestFixture::Message m2;
- auto* rm2 = m2.add_repeated_msg();
- rm2->add_repeated_int32(3);
- rm2->add_repeated_int32(4);
- rm2->add_repeated_int32(5);
- rm2->add_repeated_int32(6);
-
- int iterations = 10000;
- std::set<std::set<int>> sets;
- TestMutator mutator(false);
- for (int j = 0; j < iterations; ++j) {
- typename TestFixture::Message message;
- message.CopyFrom(m1);
- mutator.NoDeDupCrossOver(m2, &message);
- for (const auto& msg : message.repeated_msg())
- sets.insert({msg.repeated_int32().begin(), msg.repeated_int32().end()});
- }
-
- EXPECT_EQ(1u << 6, sets.size());
-}
-
TYPED_TEST(MutatorTypedTest, FailedMutations) {
TestMutator mutator(false);
size_t crossovers = 0;
@@ -575,47 +595,59 @@ TYPED_TEST(MutatorTypedTest, FailedMutations) {
}
tmp.CopyFrom(messages[1]);
- mutator.CrossOver(messages[0], &tmp);
+ mutator.CrossOver(messages[0], &tmp, 1000);
if (MessageDifferencer::Equals(tmp, messages[1]) ||
MessageDifferencer::Equals(tmp, messages[0]))
++crossovers;
}
// CrossOver may fail but very rare.
- EXPECT_LT(crossovers, 10u);
+ EXPECT_LT(crossovers, 100u);
}
TYPED_TEST(MutatorTypedTest, RegisterPostProcessor) {
- constexpr char kInitialString[] = " ";
- constexpr char kIndicatorString[] = "0123456789abcdef";
- bool custom_mutation = false;
- bool regular_mutation = false;
-
+ std::set<std::string> top_mutations = {"0123456789abcdef",
+ "abcdef0123456789"};
TestMutator mutator(false);
- mutator.RegisterPostProcessor(
- TestFixture::Message::descriptor(),
- [kIndicatorString](protobuf::Message* message, unsigned int seed) {
- typename TestFixture::Message* test_message =
- static_cast<typename TestFixture::Message*>(message);
- if (seed % 2) test_message->set_optional_string(kIndicatorString);
- });
+ for (auto& v : top_mutations) {
+ mutator.RegisterPostProcessor(
+ TestFixture::Message::descriptor(),
+ [=](protobuf::Message* message, unsigned int seed) {
+ auto test_message =
+ static_cast<typename TestFixture::Message*>(message);
+ if (seed % 2) test_message->set_optional_string(v);
+ });
+ }
+
+ std::set<int64_t> nested_mutations = {1234567, 567890};
+ for (auto& v : nested_mutations) {
+ mutator.RegisterPostProcessor(
+ TestFixture::Message::SubMsg::descriptor(),
+ [=](protobuf::Message* message, unsigned int seed) {
+ auto test_message =
+ static_cast<typename TestFixture::Message::SubMsg*>(message);
+ if (seed % 2) test_message->set_optional_int64(v);
+ });
+ }
+
+ bool regular_mutation = false;
for (int j = 0; j < 100000; ++j) {
// Include this field to increase the probability of mutation.
typename TestFixture::Message message;
- message.set_optional_string(kInitialString);
+ message.set_optional_string("a");
mutator.Mutate(&message, 1000);
- if (message.optional_string() == kIndicatorString) {
- custom_mutation = true;
- } else if (message.optional_string() != kInitialString) {
- regular_mutation = true;
- }
+ top_mutations.erase(message.optional_string());
+ nested_mutations.erase(message.mutable_sub_message()->optional_int64());
+ if (message.optional_string().empty()) regular_mutation = true;
- if (custom_mutation && regular_mutation) break;
+ if (top_mutations.empty() && nested_mutations.empty() && regular_mutation)
+ break;
}
- EXPECT_TRUE(custom_mutation);
+ EXPECT_TRUE(top_mutations.empty());
+ EXPECT_TRUE(nested_mutations.empty());
EXPECT_TRUE(regular_mutation);
}
@@ -709,6 +741,25 @@ TEST_P(MutatorMessagesTest, InsertMessage) {
EXPECT_TRUE(Mutate(*m1_, *m2_));
}
+class MutatorMessagesSizeTest : public TestWithParam<size_t> {};
+
+static const size_t kMaxSizes[] = {100, 256, 777, 10101};
+INSTANTIATE_TEST_SUITE_P(Proto, MutatorMessagesSizeTest, ValuesIn(kMaxSizes));
+
+TEST_P(MutatorMessagesSizeTest, MaxSize) {
+ TestMutator mutator(false);
+ size_t over_sized_count = 0;
+ Msg message;
+ const size_t kMaxSize = GetParam();
+ const int kIterations = 10000;
+ for (int i = 0; i < kIterations; ++i) {
+ mutator.Mutate(&message, kMaxSize);
+ if (message.ByteSizeLong() > kMaxSize) ++over_sized_count;
+ EXPECT_LT(message.ByteSizeLong(), 1.1 * kMaxSize);
+ }
+ EXPECT_LT(over_sized_count, kIterations * .1);
+}
+
// TODO(vitalybuka): Special tests for oneof.
TEST(MutatorMessagesTest, NeverCopyUnknownEnum) {
diff --git a/src/mutator_test_proto2.proto b/src/mutator_test_proto2.proto
index 0927775..44d4757 100644
--- a/src/mutator_test_proto2.proto
+++ b/src/mutator_test_proto2.proto
@@ -1,6 +1,9 @@
syntax = "proto2";
+
package protobuf_mutator;
+import "google/protobuf/any.proto";
+
message Msg {
enum Enum {
ENUM_0 = 0;
@@ -93,6 +96,8 @@ message Msg {
Msg oneof_msg = 68;
}
+ optional SubMsg sub_message = 69;
+
required group Group = 70 {
required bool required_bool = 1;
optional bool optional_bool = 2;
@@ -122,6 +127,7 @@ message Msg {
map<string, int32> map1 = 1;
map<int32, Msg> map2 = 2;
}
-}
+ optional google.protobuf.Any any = 90;
+}
diff --git a/src/mutator_test_proto3.proto b/src/mutator_test_proto3.proto
index 5fd7507..f40fe2b 100644
--- a/src/mutator_test_proto3.proto
+++ b/src/mutator_test_proto3.proto
@@ -1,6 +1,9 @@
syntax = "proto3";
+
package protobuf_mutator;
+import "google/protobuf/any.proto";
+
message Msg3 {
enum Enum {
ENUM_0 = 0;
@@ -15,6 +18,10 @@ message Msg3 {
ENUM_9 = 9;
}
+ message SubMsg {
+ int64 optional_int64 = 1;
+ }
+
double optional_double = 18;
float optional_float = 19;
int32 optional_int32 = 20;
@@ -71,6 +78,8 @@ message Msg3 {
Msg3 oneof_msg = 68;
}
+ SubMsg sub_message = 69;
+
message EmptyMessage {}
message RegressionMessage {
@@ -94,4 +103,6 @@ message Msg3 {
map<string, int32> map1 = 1;
map<int32, Msg3> map2 = 2;
}
+
+ google.protobuf.Any any = 90;
}