Skip to content

Commit 4df416c

Browse files
committed
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.
1 parent 04eac41 commit 4df416c

File tree

2 files changed

+22
-9
lines changed

2 files changed

+22
-9
lines changed

proto/equal.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ Equality is defined in this way:
5050
are equal, and extensions sets are equal.
5151
- Two set scalar fields are equal iff their values are equal.
5252
If the fields are of a floating-point type, remember that
53-
NaN != x for all x, including NaN.
53+
NaN != x for all x, including NaN. If the message is defined
54+
in a proto3 .proto file, fields are not "set"; specifically,
55+
zero length proto3 "bytes" fields are equal (nil == {}).
5456
- Two repeated fields are equal iff their lengths are the same,
5557
and their corresponding elements are equal (a "bytes" field,
5658
although represented by []byte, is not a repeated field)
@@ -88,6 +90,7 @@ func Equal(a, b Message) bool {
8890

8991
// v1 and v2 are known to have the same type.
9092
func equalStruct(v1, v2 reflect.Value) bool {
93+
sprop := GetProperties(v1.Type())
9194
for i := 0; i < v1.NumField(); i++ {
9295
f := v1.Type().Field(i)
9396
if strings.HasPrefix(f.Name, "XXX_") {
@@ -113,7 +116,7 @@ func equalStruct(v1, v2 reflect.Value) bool {
113116
}
114117
f1, f2 = f1.Elem(), f2.Elem()
115118
}
116-
if !equalAny(f1, f2) {
119+
if !equalAny(f1, f2, sprop.Prop[i]) {
117120
return false
118121
}
119122
}
@@ -140,7 +143,8 @@ func equalStruct(v1, v2 reflect.Value) bool {
140143
}
141144

142145
// v1 and v2 are known to have the same type.
143-
func equalAny(v1, v2 reflect.Value) bool {
146+
// prop may be nil.
147+
func equalAny(v1, v2 reflect.Value, prop *Properties) bool {
144148
if v1.Type() == protoMessageType {
145149
m1, _ := v1.Interface().(Message)
146150
m2, _ := v2.Interface().(Message)
@@ -163,7 +167,7 @@ func equalAny(v1, v2 reflect.Value) bool {
163167
if e1.Type() != e2.Type() {
164168
return false
165169
}
166-
return equalAny(e1, e2)
170+
return equalAny(e1, e2, nil)
167171
case reflect.Map:
168172
if v1.Len() != v2.Len() {
169173
return false
@@ -174,16 +178,22 @@ func equalAny(v1, v2 reflect.Value) bool {
174178
// This key was not found in the second map.
175179
return false
176180
}
177-
if !equalAny(v1.MapIndex(key), val2) {
181+
if !equalAny(v1.MapIndex(key), val2, nil) {
178182
return false
179183
}
180184
}
181185
return true
182186
case reflect.Ptr:
183-
return equalAny(v1.Elem(), v2.Elem())
187+
return equalAny(v1.Elem(), v2.Elem(), prop)
184188
case reflect.Slice:
185189
if v1.Type().Elem().Kind() == reflect.Uint8 {
186190
// short circuit: []byte
191+
192+
// Edge case: if this is in a proto3 message, a zero length
193+
// bytes field is considered the zero value.
194+
if prop != nil && prop.proto3 && v1.Len() == 0 && v2.Len() == 0 {
195+
return true
196+
}
187197
if v1.IsNil() != v2.IsNil() {
188198
return false
189199
}
@@ -194,7 +204,7 @@ func equalAny(v1, v2 reflect.Value) bool {
194204
return false
195205
}
196206
for i := 0; i < v1.Len(); i++ {
197-
if !equalAny(v1.Index(i), v2.Index(i)) {
207+
if !equalAny(v1.Index(i), v2.Index(i), prop) {
198208
return false
199209
}
200210
}
@@ -229,7 +239,7 @@ func equalExtensions(base reflect.Type, em1, em2 map[int32]Extension) bool {
229239

230240
if m1 != nil && m2 != nil {
231241
// Both are unencoded.
232-
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2)) {
242+
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
233243
return false
234244
}
235245
continue
@@ -257,7 +267,7 @@ func equalExtensions(base reflect.Type, em1, em2 map[int32]Extension) bool {
257267
log.Printf("proto: badly encoded extension %d of %v: %v", extNum, base, err)
258268
return false
259269
}
260-
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2)) {
270+
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
261271
return false
262272
}
263273
}

proto/equal_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"testing"
3636

3737
. "github.com/golang/protobuf/proto"
38+
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
3839
pb "github.com/golang/protobuf/proto/testdata"
3940
)
4041

@@ -131,6 +132,8 @@ var EqualTests = []struct {
131132
&pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}},
132133
true,
133134
},
135+
// In proto3, []byte{} and []byte(nil) are equal.
136+
{"proto3 bytes, empty vs nil", &proto3pb.Message{Data: []byte{}}, &proto3pb.Message{Data: nil}, true},
134137

135138
{"extension vs. no extension", messageWithoutExtension, messageWithExtension1a, false},
136139
{"extension vs. same extension", messageWithExtension1a, messageWithExtension1b, true},

0 commit comments

Comments
 (0)