mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-03-31 01:20:38 +00:00
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:
parent
0fbe4ed572
commit
118baf6390
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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 == "" {
|
||||||
|
@ -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}}
|
||||||
|
@ -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 {
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user