all: funnel similar functionality through a single function

Some companies (e.g., Google) run a profiling service where they may
choose to special-case certain symbols in a binary to classify
commonly used libraries like protobufs.

This CL funnels similar functionality through a single function
so that they can be more easily identified. This is by no means a
firm statement that these identifiers will never change names,
but at least the code documents warnings to avoid changing the
name of certain identifiers.

This CL provides the following semi-stable symbol names:
	"google.golang.org/protobuf/proto".MarshalOptions.size
	"google.golang.org/protobuf/proto".MarshalOptions.marshal
	"google.golang.org/protobuf/proto".UnmarshalOptions.unmarshal
	"google.golang.org/protobuf/encoding/prototext".MarshalOptions.marshal
	"google.golang.org/protobuf/encoding/prototext".UnmarshalOptions.unmarshal
	"google.golang.org/protobuf/encoding/protojson".MarshalOptions.marshal
	"google.golang.org/protobuf/encoding/protojson".UnmarshalOptions.unmarshal

Merge and Clone are not part of the above set since there is a
possibility that MergeOptions will be added in the future.

We use an unexported method so that we have the freedom to change the
method however we want since profilers do not care about that.

Change-Id: Ia79af260d00125f48139420e1e18a86482bd1829
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/234079
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Joe Tsai 2020-05-14 15:29:58 -07:00
parent 0fbe4ed572
commit 118baf6390
10 changed files with 69 additions and 32 deletions

View File

