Fix proto.Equal handling of proto3 bytes fields.
proto3 specifies that non-message scalar fields don't have a "has" bit,
and so []byte{} and []byte(nil) are considered equivalent.
diff --git a/proto/equal.go b/proto/equal.go
index cc3f2c9..f5db1de 100644
--- a/proto/equal.go
+++ b/proto/equal.go
@@ -50,7 +50,9 @@
are equal, and extensions sets are equal.
- Two set scalar fields are equal iff their values are equal.
If the fields are of a floating-point type, remember that
- NaN != x for all x, including NaN.
+ NaN != x for all x, including NaN. If the message is defined
+ in a proto3 .proto file, fields are not "set"; specifically,
+ zero length proto3 "bytes" fields are equal (nil == {}).
- Two repeated fields are equal iff their lengths are the same,
and their corresponding elements are equal (a "bytes" field,
although represented by []byte, is not a repeated field)
@@ -88,6 +90,7 @@
// v1 and v2 are known to have the same type.
func equalStruct(v1, v2 reflect.Value) bool {
+ sprop := GetProperties(v1.Type())
for i := 0; i < v1.NumField(); i++ {
f := v1.Type().Field(i)
if strings.HasPrefix(f.Name, "XXX_") {
@@ -113,7 +116,7 @@
}
f1, f2 = f1.Elem(), f2.Elem()
}
- if !equalAny(f1, f2) {
+ if !equalAny(f1, f2, sprop.Prop[i]) {
return false
}
}
@@ -140,7 +143,8 @@
}
// v1 and v2 are known to have the same type.
-func equalAny(v1, v2 reflect.Value) bool {
+// prop may be nil.
+func equalAny(v1, v2 reflect.Value, prop *Properties) bool {
if v1.Type() == protoMessageType {
m1, _ := v1.Interface().(Message)
m2, _ := v2.Interface().(Message)
@@ -163,7 +167,7 @@
if e1.Type() != e2.Type() {
return false
}
- return equalAny(e1, e2)
+ return equalAny(e1, e2, nil)
case reflect.Map:
if v1.Len() != v2.Len() {
return false
@@ -174,16 +178,22 @@
// This key was not found in the second map.
return false
}
- if !equalAny(v1.MapIndex(key), val2) {
+ if !equalAny(v1.MapIndex(key), val2, nil) {
return false
}
}
return true
case reflect.Ptr:
- return equalAny(v1.Elem(), v2.Elem())
+ return equalAny(v1.Elem(), v2.Elem(), prop)
case reflect.Slice:
if v1.Type().Elem().Kind() == reflect.Uint8 {
// short circuit: []byte
+
+ // Edge case: if this is in a proto3 message, a zero length
+ // bytes field is considered the zero value.
+ if prop != nil && prop.proto3 && v1.Len() == 0 && v2.Len() == 0 {
+ return true
+ }
if v1.IsNil() != v2.IsNil() {
return false
}
@@ -194,7 +204,7 @@
return false
}
for i := 0; i < v1.Len(); i++ {
- if !equalAny(v1.Index(i), v2.Index(i)) {
+ if !equalAny(v1.Index(i), v2.Index(i), prop) {
return false
}
}
@@ -229,7 +239,7 @@
if m1 != nil && m2 != nil {
// Both are unencoded.
- if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2)) {
+ if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
return false
}
continue
@@ -257,7 +267,7 @@
log.Printf("proto: badly encoded extension %d of %v: %v", extNum, base, err)
return false
}
- if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2)) {
+ if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
return false
}
}
diff --git a/proto/equal_test.go b/proto/equal_test.go
index 0e0db8a..7b45eaa 100644
--- a/proto/equal_test.go
+++ b/proto/equal_test.go
@@ -35,6 +35,7 @@
"testing"
. "github.com/golang/protobuf/proto"
+ proto3pb "github.com/golang/protobuf/proto/proto3_proto"
pb "github.com/golang/protobuf/proto/testdata"
)
@@ -131,6 +132,8 @@
&pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}},
true,
},
+ // In proto3, []byte{} and []byte(nil) are equal.
+ {"proto3 bytes, empty vs nil", &proto3pb.Message{Data: []byte{}}, &proto3pb.Message{Data: nil}, true},
{"extension vs. no extension", messageWithoutExtension, messageWithExtension1a, false},
{"extension vs. same extension", messageWithExtension1a, messageWithExtension1b, true},