mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-02-09 18:40:25 +00:00
The internal/legacy package was originally separated out from internal/impl to avoid a cyclic dependency on descriptor proto. However, the dependency that legacy has on descriptor has long been dropped such that we can now merge the two packages together again. All legacy related logic are in a file with a legacy prefix. Change-Id: I2424fc0f50721696ad06fa7cebb9bdd0babea13c Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/178542 Reviewed-by: Damien Neil <dneil@google.com>
326 lines
10 KiB
Go
326 lines
10 KiB
Go
// Copyright 2018 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"
|
|
"strings"
|
|
"sync"
|
|
"unicode"
|
|
|
|
ptag "google.golang.org/protobuf/internal/encoding/tag"
|
|
ptype "google.golang.org/protobuf/internal/prototype"
|
|
pref "google.golang.org/protobuf/reflect/protoreflect"
|
|
"google.golang.org/protobuf/reflect/prototype"
|
|
)
|
|
|
|
// legacyWrapMessage wraps v as a protoreflect.ProtoMessage,
|
|
// where v must be a *struct kind and not implement the v2 API already.
|
|
func legacyWrapMessage(v reflect.Value) pref.ProtoMessage {
|
|
mt := legacyLoadMessageInfo(v.Type())
|
|
return mt.MessageOf(v.Interface()).Interface()
|
|
}
|
|
|
|
var legacyMessageTypeCache sync.Map // map[reflect.Type]*MessageInfo
|
|
|
|
// legacyLoadMessageInfo dynamically loads a *MessageInfo for t,
|
|
// where t must be a *struct kind and not implement the v2 API already.
|
|
func legacyLoadMessageInfo(t reflect.Type) *MessageInfo {
|
|
// Fast-path: check if a MessageInfo is cached for this concrete type.
|
|
if mt, ok := legacyMessageTypeCache.Load(t); ok {
|
|
return mt.(*MessageInfo)
|
|
}
|
|
|
|
// Slow-path: derive message descriptor and initialize MessageInfo.
|
|
md := LegacyLoadMessageDesc(t)
|
|
mt := new(MessageInfo)
|
|
mt.GoType = t
|
|
mt.PBType = &prototype.Message{
|
|
MessageDescriptor: md,
|
|
NewMessage: func() pref.Message {
|
|
return mt.MessageOf(reflect.New(t.Elem()).Interface())
|
|
},
|
|
}
|
|
if mt, ok := legacyMessageTypeCache.LoadOrStore(t, mt); ok {
|
|
return mt.(*MessageInfo)
|
|
}
|
|
return mt
|
|
}
|
|
|
|
var (
|
|
legacyMessageDescLock sync.Mutex
|
|
legacyMessageDescCache sync.Map // map[reflect.Type]protoreflect.MessageDescriptor
|
|
)
|
|
|
|
// LegacyLoadMessageDesc returns an MessageDescriptor derived from the Go type,
|
|
// which must be a *struct kind and not implement the v2 API already.
|
|
//
|
|
// This is exported for testing purposes.
|
|
func LegacyLoadMessageDesc(t reflect.Type) pref.MessageDescriptor {
|
|
return legacyMessageDescSet{}.Load(t)
|
|
}
|
|
|
|
type legacyMessageDescSet struct {
|
|
visited map[reflect.Type]*ptype.StandaloneMessage
|
|
descs []*ptype.StandaloneMessage
|
|
types []reflect.Type
|
|
}
|
|
|
|
func (ms legacyMessageDescSet) Load(t reflect.Type) pref.MessageDescriptor {
|
|
// Fast-path: check if a MessageDescriptor is cached for this concrete type.
|
|
if mi, ok := legacyMessageDescCache.Load(t); ok {
|
|
return mi.(pref.MessageDescriptor)
|
|
}
|
|
|
|
// Slow-path: initialize MessageDescriptor from the Go type.
|
|
//
|
|
// Hold a global lock during message creation to ensure that each Go type
|
|
// maps to exactly one MessageDescriptor. After obtaining the lock, we must
|
|
// check again whether the message has already been handled.
|
|
legacyMessageDescLock.Lock()
|
|
defer legacyMessageDescLock.Unlock()
|
|
if mi, ok := legacyMessageDescCache.Load(t); ok {
|
|
return mi.(pref.MessageDescriptor)
|
|
}
|
|
|
|
// Processing t recursively populates descs and types with all sub-messages.
|
|
// The descriptor for the first type is guaranteed to be at the front.
|
|
ms.processMessage(t)
|
|
|
|
// Within a proto file it is possible for cyclic dependencies to exist
|
|
// between multiple message types. When these cases arise, the set of
|
|
// message descriptors must be created together.
|
|
mds, err := ptype.NewMessages(ms.descs)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
for i, md := range mds {
|
|
// Protobuf semantics represents map entries under-the-hood as
|
|
// pseudo-messages (has a descriptor, but no generated Go type).
|
|
// Avoid caching these fake messages.
|
|
if t := ms.types[i]; t.Kind() != reflect.Map {
|
|
legacyMessageDescCache.Store(t, md)
|
|
}
|
|
}
|
|
return mds[0]
|
|
}
|
|
|
|
func (ms *legacyMessageDescSet) processMessage(t reflect.Type) pref.MessageDescriptor {
|
|
// Fast-path: Obtain a placeholder if the message is already processed.
|
|
if m, ok := ms.visited[t]; ok {
|
|
return ptype.PlaceholderMessage(m.FullName)
|
|
}
|
|
|
|
// Slow-path: Walk over the struct fields to derive the message descriptor.
|
|
if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct || t.Elem().PkgPath() == "" {
|
|
panic(fmt.Sprintf("got %v, want named *struct kind", t))
|
|
}
|
|
|
|
// Derive name and syntax from the raw descriptor.
|
|
m := new(ptype.StandaloneMessage)
|
|
mv := reflect.New(t.Elem()).Interface()
|
|
if _, ok := mv.(pref.ProtoMessage); ok {
|
|
panic(fmt.Sprintf("%v already implements proto.Message", t))
|
|
}
|
|
if md, ok := mv.(messageV1); ok {
|
|
b, idxs := md.Descriptor()
|
|
fd := legacyLoadFileDesc(b)
|
|
|
|
// Derive syntax.
|
|
switch fd.GetSyntax() {
|
|
case "proto2", "":
|
|
m.Syntax = pref.Proto2
|
|
case "proto3":
|
|
m.Syntax = pref.Proto3
|
|
}
|
|
|
|
// Derive full name.
|
|
md := fd.MessageType[idxs[0]]
|
|
m.FullName = pref.FullName(fd.GetPackage()).Append(pref.Name(md.GetName()))
|
|
for _, i := range idxs[1:] {
|
|
md = md.NestedType[i]
|
|
m.FullName = m.FullName.Append(pref.Name(md.GetName()))
|
|
}
|
|
} else {
|
|
// If the type does not implement messageV1, then the only way to
|
|
// obtain the full name is through the registry. However, this is
|
|
// unreliable as some generated messages register with a fork of
|
|
// golang/protobuf, so the registry may not have this information.
|
|
m.FullName = legacyDeriveFullName(t.Elem())
|
|
m.Syntax = pref.Proto2
|
|
|
|
// Try to determine if the message is using proto3 by checking scalars.
|
|
for i := 0; i < t.Elem().NumField(); i++ {
|
|
f := t.Elem().Field(i)
|
|
if tag := f.Tag.Get("protobuf"); tag != "" {
|
|
switch f.Type.Kind() {
|
|
case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
|
|
m.Syntax = pref.Proto3
|
|
}
|
|
for _, s := range strings.Split(tag, ",") {
|
|
if s == "proto3" {
|
|
m.Syntax = pref.Proto3
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ms.visit(m, t)
|
|
|
|
// Obtain a list of oneof wrapper types.
|
|
var oneofWrappers []reflect.Type
|
|
if fn, ok := t.MethodByName("XXX_OneofFuncs"); ok {
|
|
vs := fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[3]
|
|
for _, v := range vs.Interface().([]interface{}) {
|
|
oneofWrappers = append(oneofWrappers, reflect.TypeOf(v))
|
|
}
|
|
}
|
|
if fn, ok := t.MethodByName("XXX_OneofWrappers"); ok {
|
|
vs := fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[0]
|
|
for _, v := range vs.Interface().([]interface{}) {
|
|
oneofWrappers = append(oneofWrappers, reflect.TypeOf(v))
|
|
}
|
|
}
|
|
|
|
// Obtain a list of the extension ranges.
|
|
if fn, ok := t.MethodByName("ExtensionRangeArray"); ok {
|
|
vs := fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[0]
|
|
for i := 0; i < vs.Len(); i++ {
|
|
v := vs.Index(i)
|
|
m.ExtensionRanges = append(m.ExtensionRanges, [2]pref.FieldNumber{
|
|
pref.FieldNumber(v.FieldByName("Start").Int()),
|
|
pref.FieldNumber(v.FieldByName("End").Int() + 1),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Derive the message fields by inspecting the struct fields.
|
|
for i := 0; i < t.Elem().NumField(); i++ {
|
|
f := t.Elem().Field(i)
|
|
if tag := f.Tag.Get("protobuf"); tag != "" {
|
|
tagKey := f.Tag.Get("protobuf_key")
|
|
tagVal := f.Tag.Get("protobuf_val")
|
|
m.Fields = append(m.Fields, ms.parseField(tag, tagKey, tagVal, f.Type, m))
|
|
}
|
|
if tag := f.Tag.Get("protobuf_oneof"); tag != "" {
|
|
name := pref.Name(tag)
|
|
m.Oneofs = append(m.Oneofs, ptype.Oneof{Name: name})
|
|
for _, t := range oneofWrappers {
|
|
if t.Implements(f.Type) {
|
|
f := t.Elem().Field(0)
|
|
if tag := f.Tag.Get("protobuf"); tag != "" {
|
|
ft := ms.parseField(tag, "", "", f.Type, m)
|
|
ft.OneofName = name
|
|
m.Fields = append(m.Fields, ft)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ptype.PlaceholderMessage(m.FullName)
|
|
}
|
|
|
|
func (ms *legacyMessageDescSet) parseField(tag, tagKey, tagVal string, goType reflect.Type, parent *ptype.StandaloneMessage) ptype.Field {
|
|
t := goType
|
|
isOptional := t.Kind() == reflect.Ptr && t.Elem().Kind() != reflect.Struct
|
|
isRepeated := t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
|
|
if isOptional || isRepeated {
|
|
t = t.Elem()
|
|
}
|
|
f := ptag.Unmarshal(tag, t)
|
|
|
|
// Populate EnumType and MessageType.
|
|
if f.EnumType == nil && f.Kind == pref.EnumKind {
|
|
if ev, ok := reflect.Zero(t).Interface().(pref.Enum); ok {
|
|
f.EnumType = ev.Descriptor()
|
|
} else {
|
|
f.EnumType = LegacyLoadEnumDesc(t)
|
|
}
|
|
}
|
|
if f.MessageType == nil && (f.Kind == pref.MessageKind || f.Kind == pref.GroupKind) {
|
|
if mv, ok := reflect.Zero(t).Interface().(pref.ProtoMessage); ok {
|
|
f.MessageType = mv.ProtoReflect().Descriptor()
|
|
} else if t.Kind() == reflect.Map {
|
|
m := &ptype.StandaloneMessage{
|
|
Syntax: parent.Syntax,
|
|
FullName: parent.FullName.Append(legacyMapEntryName(f.Name)),
|
|
IsMapEntry: true,
|
|
Fields: []ptype.Field{
|
|
ms.parseField(tagKey, "", "", t.Key(), nil),
|
|
ms.parseField(tagVal, "", "", t.Elem(), nil),
|
|
},
|
|
}
|
|
ms.visit(m, t)
|
|
f.MessageType = ptype.PlaceholderMessage(m.FullName)
|
|
} else if mv, ok := legacyMessageDescCache.Load(t); ok {
|
|
f.MessageType = mv.(pref.MessageDescriptor)
|
|
} else {
|
|
f.MessageType = ms.processMessage(t)
|
|
}
|
|
}
|
|
return f
|
|
}
|
|
|
|
func (ms *legacyMessageDescSet) visit(m *ptype.StandaloneMessage, t reflect.Type) {
|
|
if ms.visited == nil {
|
|
ms.visited = make(map[reflect.Type]*ptype.StandaloneMessage)
|
|
}
|
|
if t.Kind() != reflect.Map {
|
|
ms.visited[t] = m
|
|
}
|
|
ms.descs = append(ms.descs, m)
|
|
ms.types = append(ms.types, t)
|
|
}
|
|
|
|
// legacyDeriveFullName derives a fully qualified protobuf name for the given Go type
|
|
// The provided name is not guaranteed to be stable nor universally unique.
|
|
// It should be sufficiently unique within a program.
|
|
func legacyDeriveFullName(t reflect.Type) pref.FullName {
|
|
sanitize := func(r rune) rune {
|
|
switch {
|
|
case r == '/':
|
|
return '.'
|
|
case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', '0' <= r && r <= '9':
|
|
return r
|
|
default:
|
|
return '_'
|
|
}
|
|
}
|
|
prefix := strings.Map(sanitize, t.PkgPath())
|
|
suffix := strings.Map(sanitize, t.Name())
|
|
if suffix == "" {
|
|
suffix = fmt.Sprintf("UnknownX%X", reflect.ValueOf(t).Pointer())
|
|
}
|
|
|
|
ss := append(strings.Split(prefix, "."), suffix)
|
|
for i, s := range ss {
|
|
if s == "" || ('0' <= s[0] && s[0] <= '9') {
|
|
ss[i] = "x" + s
|
|
}
|
|
}
|
|
return pref.FullName(strings.Join(ss, "."))
|
|
}
|
|
|
|
// legacyMapEntryName derives the message name for a map field of a given name.
|
|
// This is identical to MapEntryName from parser.cc in the protoc source.
|
|
func legacyMapEntryName(s pref.Name) pref.Name {
|
|
var b []byte
|
|
nextUpper := true
|
|
for i := 0; i < len(s); i++ {
|
|
if c := s[i]; c == '_' {
|
|
nextUpper = true
|
|
} else {
|
|
if nextUpper {
|
|
c = byte(unicode.ToUpper(rune(c)))
|
|
nextUpper = false
|
|
}
|
|
b = append(b, c)
|
|
}
|
|
}
|
|
return pref.Name(append(b, "Entry"...))
|
|
}
|