protobuf-go/proto/merge.go
Joe Tsai c908144c88 proto: fix race in Merge
Some existing targets (whether correctly or not) rely on it Merge
being safe to call concurrently so long as the set of fields being
merged are disjoint.

Change-Id: I4db9e64efccc7a2d44a5f9b52261b611cce461b0
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/196737
Reviewed-by: Damien Neil <dneil@google.com>
2019-09-20 19:55:42 +00:00

81 lines
2.4 KiB
Go

// Copyright 2019 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.
package proto
import "google.golang.org/protobuf/reflect/protoreflect"
// Merge merges src into dst, which must be messages with the same descriptor.
//
// Populated scalar fields in src are copied to dst, while populated
// singular messages in src are merged into dst by recursively calling Merge.
// The elements of every list field in src is appended to the corresponded
// list fields in dst. The entries of every map field in src is copied into
// the corresponding map field in dst, possibly replacing existing entries.
// The unknown fields of src are appended to the unknown fields of dst.
func Merge(dst, src Message) {
mergeMessage(dst.ProtoReflect(), src.ProtoReflect())
}
func mergeMessage(dst, src protoreflect.Message) {
if dst.Descriptor() != src.Descriptor() {
panic("descriptor mismatch")
}
src.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
switch {
case fd.IsList():
mergeList(dst.Mutable(fd).List(), v.List(), fd)
case fd.IsMap():
mergeMap(dst.Mutable(fd).Map(), v.Map(), fd.MapValue())
case fd.Message() != nil:
mergeMessage(dst.Mutable(fd).Message(), v.Message())
case fd.Kind() == protoreflect.BytesKind:
dst.Set(fd, cloneBytes(v))
default:
dst.Set(fd, v)
}
return true
})
if len(src.GetUnknown()) > 0 {
dst.SetUnknown(append(dst.GetUnknown(), src.GetUnknown()...))
}
}
func mergeList(dst, src protoreflect.List, fd protoreflect.FieldDescriptor) {
for i := 0; i < src.Len(); i++ {
switch v := src.Get(i); {
case fd.Message() != nil:
dstv := dst.NewElement()
mergeMessage(dstv.Message(), v.Message())
dst.Append(dstv)
case fd.Kind() == protoreflect.BytesKind:
dst.Append(cloneBytes(v))
default:
dst.Append(v)
}
}
}
func mergeMap(dst, src protoreflect.Map, fd protoreflect.FieldDescriptor) {
src.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
switch {
case fd.Message() != nil:
dstv := dst.NewValue()
mergeMessage(dstv.Message(), v.Message())
dst.Set(k, dstv) // may replace existing entry
case fd.Kind() == protoreflect.BytesKind:
dst.Set(k, cloneBytes(v))
default:
dst.Set(k, v)
}
return true
})
}
func cloneBytes(v protoreflect.Value) protoreflect.Value {
return protoreflect.ValueOfBytes(append([]byte{}, v.Bytes()...))
}