testing/protocmp: fix representation of empty bytes

The protoreflect API does not use the distinction between nil and non-nil
empty []byte to preserve presence. As such, avoid accidentally presenting
such information through the transformation.

Change-Id: Ia46b5e2d3fb534d7cd29f478ad47722bfaa06810
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/247458
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/testing/protocmp/xform.go b/testing/protocmp/xform.go
index d69bfb8..5a47d0f 100644
--- a/testing/protocmp/xform.go
+++ b/testing/protocmp/xform.go
@@ -296,6 +296,14 @@
 		return Enum{num: v.Enum(), ed: fd.Enum()}
 	case protoreflect.MessageKind, protoreflect.GroupKind:
 		return transformMessage(v.Message())
+	case protoreflect.BytesKind:
+		// The protoreflect API does not specify whether an empty bytes is
+		// guaranteed to be nil or not. Always return non-nil bytes to avoid
+		// leaking information about the concrete proto.Message implementation.
+		if len(v.Bytes()) == 0 {
+			return []byte{}
+		}
+		return v.Bytes()
 	default:
 		return v.Interface()
 	}
diff --git a/testing/protocmp/xform_test.go b/testing/protocmp/xform_test.go
index 7fc3f68..c6355d7 100644
--- a/testing/protocmp/xform_test.go
+++ b/testing/protocmp/xform_test.go
@@ -102,8 +102,8 @@
 			MapUint64Uint64: map[uint64]uint64{0: 64},
 			MapInt32Float:   map[int32]float32{32: 32.32},
 			MapInt32Double:  map[int32]float64{64: 64.64},
-			MapStringString: map[string]string{"k": "v"},
-			MapStringBytes:  map[string][]byte{"k": []byte("v")},
+			MapStringString: map[string]string{"k": "v", "empty": ""},
+			MapStringBytes:  map[string][]byte{"k": []byte("v"), "empty": nil},
 			MapStringNestedEnum: map[string]testpb.TestAllTypes_NestedEnum{
 				"k": testpb.TestAllTypes_FOO,
 			},
@@ -120,8 +120,8 @@
 			"map_uint64_uint64": map[uint64]uint64{0: 64},
 			"map_int32_float":   map[int32]float32{32: 32.32},
 			"map_int32_double":  map[int32]float64{64: 64.64},
-			"map_string_string": map[string]string{"k": "v"},
-			"map_string_bytes":  map[string][]byte{"k": []byte("v")},
+			"map_string_string": map[string]string{"k": "v", "empty": ""},
+			"map_string_bytes":  map[string][]byte{"k": []byte("v"), "empty": []byte{}},
 			"map_string_nested_enum": map[string]Enum{
 				"k": enumOf(testpb.TestAllTypes_FOO),
 			},