@ -52,6 +52,13 @@ type UnmarshalOptions struct {
// setting the fields. If it returns an error, the given message may be // setting the fields. If it returns an error, the given message may be
// partially set. // partially set.
func (o UnmarshalOptions) Unmarshal(b []byte, m proto.Message) error { func (o UnmarshalOptions) Unmarshal(b []byte, m proto.Message) error {
return o.unmarshal(b, m)
}
// unmarshal is a centralized function that all unmarshal operations go through.
// For profiling purposes, avoid changing the name of this function or
// introducing other code paths for unmarshal that do not go through this.
func (o UnmarshalOptions) unmarshal(b []byte, m proto.Message) error {
proto.Reset(m) proto.Reset(m)
if o.Resolver == nil { if o.Resolver == nil {

View File

@ -104,6 +104,13 @@ func (o MarshalOptions) Format(m proto.Message) string {
// MarshalOptions. Do not depend on the output being stable. It may change over // MarshalOptions. Do not depend on the output being stable. It may change over
// time across different versions of the program. // time across different versions of the program.
func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) { func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {
return o.marshal(m)
}
// marshal is a centralized function that all marshal operations go through.
// For profiling purposes, avoid changing the name of this function or
// introducing other code paths for marshal that do not go through this.
func (o MarshalOptions) marshal(m proto.Message) ([]byte, error) {
if o.Multiline && o.Indent == "" { if o.Multiline && o.Indent == "" {
o.Indent = defaultIndent o.Indent = defaultIndent
} }

View File

@ -54,6 +54,13 @@ type UnmarshalOptions struct {
// Unmarshal reads the given []byte and populates the given proto.Message using options in // Unmarshal reads the given []byte and populates the given proto.Message using options in
// UnmarshalOptions object. // UnmarshalOptions object.
func (o UnmarshalOptions) Unmarshal(b []byte, m proto.Message) error { func (o UnmarshalOptions) Unmarshal(b []byte, m proto.Message) error {
return o.unmarshal(b, m)
}
// unmarshal is a centralized function that all unmarshal operations go through.
// For profiling purposes, avoid changing the name of this function or
// introducing other code paths for unmarshal that do not go through this.
func (o UnmarshalOptions) unmarshal(b []byte, m proto.Message) error {
proto.Reset(m) proto.Reset(m)
if o.Resolver == nil { if o.Resolver == nil {

View File

@ -102,6 +102,13 @@ func (o MarshalOptions) Format(m proto.Message) string {
// MarshalOptions object. Do not depend on the output being stable. It may // MarshalOptions object. Do not depend on the output being stable. It may
// change over time across different versions of the program. // change over time across different versions of the program.
func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) { func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {
return o.marshal(m)
}
// marshal is a centralized function that all marshal operations go through.
// For profiling purposes, avoid changing the name of this function or
// introducing other code paths for marshal that do not go through this.
func (o MarshalOptions) marshal(m proto.Message) ([]byte, error) {
var delims = [2]byte{'{', '}'} var delims = [2]byte{'{', '}'}
if o.Multiline && o.Indent == "" { if o.Multiline && o.Indent == "" {

View File

@ -413,18 +413,18 @@ func generateProtoSize() string {
} }
var protoSizeTemplate = template.Must(template.New("").Parse(` var protoSizeTemplate = template.Must(template.New("").Parse(`
func sizeSingular(num protowire.Number, kind protoreflect.Kind, v protoreflect.Value) int { func (o MarshalOptions) sizeSingular(num protowire.Number, kind protoreflect.Kind, v protoreflect.Value) int {
switch kind { switch kind {
{{- range .}} {{- range .}}
case {{.Expr}}: case {{.Expr}}:
{{if (eq .Name "Message") -}} {{if (eq .Name "Message") -}}
return protowire.SizeBytes(sizeMessage(v.Message())) return protowire.SizeBytes(o.size(v.Message()))
{{- else if or (eq .WireType "Fixed32") (eq .WireType "Fixed64") -}} {{- else if or (eq .WireType "Fixed32") (eq .WireType "Fixed64") -}}
return protowire.Size{{.WireType}}() return protowire.Size{{.WireType}}()
{{- else if (eq .WireType "Bytes") -}} {{- else if (eq .WireType "Bytes") -}}
return protowire.Size{{.WireType}}(len({{.FromValue}})) return protowire.Size{{.WireType}}(len({{.FromValue}}))
{{- else if (eq .WireType "Group") -}} {{- else if (eq .WireType "Group") -}}
return protowire.Size{{.WireType}}(num, sizeMessage(v.Message())) return protowire.Size{{.WireType}}(num, o.size(v.Message()))
{{- else -}} {{- else -}}
return protowire.Size{{.WireType}}({{.FromValue}}) return protowire.Size{{.WireType}}({{.FromValue}})
{{- end}} {{- end}}

View File

@ -63,12 +63,15 @@ func (o UnmarshalOptions) UnmarshalState(in protoiface.UnmarshalInput) (protoifa
return o.unmarshal(in.Buf, in.Message) return o.unmarshal(in.Buf, in.Message)
} }
// unmarshal is a centralized function that all unmarshal operations go through.
// For profiling purposes, avoid changing the name of this function or
// introducing other code paths for unmarshal that do not go through this.
func (o UnmarshalOptions) unmarshal(b []byte, m protoreflect.Message) (out protoiface.UnmarshalOutput, err error) { func (o UnmarshalOptions) unmarshal(b []byte, m protoreflect.Message) (out protoiface.UnmarshalOutput, err error) {
if o.Resolver == nil { if o.Resolver == nil {
o.Resolver = protoregistry.GlobalTypes o.Resolver = protoregistry.GlobalTypes
} }
if !o.Merge { if !o.Merge {
Reset(m.Interface()) // TODO Reset(m.Interface())
} }
allowPartial := o.AllowPartial allowPartial := o.AllowPartial
o.Merge = true o.Merge = true
@ -105,7 +108,7 @@ func (o UnmarshalOptions) unmarshalMessage(b []byte, m protoreflect.Message) err
func (o UnmarshalOptions) unmarshalMessageSlow(b []byte, m protoreflect.Message) error { func (o UnmarshalOptions) unmarshalMessageSlow(b []byte, m protoreflect.Message) error {
md := m.Descriptor() md := m.Descriptor()
if messageset.IsMessageSet(md) { if messageset.IsMessageSet(md) {
return unmarshalMessageSet(b, m, o) return o.unmarshalMessageSet(b, m)
} }
fields := md.Fields() fields := md.Fields()
for len(b) > 0 { for len(b) > 0 {

View File

@ -134,6 +134,9 @@ func (o MarshalOptions) MarshalState(in protoiface.MarshalInput) (protoiface.Mar
return o.marshal(in.Buf, in.Message) return o.marshal(in.Buf, in.Message)
} }
// marshal is a centralized function that all marshal operations go through.
// For profiling purposes, avoid changing the name of this function or
// introducing other code paths for marshal that do not go through this.
func (o MarshalOptions) marshal(b []byte, m protoreflect.Message) (out protoiface.MarshalOutput, err error) { func (o MarshalOptions) marshal(b []byte, m protoreflect.Message) (out protoiface.MarshalOutput, err error) {
allowPartial := o.AllowPartial allowPartial := o.AllowPartial
o.AllowPartial = true o.AllowPartial = true
@ -206,7 +209,7 @@ func growcap(oldcap, wantcap int) (newcap int) {
func (o MarshalOptions) marshalMessageSlow(b []byte, m protoreflect.Message) ([]byte, error) { func (o MarshalOptions) marshalMessageSlow(b []byte, m protoreflect.Message) ([]byte, error) {
if messageset.IsMessageSet(m.Descriptor()) { if messageset.IsMessageSet(m.Descriptor()) {
return marshalMessageSet(b, m, o) return o.marshalMessageSet(b, m)
} }
// There are many choices for what order we visit fields in. The default one here // There are many choices for what order we visit fields in. The default one here
// is chosen for reasonable efficiency and simplicity given the protoreflect API. // is chosen for reasonable efficiency and simplicity given the protoreflect API.

View File

@ -13,24 +13,24 @@ import (
"google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/reflect/protoregistry"
) )
func sizeMessageSet(m protoreflect.Message) (size int) { func (o MarshalOptions) sizeMessageSet(m protoreflect.Message) (size int) {
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
size += messageset.SizeField(fd.Number()) size += messageset.SizeField(fd.Number())
size += protowire.SizeTag(messageset.FieldMessage) size += protowire.SizeTag(messageset.FieldMessage)
size += protowire.SizeBytes(sizeMessage(v.Message())) size += protowire.SizeBytes(o.size(v.Message()))
return true return true
}) })
size += messageset.SizeUnknown(m.GetUnknown()) size += messageset.SizeUnknown(m.GetUnknown())
return size return size
} }
func marshalMessageSet(b []byte, m protoreflect.Message, o MarshalOptions) ([]byte, error) { func (o MarshalOptions) marshalMessageSet(b []byte, m protoreflect.Message) ([]byte, error) {
if !flags.ProtoLegacy { if !flags.ProtoLegacy {
return b, errors.New("no support for message_set_wire_format") return b, errors.New("no support for message_set_wire_format")
} }
var err error var err error
o.rangeFields(m, func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { o.rangeFields(m, func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
b, err = marshalMessageSetField(b, fd, v, o) b, err = o.marshalMessageSetField(b, fd, v)
return err == nil return err == nil
}) })
if err != nil { if err != nil {
@ -39,7 +39,7 @@ func marshalMessageSet(b []byte, m protoreflect.Message, o MarshalOptions) ([]by
return messageset.AppendUnknown(b, m.GetUnknown()) return messageset.AppendUnknown(b, m.GetUnknown())
} }
func marshalMessageSetField(b []byte, fd protoreflect.FieldDescriptor, value protoreflect.Value, o MarshalOptions) ([]byte, error) { func (o MarshalOptions) marshalMessageSetField(b []byte, fd protoreflect.FieldDescriptor, value protoreflect.Value) ([]byte, error) {
b = messageset.AppendFieldStart(b, fd.Number()) b = messageset.AppendFieldStart(b, fd.Number())
b = protowire.AppendTag(b, messageset.FieldMessage, protowire.BytesType) b = protowire.AppendTag(b, messageset.FieldMessage, protowire.BytesType)
b = protowire.AppendVarint(b, uint64(o.Size(value.Message().Interface()))) b = protowire.AppendVarint(b, uint64(o.Size(value.Message().Interface())))
@ -51,12 +51,12 @@ func marshalMessageSetField(b []byte, fd protoreflect.FieldDescriptor, value pro
return b, nil return b, nil
} }
func unmarshalMessageSet(b []byte, m protoreflect.Message, o UnmarshalOptions) error { func (o UnmarshalOptions) unmarshalMessageSet(b []byte, m protoreflect.Message) error {
if !flags.ProtoLegacy { if !flags.ProtoLegacy {
return errors.New("no support for message_set_wire_format") return errors.New("no support for message_set_wire_format")
} }
return messageset.Unmarshal(b, false, func(num protowire.Number, v []byte) error { return messageset.Unmarshal(b, false, func(num protowire.Number, v []byte) error {
err := unmarshalMessageSetField(m, num, v, o) err := o.unmarshalMessageSetField(m, num, v)
if err == errUnknown { if err == errUnknown {
unknown := m.GetUnknown() unknown := m.GetUnknown()
unknown = protowire.AppendTag(unknown, num, protowire.BytesType) unknown = protowire.AppendTag(unknown, num, protowire.BytesType)
@ -68,7 +68,7 @@ func unmarshalMessageSet(b []byte, m protoreflect.Message, o UnmarshalOptions) e
}) })
} }
func unmarshalMessageSetField(m protoreflect.Message, num protowire.Number, v []byte, o UnmarshalOptions) error { func (o UnmarshalOptions) unmarshalMessageSetField(m protoreflect.Message, num protowire.Number, v []byte) error {
md := m.Descriptor() md := m.Descriptor()
if !md.ExtensionRanges().Has(num) { if !md.ExtensionRanges().Has(num) {
return errUnknown return errUnknown

View File

@ -23,10 +23,13 @@ func (o MarshalOptions) Size(m Message) int {
return 0 return 0
} }
return sizeMessage(m.ProtoReflect()) return o.size(m.ProtoReflect())
} }
func sizeMessage(m protoreflect.Message) (size int) { // size is a centralized function that all size operations go through.
// For profiling purposes, avoid changing the name of this function or
// introducing other code paths for size that do not go through this.
func (o MarshalOptions) size(m protoreflect.Message) (size int) {
methods := protoMethods(m) methods := protoMethods(m)
if methods != nil && methods.Size != nil { if methods != nil && methods.Size != nil {
out := methods.Size(protoiface.SizeInput{ out := methods.Size(protoiface.SizeInput{
@ -42,52 +45,52 @@ func sizeMessage(m protoreflect.Message) (size int) {
}) })
return len(out.Buf) return len(out.Buf)
} }
return sizeMessageSlow(m) return o.sizeMessageSlow(m)
} }
func sizeMessageSlow(m protoreflect.Message) (size int) { func (o MarshalOptions) sizeMessageSlow(m protoreflect.Message) (size int) {
if messageset.IsMessageSet(m.Descriptor()) { if messageset.IsMessageSet(m.Descriptor()) {
return sizeMessageSet(m) return o.sizeMessageSet(m)
} }
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
size += sizeField(fd, v) size += o.sizeField(fd, v)
return true return true
}) })
size += len(m.GetUnknown()) size += len(m.GetUnknown())
return size return size
} }
func sizeField(fd protoreflect.FieldDescriptor, value protoreflect.Value) (size int) { func (o MarshalOptions) sizeField(fd protoreflect.FieldDescriptor, value protoreflect.Value) (size int) {
num := fd.Number() num := fd.Number()
switch { switch {
case fd.IsList(): case fd.IsList():
return sizeList(num, fd, value.List()) return o.sizeList(num, fd, value.List())
case fd.IsMap(): case fd.IsMap():
return sizeMap(num, fd, value.Map()) return o.sizeMap(num, fd, value.Map())
default: default:
return protowire.SizeTag(num) + sizeSingular(num, fd.Kind(), value) return protowire.SizeTag(num) + o.sizeSingular(num, fd.Kind(), value)
} }
} }
func sizeList(num protowire.Number, fd protoreflect.FieldDescriptor, list protoreflect.List) (size int) { func (o MarshalOptions) sizeList(num protowire.Number, fd protoreflect.FieldDescriptor, list protoreflect.List) (size int) {
if fd.IsPacked() && list.Len() > 0 { if fd.IsPacked() && list.Len() > 0 {
content := 0 content := 0
for i, llen := 0, list.Len(); i < llen; i++ { for i, llen := 0, list.Len(); i < llen; i++ {
content += sizeSingular(num, fd.Kind(), list.Get(i)) content += o.sizeSingular(num, fd.Kind(), list.Get(i))
} }
return protowire.SizeTag(num) + protowire.SizeBytes(content) return protowire.SizeTag(num) + protowire.SizeBytes(content)
} }
for i, llen := 0, list.Len(); i < llen; i++ { for i, llen := 0, list.Len(); i < llen; i++ {
size += protowire.SizeTag(num) + sizeSingular(num, fd.Kind(), list.Get(i)) size += protowire.SizeTag(num) + o.sizeSingular(num, fd.Kind(), list.Get(i))
} }
return size return size
} }
func sizeMap(num protowire.Number, fd protoreflect.FieldDescriptor, mapv protoreflect.Map) (size int) { func (o MarshalOptions) sizeMap(num protowire.Number, fd protoreflect.FieldDescriptor, mapv protoreflect.Map) (size int) {
mapv.Range(func(key protoreflect.MapKey, value protoreflect.Value) bool { mapv.Range(func(key protoreflect.MapKey, value protoreflect.Value) bool {
size += protowire.SizeTag(num) size += protowire.SizeTag(num)
size += protowire.SizeBytes(sizeField(fd.MapKey(), key.Value()) + sizeField(fd.MapValue(), value)) size += protowire.SizeBytes(o.sizeField(fd.MapKey(), key.Value()) + o.sizeField(fd.MapValue(), value))
return true return true
}) })
return size return size

View File

@ -11,7 +11,7 @@ import (
"google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoreflect"
) )
func sizeSingular(num protowire.Number, kind protoreflect.Kind, v protoreflect.Value) int { func (o MarshalOptions) sizeSingular(num protowire.Number, kind protoreflect.Kind, v protoreflect.Value) int {
switch kind { switch kind {
case protoreflect.BoolKind: case protoreflect.BoolKind:
return protowire.SizeVarint(protowire.EncodeBool(v.Bool())) return protowire.SizeVarint(protowire.EncodeBool(v.Bool()))
@ -46,9 +46,9 @@ func sizeSingular(num protowire.Number, kind protoreflect.Kind, v protoreflect.V
case protoreflect.BytesKind: case protoreflect.BytesKind:
return protowire.SizeBytes(len(v.Bytes())) return protowire.SizeBytes(len(v.Bytes()))
case protoreflect.MessageKind: case protoreflect.MessageKind:
return protowire.SizeBytes(sizeMessage(v.Message())) return protowire.SizeBytes(o.size(v.Message()))
case protoreflect.GroupKind: case protoreflect.GroupKind:
return protowire.SizeGroup(num, sizeMessage(v.Message())) return protowire.SizeGroup(num, o.size(v.Message()))
default: default:
return 0 return 0
} }