mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-02-06 09:40:07 +00:00
23ccb359e1
Implement support in the protobuf runtime to better understand message types that are not generated by the official generator. In particular: * Add a best-effort implementation of protobuf reflection for "non-nullable" fields which are supposed to be represented by *T, but are instead represented by a T. "Non-nullable" message fields report presence based on whether the message is the zero Go value. * We do NOT implement support for "non-nullable" fields in the table-driven implementation since we assume that the aberrant messages that we care about have a Marshal and Unmarshal method. * We better handle custom messages that implement Marshal and Unmarshal, but do NOT implement Merge. In that case, we implement merge in terms of a back-to-back marshal and unmarshal. * We better tolerate the situations where a protobuf message field cannot be mapped to a Go struct field since the latter is missing. In such cases, reflection treats the field as if it were unpopulated. Setting such fields will panic. This change allows the runtime to handle all message types declared in the "go.etcd.io/etcd" and "k8s.io" modules where protobuf reflection, Marshal, Unmarshal, Reset, Merge, and Equal all work. The only types that still do not fully work are: * "k8s.io/api/authentication/v1".ExtraValue * "k8s.io/api/authentication/v1beta1".ExtraValue * "k8s.io/api/authorization/v1".ExtraValue * "k8s.io/api/authorization/v1beta1".ExtraValue * "k8s.io/api/certificates/v1".ExtraValue * "k8s.io/api/certificates/v1beta1".ExtraValue * "k8s.io/apimachinery/pkg/apis/meta/v1".MicroTime * "k8s.io/apimachinery/pkg/apis/meta/v1".Time * "k8s.io/apimachinery/pkg/apis/meta/v1".Verbs While Marshal, Unmarshal, Reset, and Merge continue to work, protobuf reflection and any functionality that depends on it (e.g., prototext, protojson, Equal, etc.) will not work. Change-Id: I67a9d2f1bec35248045ad0c16220d02fc2e0e172 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/300869 Trust: Joe Tsai <joetsai@digital-static.net> Trust: Joe Tsai <thebrokentoaster@gmail.com> Reviewed-by: Damien Neil <dneil@google.com>
218 lines
7.0 KiB
Go
218 lines
7.0 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 impl
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
|
|
"google.golang.org/protobuf/encoding/protowire"
|
|
"google.golang.org/protobuf/internal/encoding/messageset"
|
|
"google.golang.org/protobuf/internal/order"
|
|
pref "google.golang.org/protobuf/reflect/protoreflect"
|
|
piface "google.golang.org/protobuf/runtime/protoiface"
|
|
)
|
|
|
|
// coderMessageInfo contains per-message information used by the fast-path functions.
|
|
// This is a different type from MessageInfo to keep MessageInfo as general-purpose as
|
|
// possible.
|
|
type coderMessageInfo struct {
|
|
methods piface.Methods
|
|
|
|
orderedCoderFields []*coderFieldInfo
|
|
denseCoderFields []*coderFieldInfo
|
|
coderFields map[protowire.Number]*coderFieldInfo
|
|
sizecacheOffset offset
|
|
unknownOffset offset
|
|
unknownPtrKind bool
|
|
extensionOffset offset
|
|
needsInitCheck bool
|
|
isMessageSet bool
|
|
numRequiredFields uint8
|
|
}
|
|
|
|
type coderFieldInfo struct {
|
|
funcs pointerCoderFuncs // fast-path per-field functions
|
|
mi *MessageInfo // field's message
|
|
ft reflect.Type
|
|
validation validationInfo // information used by message validation
|
|
num pref.FieldNumber // field number
|
|
offset offset // struct field offset
|
|
wiretag uint64 // field tag (number + wire type)
|
|
tagsize int // size of the varint-encoded tag
|
|
isPointer bool // true if IsNil may be called on the struct field
|
|
isRequired bool // true if field is required
|
|
}
|
|
|
|
func (mi *MessageInfo) makeCoderMethods(t reflect.Type, si structInfo) {
|
|
mi.sizecacheOffset = invalidOffset
|
|
mi.unknownOffset = invalidOffset
|
|
mi.extensionOffset = invalidOffset
|
|
|
|
if si.sizecacheOffset.IsValid() && si.sizecacheType == sizecacheType {
|
|
mi.sizecacheOffset = si.sizecacheOffset
|
|
}
|
|
if si.unknownOffset.IsValid() && (si.unknownType == unknownFieldsAType || si.unknownType == unknownFieldsBType) {
|
|
mi.unknownOffset = si.unknownOffset
|
|
mi.unknownPtrKind = si.unknownType.Kind() == reflect.Ptr
|
|
}
|
|
if si.extensionOffset.IsValid() && si.extensionType == extensionFieldsType {
|
|
mi.extensionOffset = si.extensionOffset
|
|
}
|
|
|
|
mi.coderFields = make(map[protowire.Number]*coderFieldInfo)
|
|
fields := mi.Desc.Fields()
|
|
preallocFields := make([]coderFieldInfo, fields.Len())
|
|
for i := 0; i < fields.Len(); i++ {
|
|
fd := fields.Get(i)
|
|
|
|
fs := si.fieldsByNumber[fd.Number()]
|
|
isOneof := fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic()
|
|
if isOneof {
|
|
fs = si.oneofsByName[fd.ContainingOneof().Name()]
|
|
}
|
|
ft := fs.Type
|
|
var wiretag uint64
|
|
if !fd.IsPacked() {
|
|
wiretag = protowire.EncodeTag(fd.Number(), wireTypes[fd.Kind()])
|
|
} else {
|
|
wiretag = protowire.EncodeTag(fd.Number(), protowire.BytesType)
|
|
}
|
|
var fieldOffset offset
|
|
var funcs pointerCoderFuncs
|
|
var childMessage *MessageInfo
|
|
switch {
|
|
case ft == nil:
|
|
// This never occurs for generated message types.
|
|
// It implies that a hand-crafted type has missing Go fields
|
|
// for specific protobuf message fields.
|
|
funcs = pointerCoderFuncs{
|
|
size: func(p pointer, f *coderFieldInfo, opts marshalOptions) int {
|
|
return 0
|
|
},
|
|
marshal: func(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) {
|
|
return nil, nil
|
|
},
|
|
unmarshal: func(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (unmarshalOutput, error) {
|
|
panic("missing Go struct field for " + string(fd.FullName()))
|
|
},
|
|
isInit: func(p pointer, f *coderFieldInfo) error {
|
|
panic("missing Go struct field for " + string(fd.FullName()))
|
|
},
|
|
merge: func(dst, src pointer, f *coderFieldInfo, opts mergeOptions) {
|
|
panic("missing Go struct field for " + string(fd.FullName()))
|
|
},
|
|
}
|
|
case isOneof:
|
|
fieldOffset = offsetOf(fs, mi.Exporter)
|
|
case fd.IsWeak():
|
|
fieldOffset = si.weakOffset
|
|
funcs = makeWeakMessageFieldCoder(fd)
|
|
default:
|
|
fieldOffset = offsetOf(fs, mi.Exporter)
|
|
childMessage, funcs = fieldCoder(fd, ft)
|
|
}
|
|
cf := &preallocFields[i]
|
|
*cf = coderFieldInfo{
|
|
num: fd.Number(),
|
|
offset: fieldOffset,
|
|
wiretag: wiretag,
|
|
ft: ft,
|
|
tagsize: protowire.SizeVarint(wiretag),
|
|
funcs: funcs,
|
|
mi: childMessage,
|
|
validation: newFieldValidationInfo(mi, si, fd, ft),
|
|
isPointer: fd.Cardinality() == pref.Repeated || fd.HasPresence(),
|
|
isRequired: fd.Cardinality() == pref.Required,
|
|
}
|
|
mi.orderedCoderFields = append(mi.orderedCoderFields, cf)
|
|
mi.coderFields[cf.num] = cf
|
|
}
|
|
for i, oneofs := 0, mi.Desc.Oneofs(); i < oneofs.Len(); i++ {
|
|
if od := oneofs.Get(i); !od.IsSynthetic() {
|
|
mi.initOneofFieldCoders(od, si)
|
|
}
|
|
}
|
|
if messageset.IsMessageSet(mi.Desc) {
|
|
if !mi.extensionOffset.IsValid() {
|
|
panic(fmt.Sprintf("%v: MessageSet with no extensions field", mi.Desc.FullName()))
|
|
}
|
|
if !mi.unknownOffset.IsValid() {
|
|
panic(fmt.Sprintf("%v: MessageSet with no unknown field", mi.Desc.FullName()))
|
|
}
|
|
mi.isMessageSet = true
|
|
}
|
|
sort.Slice(mi.orderedCoderFields, func(i, j int) bool {
|
|
return mi.orderedCoderFields[i].num < mi.orderedCoderFields[j].num
|
|
})
|
|
|
|
var maxDense pref.FieldNumber
|
|
for _, cf := range mi.orderedCoderFields {
|
|
if cf.num >= 16 && cf.num >= 2*maxDense {
|
|
break
|
|
}
|
|
maxDense = cf.num
|
|
}
|
|
mi.denseCoderFields = make([]*coderFieldInfo, maxDense+1)
|
|
for _, cf := range mi.orderedCoderFields {
|
|
if int(cf.num) >= len(mi.denseCoderFields) {
|
|
break
|
|
}
|
|
mi.denseCoderFields[cf.num] = cf
|
|
}
|
|
|
|
// To preserve compatibility with historic wire output, marshal oneofs last.
|
|
if mi.Desc.Oneofs().Len() > 0 {
|
|
sort.Slice(mi.orderedCoderFields, func(i, j int) bool {
|
|
fi := fields.ByNumber(mi.orderedCoderFields[i].num)
|
|
fj := fields.ByNumber(mi.orderedCoderFields[j].num)
|
|
return order.LegacyFieldOrder(fi, fj)
|
|
})
|
|
}
|
|
|
|
mi.needsInitCheck = needsInitCheck(mi.Desc)
|
|
if mi.methods.Marshal == nil && mi.methods.Size == nil {
|
|
mi.methods.Flags |= piface.SupportMarshalDeterministic
|
|
mi.methods.Marshal = mi.marshal
|
|
mi.methods.Size = mi.size
|
|
}
|
|
if mi.methods.Unmarshal == nil {
|
|
mi.methods.Flags |= piface.SupportUnmarshalDiscardUnknown
|
|
mi.methods.Unmarshal = mi.unmarshal
|
|
}
|
|
if mi.methods.CheckInitialized == nil {
|
|
mi.methods.CheckInitialized = mi.checkInitialized
|
|
}
|
|
if mi.methods.Merge == nil {
|
|
mi.methods.Merge = mi.merge
|
|
}
|
|
}
|
|
|
|
// getUnknownBytes returns a *[]byte for the unknown fields.
|
|
// It is the caller's responsibility to check whether the pointer is nil.
|
|
// This function is specially designed to be inlineable.
|
|
func (mi *MessageInfo) getUnknownBytes(p pointer) *[]byte {
|
|
if mi.unknownPtrKind {
|
|
return *p.Apply(mi.unknownOffset).BytesPtr()
|
|
} else {
|
|
return p.Apply(mi.unknownOffset).Bytes()
|
|
}
|
|
}
|
|
|
|
// mutableUnknownBytes returns a *[]byte for the unknown fields.
|
|
// The returned pointer is guaranteed to not be nil.
|
|
func (mi *MessageInfo) mutableUnknownBytes(p pointer) *[]byte {
|
|
if mi.unknownPtrKind {
|
|
bp := p.Apply(mi.unknownOffset).BytesPtr()
|
|
if *bp == nil {
|
|
*bp = new([]byte)
|
|
}
|
|
return *bp
|
|
} else {
|
|
return p.Apply(mi.unknownOffset).Bytes()
|
|
}
|
|
}
|