proto: add decoding fuzz test for proto3 to editions conversion

Change-Id: Ic2536373eeb5c814059ddf5c7c0d675d0e06153c
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/564817
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Stapelberg <stapelberg@google.com>
Auto-Submit: Lasse Folger <lassefolger@google.com>
This commit is contained in:
Lasse Folger 2024-02-20 09:05:39 +01:00
parent 8f6a6615d6
commit 5e8da94149
6 changed files with 3596 additions and 40 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,125 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
syntax = "proto3";
package goproto.proto.test;
option go_package = "google.golang.org/protobuf/internal/testprotos/editionsfuzztest";
message TestAllTypesProto3 {
message NestedMessage {
int32 a = 1;
TestAllTypesProto3 corecursive = 2;
}
enum NestedEnum {
FOO = 0;
BAR = 1;
BAZ = 2;
NEG = -1; // Intentionally negative.
}
int32 singular_int32 = 81;
int64 singular_int64 = 82;
uint32 singular_uint32 = 83;
uint64 singular_uint64 = 84;
sint32 singular_sint32 = 85;
sint64 singular_sint64 = 86;
fixed32 singular_fixed32 = 87;
fixed64 singular_fixed64 = 88;
sfixed32 singular_sfixed32 = 89;
sfixed64 singular_sfixed64 = 90;
float singular_float = 91;
double singular_double = 92;
bool singular_bool = 93;
string singular_string = 94;
bytes singular_bytes = 95;
NestedMessage singular_nested_message = 98;
ForeignMessageProto3 singular_foreign_message = 99;
NestedEnum singular_nested_enum = 101;
ForeignEnumProto3 singular_foreign_enum = 102;
optional int32 optional_int32 = 1;
optional int64 optional_int64 = 2;
optional uint32 optional_uint32 = 3;
optional uint64 optional_uint64 = 4;
optional sint32 optional_sint32 = 5;
optional sint64 optional_sint64 = 6;
optional fixed32 optional_fixed32 = 7;
optional fixed64 optional_fixed64 = 8;
optional sfixed32 optional_sfixed32 = 9;
optional sfixed64 optional_sfixed64 = 10;
optional float optional_float = 11;
optional double optional_double = 12;
optional bool optional_bool = 13;
optional string optional_string = 14;
optional bytes optional_bytes = 15;
optional NestedMessage optional_nested_message = 18;
optional ForeignMessageProto3 optional_foreign_message = 19;
optional NestedEnum optional_nested_enum = 21;
optional ForeignEnumProto3 optional_foreign_enum = 22;
repeated int32 repeated_int32 = 31;
repeated int64 repeated_int64 = 32;
repeated uint32 repeated_uint32 = 33;
repeated uint64 repeated_uint64 = 34;
repeated sint32 repeated_sint32 = 35;
repeated sint64 repeated_sint64 = 36;
repeated fixed32 repeated_fixed32 = 37;
repeated fixed64 repeated_fixed64 = 38;
repeated sfixed32 repeated_sfixed32 = 39;
repeated sfixed64 repeated_sfixed64 = 40;
repeated float repeated_float = 41;
repeated double repeated_double = 42;
repeated bool repeated_bool = 43;
repeated string repeated_string = 44;
repeated bytes repeated_bytes = 45;
repeated NestedMessage repeated_nested_message = 48;
repeated ForeignMessageProto3 repeated_foreign_message = 49;
repeated NestedEnum repeated_nested_enum = 51;
repeated ForeignEnumProto3 repeated_foreign_enum = 52;
map<int32, int32> map_int32_int32 = 56;
map<int64, int64> map_int64_int64 = 57;
map<uint32, uint32> map_uint32_uint32 = 58;
map<uint64, uint64> map_uint64_uint64 = 59;
map<sint32, sint32> map_sint32_sint32 = 60;
map<sint64, sint64> map_sint64_sint64 = 61;
map<fixed32, fixed32> map_fixed32_fixed32 = 62;
map<fixed64, fixed64> map_fixed64_fixed64 = 63;
map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 64;
map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 65;
map<int32, float> map_int32_float = 66;
map<int32, double> map_int32_double = 67;
map<bool, bool> map_bool_bool = 68;
map<string, string> map_string_string = 69;
map<string, bytes> map_string_bytes = 70;
map<string, NestedMessage> map_string_nested_message = 71;
map<string, NestedEnum> map_string_nested_enum = 73;
oneof oneof_field {
uint32 oneof_uint32 = 111;
NestedMessage oneof_nested_message = 112;
string oneof_string = 113;
bytes oneof_bytes = 114;
bool oneof_bool = 115;
uint64 oneof_uint64 = 116;
float oneof_float = 117;
double oneof_double = 118;
NestedEnum oneof_enum = 119;
}
}
message ForeignMessageProto3 {
int32 c = 1;
int32 d = 2;
}
enum ForeignEnumProto3 {
FOREIGN_PROTO3_ZERO = 0;
FOREIGN_PROTO3_FOO = 4;
FOREIGN_PROTO3_BAR = 5;
FOREIGN_PROTO3_BAZ = 6;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,139 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
edition = "2023";
package goproto.proto.test;
option features.field_presence = IMPLICIT;
option go_package = "google.golang.org/protobuf/internal/testprotos/editionsfuzztest";
message TestAllTypesProto3Editions {
message NestedMessage {
int32 a = 1;
TestAllTypesProto3Editions corecursive = 2;
}
enum NestedEnum {
FOO = 0;
BAR = 1;
BAZ = 2;
NEG = -1; // Intentionally negative.
}
int32 singular_int32 = 81;
int64 singular_int64 = 82;
uint32 singular_uint32 = 83;
uint64 singular_uint64 = 84;
sint32 singular_sint32 = 85;
sint64 singular_sint64 = 86;
fixed32 singular_fixed32 = 87;
fixed64 singular_fixed64 = 88;
sfixed32 singular_sfixed32 = 89;
sfixed64 singular_sfixed64 = 90;
float singular_float = 91;
double singular_double = 92;
bool singular_bool = 93;
string singular_string = 94;
bytes singular_bytes = 95;
NestedMessage singular_nested_message = 98;
ForeignMessageProto3Editions singular_foreign_message = 99;
NestedEnum singular_nested_enum = 101;
ForeignEnumProto3Editions singular_foreign_enum = 102;
int32 optional_int32 = 1 [features.field_presence = EXPLICIT];
int64 optional_int64 = 2 [features.field_presence = EXPLICIT];
uint32 optional_uint32 = 3 [features.field_presence = EXPLICIT];
uint64 optional_uint64 = 4 [features.field_presence = EXPLICIT];
sint32 optional_sint32 = 5 [features.field_presence = EXPLICIT];
sint64 optional_sint64 = 6 [features.field_presence = EXPLICIT];
fixed32 optional_fixed32 = 7 [features.field_presence = EXPLICIT];
fixed64 optional_fixed64 = 8 [features.field_presence = EXPLICIT];
sfixed32 optional_sfixed32 = 9 [features.field_presence = EXPLICIT];
sfixed64 optional_sfixed64 = 10 [features.field_presence = EXPLICIT];
float optional_float = 11 [features.field_presence = EXPLICIT];
double optional_double = 12 [features.field_presence = EXPLICIT];
bool optional_bool = 13 [features.field_presence = EXPLICIT];
string optional_string = 14 [features.field_presence = EXPLICIT];
bytes optional_bytes = 15 [features.field_presence = EXPLICIT];
NestedMessage optional_nested_message = 18;
ForeignMessageProto3Editions optional_foreign_message = 19;
NestedEnum optional_nested_enum = 21 [features.field_presence = EXPLICIT];
ForeignEnumProto3Editions optional_foreign_enum = 22 [features.field_presence = EXPLICIT];
repeated int32 repeated_int32 = 31;
repeated int64 repeated_int64 = 32;
repeated uint32 repeated_uint32 = 33;
repeated uint64 repeated_uint64 = 34;
repeated sint32 repeated_sint32 = 35;
repeated sint64 repeated_sint64 = 36;
repeated fixed32 repeated_fixed32 = 37;
repeated fixed64 repeated_fixed64 = 38;
repeated sfixed32 repeated_sfixed32 = 39;
repeated sfixed64 repeated_sfixed64 = 40;
repeated float repeated_float = 41;
repeated double repeated_double = 42;
repeated bool repeated_bool = 43;
repeated string repeated_string = 44;
repeated bytes repeated_bytes = 45;
repeated NestedMessage repeated_nested_message = 48;
repeated ForeignMessageProto3Editions repeated_foreign_message = 49;
repeated NestedEnum repeated_nested_enum = 51;
repeated ForeignEnumProto3Editions repeated_foreign_enum = 52;
map<int32, int32> map_int32_int32 = 56;
map<int64, int64> map_int64_int64 = 57;
map<uint32, uint32> map_uint32_uint32 = 58;
map<uint64, uint64> map_uint64_uint64 = 59;
map<sint32, sint32> map_sint32_sint32 = 60;
map<sint64, sint64> map_sint64_sint64 = 61;
map<fixed32, fixed32> map_fixed32_fixed32 = 62;
map<fixed64, fixed64> map_fixed64_fixed64 = 63;
map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 64;
map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 65;
map<int32, float> map_int32_float = 66;
map<int32, double> map_int32_double = 67;
map<bool, bool> map_bool_bool = 68;
map<string, string> map_string_string = 69;
map<string, bytes> map_string_bytes = 70;
map<string, NestedMessage> map_string_nested_message = 71;
map<string, NestedEnum> map_string_nested_enum = 73;
oneof oneof_field {
uint32 oneof_uint32 = 111;
NestedMessage oneof_nested_message = 112;
string oneof_string = 113;
bytes oneof_bytes = 114;
bool oneof_bool = 115;
uint64 oneof_uint64 = 116;
float oneof_float = 117;
double oneof_double = 118;
NestedEnum oneof_enum = 119;
}
}
message ForeignMessageProto3Editions {
int32 c = 1;
int32 d = 2;
}
enum ForeignEnumProto3Editions {
FOREIGN_PROTO3_EDITIONS_ZERO = 0;
FOREIGN_PROTO3_EDITIONS_FOO = 4;
FOREIGN_PROTO3_EDITIONS_BAR = 5;
FOREIGN_PROTO3_EDITIONS_BAZ = 6;
}

View File

@ -35,48 +35,62 @@ func TestUnmarshalInvalidGroupField(t *testing.T) {
}
}
// compareEquivalentProtos compares equivalent messages m0 and m1, where one is
// typically a Protobuf Editions message and the other isn't. It unmarshals the
// wireBytes into a message of type m0 and one of type m1 and compares the
// resulting messages for equality (ignoring type names). m0 and m1 must
// describe equivalent messages, meaning having the same field numbers and
// types.
func compareEquivalentProtos(t *testing.T, wireBytes []byte, m0, m1 proto.Message) {
t.Helper()
m0Instance := m0.ProtoReflect().Type().New().Interface()
errM0 := proto.Unmarshal(wireBytes, m0Instance)
m1Instance := m1.ProtoReflect().Type().New().Interface()
errM1 := proto.Unmarshal(wireBytes, m1Instance)
// Check that the error are the same (possible nil)
errorsMatch := (errM1 != nil) == (errM0 != nil)
if errM1 != nil && errM0 != nil {
errorsMatch = errM1.Error() == errM0.Error()
}
if !errorsMatch {
t.Fatalf("errors not equal:\neditions error: %v\nproto2 error:%v", errM1, errM0)
}
// Marshal the editions proto and unmarshal it into the equivalent proto2
// message to be able to compare the messages.
// This tests slightly more than necessary but should only lead to more
// coverage (unless the marshalling would undo errors of the unmarshalling
// which is very unlikely).
roundTrippedM0 := m0.ProtoReflect().Type().New().Interface()
err := roundTripMessage(roundTrippedM0, m1Instance)
if err != nil {
t.Fatalf("failed round tripping proto: %v", err)
}
// The cmp package does not deal with NaN on its own and will report
// NaN != NaN.
optNaN64 := cmp.Comparer(func(x, y float32) bool {
return (math.IsNaN(float64(x)) && math.IsNaN(float64(y))) || x == y
})
optNaN32 := cmp.Comparer(func(x, y float64) bool {
return (math.IsNaN(x) && math.IsNaN(y)) || x == y
})
if diff := cmp.Diff(m0Instance, roundTrippedM0, protocmp.Transform(), optNaN64, optNaN32); diff != "" {
t.Error(diff)
}
}
func FuzzProto2EditionConversion(f *testing.F) {
f.Add([]byte("Hello World!"))
f.Fuzz(func(t *testing.T, in []byte) {
editionsProto := &testfuzzpb.TestAllTypesProto2Editions{}
errEditions := proto.Unmarshal(in, editionsProto)
proto2Proto := &testfuzzpb.TestAllTypesProto2{}
errProto2 := proto.Unmarshal(in, proto2Proto)
// Check that the error are the same (possible nil)
errorsMatch := (errEditions != nil) == (errProto2 != nil)
if errEditions != nil && errProto2 != nil {
errorsMatch = errEditions.Error() == errProto2.Error()
}
if !errorsMatch {
t.Fatalf("errors not equal:\neditions error: %v\nproto2 error:%v", errEditions, errProto2)
}
// Marshal the editions proto and unmarshal it into the equivalent proto2
// message to be able to compare the messages.
// This tests slightly more than necessary but should only lead to more
// coverage (unless the marshalling would undo errors of the unmarshalling
// which is very unlikely).
marshalledEditionsProto, err := proto.Marshal(editionsProto)
if err != nil {
t.Fatalf("failed to marshal unmarshaled editions proto: %v", err)
}
roundTrippedProto2Proto := &testfuzzpb.TestAllTypesProto2{}
err = proto.Unmarshal(marshalledEditionsProto, roundTrippedProto2Proto)
if err != nil {
t.Fatalf("failed to unmarshal marshaled editions proto into proto2 proto: %v", err)
}
// The cmp package does not deal with NaN on its own and will report
// NaN != NaN.
optNaN64 := cmp.Comparer(func(x, y float32) bool {
return (math.IsNaN(float64(x)) && math.IsNaN(float64(y))) || x == y
})
optNaN32 := cmp.Comparer(func(x, y float64) bool {
return (math.IsNaN(x) && math.IsNaN(y)) || x == y
})
if diff := cmp.Diff(proto2Proto, roundTrippedProto2Proto, protocmp.Transform(), optNaN64, optNaN32); diff != "" {
t.Error(diff)
}
compareEquivalentProtos(t, in, (*testfuzzpb.TestAllTypesProto2)(nil), (*testfuzzpb.TestAllTypesProto2Editions)(nil))
})
}
func FuzzProto3EditionConversion(f *testing.F) {
f.Add([]byte("Hello World!"))
f.Fuzz(func(t *testing.T, in []byte) {
compareEquivalentProtos(t, in, (*testfuzzpb.TestAllTypesProto3)(nil), (*testfuzzpb.TestAllTypesProto3Editions)(nil))
})
}

View File

@ -2029,3 +2029,11 @@ func (f filterResolver) FindExtensionByNumber(message protoreflect.FullName, fie
}
return xt, nil
}
func roundTripMessage(dst, src proto.Message) error {
b, err := proto.Marshal(src)
if err != nil {
return err
}
return proto.Unmarshal(b, dst)
}