| // 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 filetype provides functionality for wrapping descriptors |
| // with Go type information. |
| package filetype |
| |
| import ( |
| "reflect" |
| |
| "google.golang.org/protobuf/internal/descopts" |
| fdesc "google.golang.org/protobuf/internal/filedesc" |
| pimpl "google.golang.org/protobuf/internal/impl" |
| pref "google.golang.org/protobuf/reflect/protoreflect" |
| preg "google.golang.org/protobuf/reflect/protoregistry" |
| ) |
| |
| // Builder constructs type descriptors from a raw file descriptor |
| // and associated Go types for each enum and message declaration. |
| // |
| // |
| // Flattened Ordering |
| // |
| // The protobuf type system represents declarations as a tree. Certain nodes in |
| // the tree require us to either associate it with a concrete Go type or to |
| // resolve a dependency, which is information that must be provided separately |
| // since it cannot be derived from the file descriptor alone. |
| // |
| // However, representing a tree as Go literals is difficult to simply do in a |
| // space and time efficient way. Thus, we store them as a flattened list of |
| // objects where the serialization order from the tree-based form is important. |
| // |
| // The "flattened ordering" is defined as a tree traversal of all enum, message, |
| // extension, and service declarations using the following algorithm: |
| // |
| // def VisitFileDecls(fd): |
| // for e in fd.Enums: yield e |
| // for m in fd.Messages: yield m |
| // for x in fd.Extensions: yield x |
| // for s in fd.Services: yield s |
| // for m in fd.Messages: yield from VisitMessageDecls(m) |
| // |
| // def VisitMessageDecls(md): |
| // for e in md.Enums: yield e |
| // for m in md.Messages: yield m |
| // for x in md.Extensions: yield x |
| // for m in md.Messages: yield from VisitMessageDecls(m) |
| // |
| // The traversal starts at the root file descriptor and yields each direct |
| // declaration within each node before traversing into sub-declarations |
| // that children themselves may have. |
| type Builder struct { |
| // File is the underlying file descriptor builder. |
| File fdesc.Builder |
| |
| // GoTypes is a unique set of the Go types for all declarations and |
| // dependencies. Each type is represented as a zero value of the Go type. |
| // |
| // Declarations are Go types generated for enums and messages directly |
| // declared (not publicly imported) in the proto source file. |
| // Messages for map entries are accounted for, but represented by nil. |
| // Enum declarations in "flattened ordering" come first, followed by |
| // message declarations in "flattened ordering". |
| // |
| // Dependencies are Go types for enums or messages referenced by |
| // message fields (excluding weak fields), for parent extended messages of |
| // extension fields, for enums or messages referenced by extension fields, |
| // and for input and output messages referenced by service methods. |
| // Dependencies must come after declarations, but the ordering of |
| // dependencies themselves is unspecified. |
| GoTypes []interface{} |
| |
| // DependencyIndexes is an ordered list of indexes into GoTypes for the |
| // dependencies of messages, extensions, or services. |
| // |
| // There are 5 sub-lists in "flattened ordering" concatenated back-to-back: |
| // 0. Message field dependencies: list of the enum or message type |
| // referred to by every message field. |
| // 1. Extension field targets: list of the extended parent message of |
| // every extension. |
| // 2. Extension field dependencies: list of the enum or message type |
| // referred to by every extension field. |
| // 3. Service method inputs: list of the input message type |
| // referred to by every service method. |
| // 4. Service method outputs: list of the output message type |
| // referred to by every service method. |
| // |
| // The offset into DependencyIndexes for the start of each sub-list |
| // is appended to the end in reverse order. |
| DependencyIndexes []int32 |
| |
| // EnumInfos is a list of enum infos in "flattened ordering". |
| EnumInfos []pimpl.EnumInfo |
| |
| // MessageInfos is a list of message infos in "flattened ordering". |
| // If provided, the GoType and PBType for each element is populated. |
| // |
| // Requirement: len(MessageInfos) == len(Build.Messages) |
| MessageInfos []pimpl.MessageInfo |
| |
| // ExtensionInfos is a list of extension infos in "flattened ordering". |
| // Each element is initialized and registered with the protoregistry package. |
| // |
| // Requirement: len(LegacyExtensions) == len(Build.Extensions) |
| ExtensionInfos []pimpl.ExtensionInfo |
| |
| // TypeRegistry is the registry to register each type descriptor. |
| // If nil, it uses protoregistry.GlobalTypes. |
| TypeRegistry interface { |
| RegisterMessage(pref.MessageType) error |
| RegisterEnum(pref.EnumType) error |
| RegisterExtension(pref.ExtensionType) error |
| } |
| } |
| |
| // Out is the output of the builder. |
| type Out struct { |
| File pref.FileDescriptor |
| } |
| |
| func (tb Builder) Build() (out Out) { |
| // Replace the resolver with one that resolves dependencies by index, |
| // which is faster and more reliable than relying on the global registry. |
| if tb.File.FileRegistry == nil { |
| tb.File.FileRegistry = preg.GlobalFiles |
| } |
| tb.File.FileRegistry = &resolverByIndex{ |
| goTypes: tb.GoTypes, |
| depIdxs: tb.DependencyIndexes, |
| fileRegistry: tb.File.FileRegistry, |
| } |
| |
| // Initialize registry if unpopulated. |
| if tb.TypeRegistry == nil { |
| tb.TypeRegistry = preg.GlobalTypes |
| } |
| |
| fbOut := tb.File.Build() |
| out.File = fbOut.File |
| |
| // Process enums. |
| enumGoTypes := tb.GoTypes[:len(fbOut.Enums)] |
| if len(tb.EnumInfos) != len(fbOut.Enums) { |
| panic("mismatching enum lengths") |
| } |
| if len(fbOut.Enums) > 0 { |
| for i := range fbOut.Enums { |
| tb.EnumInfos[i] = pimpl.EnumInfo{ |
| GoReflectType: reflect.TypeOf(enumGoTypes[i]), |
| Desc: &fbOut.Enums[i], |
| } |
| // Register enum types. |
| if err := tb.TypeRegistry.RegisterEnum(&tb.EnumInfos[i]); err != nil { |
| panic(err) |
| } |
| } |
| } |
| |
| // Process messages. |
| messageGoTypes := tb.GoTypes[len(fbOut.Enums):][:len(fbOut.Messages)] |
| if len(tb.MessageInfos) != len(fbOut.Messages) { |
| panic("mismatching message lengths") |
| } |
| if len(fbOut.Messages) > 0 { |
| for i := range fbOut.Messages { |
| if messageGoTypes[i] == nil { |
| continue // skip map entry |
| } |
| |
| tb.MessageInfos[i].GoReflectType = reflect.TypeOf(messageGoTypes[i]) |
| tb.MessageInfos[i].Desc = &fbOut.Messages[i] |
| |
| // Register message types. |
| if err := tb.TypeRegistry.RegisterMessage(&tb.MessageInfos[i]); err != nil { |
| panic(err) |
| } |
| } |
| |
| // As a special-case for descriptor.proto, |
| // locally register concrete message type for the options. |
| if out.File.Path() == "google/protobuf/descriptor.proto" && out.File.Package() == "google.protobuf" { |
| for i := range fbOut.Messages { |
| switch fbOut.Messages[i].Name() { |
| case "FileOptions": |
| descopts.File = messageGoTypes[i].(pref.ProtoMessage) |
| case "EnumOptions": |
| descopts.Enum = messageGoTypes[i].(pref.ProtoMessage) |
| case "EnumValueOptions": |
| descopts.EnumValue = messageGoTypes[i].(pref.ProtoMessage) |
| case "MessageOptions": |
| descopts.Message = messageGoTypes[i].(pref.ProtoMessage) |
| case "FieldOptions": |
| descopts.Field = messageGoTypes[i].(pref.ProtoMessage) |
| case "OneofOptions": |
| descopts.Oneof = messageGoTypes[i].(pref.ProtoMessage) |
| case "ExtensionRangeOptions": |
| descopts.ExtensionRange = messageGoTypes[i].(pref.ProtoMessage) |
| case "ServiceOptions": |
| descopts.Service = messageGoTypes[i].(pref.ProtoMessage) |
| case "MethodOptions": |
| descopts.Method = messageGoTypes[i].(pref.ProtoMessage) |
| } |
| } |
| } |
| } |
| |
| // Process extensions. |
| if len(tb.ExtensionInfos) != len(fbOut.Extensions) { |
| panic("mismatching extension lengths") |
| } |
| var depIdx int32 |
| for i := range fbOut.Extensions { |
| // For enum and message kinds, determine the referent Go type so |
| // that we can construct their constructors. |
| const listExtDeps = 2 |
| var goType reflect.Type |
| switch fbOut.Extensions[i].L1.Kind { |
| case pref.EnumKind: |
| j := depIdxs.Get(tb.DependencyIndexes, listExtDeps, depIdx) |
| goType = reflect.TypeOf(tb.GoTypes[j]) |
| depIdx++ |
| case pref.MessageKind, pref.GroupKind: |
| j := depIdxs.Get(tb.DependencyIndexes, listExtDeps, depIdx) |
| goType = reflect.TypeOf(tb.GoTypes[j]) |
| depIdx++ |
| default: |
| goType = goTypeForPBKind[fbOut.Extensions[i].L1.Kind] |
| } |
| if fbOut.Extensions[i].IsList() { |
| goType = reflect.SliceOf(goType) |
| } |
| |
| pimpl.InitExtensionInfo(&tb.ExtensionInfos[i], &fbOut.Extensions[i], goType) |
| |
| // Register extension types. |
| if err := tb.TypeRegistry.RegisterExtension(&tb.ExtensionInfos[i]); err != nil { |
| panic(err) |
| } |
| } |
| |
| return out |
| } |
| |
| var goTypeForPBKind = map[pref.Kind]reflect.Type{ |
| pref.BoolKind: reflect.TypeOf(bool(false)), |
| pref.Int32Kind: reflect.TypeOf(int32(0)), |
| pref.Sint32Kind: reflect.TypeOf(int32(0)), |
| pref.Sfixed32Kind: reflect.TypeOf(int32(0)), |
| pref.Int64Kind: reflect.TypeOf(int64(0)), |
| pref.Sint64Kind: reflect.TypeOf(int64(0)), |
| pref.Sfixed64Kind: reflect.TypeOf(int64(0)), |
| pref.Uint32Kind: reflect.TypeOf(uint32(0)), |
| pref.Fixed32Kind: reflect.TypeOf(uint32(0)), |
| pref.Uint64Kind: reflect.TypeOf(uint64(0)), |
| pref.Fixed64Kind: reflect.TypeOf(uint64(0)), |
| pref.FloatKind: reflect.TypeOf(float32(0)), |
| pref.DoubleKind: reflect.TypeOf(float64(0)), |
| pref.StringKind: reflect.TypeOf(string("")), |
| pref.BytesKind: reflect.TypeOf([]byte(nil)), |
| } |
| |
| type depIdxs []int32 |
| |
| // Get retrieves the jth element of the ith sub-list. |
| func (x depIdxs) Get(i, j int32) int32 { |
| return x[x[int32(len(x))-i-1]+j] |
| } |
| |
| type ( |
| resolverByIndex struct { |
| goTypes []interface{} |
| depIdxs depIdxs |
| fileRegistry |
| } |
| fileRegistry interface { |
| FindFileByPath(string) (pref.FileDescriptor, error) |
| FindDescriptorByName(pref.FullName) (pref.Descriptor, error) |
| RegisterFile(pref.FileDescriptor) error |
| } |
| ) |
| |
| func (r *resolverByIndex) FindEnumByIndex(i, j int32, es []fdesc.Enum, ms []fdesc.Message) pref.EnumDescriptor { |
| if depIdx := int(r.depIdxs.Get(i, j)); int(depIdx) < len(es)+len(ms) { |
| return &es[depIdx] |
| } else { |
| return pimpl.Export{}.EnumDescriptorOf(r.goTypes[depIdx]) |
| } |
| } |
| |
| func (r *resolverByIndex) FindMessageByIndex(i, j int32, es []fdesc.Enum, ms []fdesc.Message) pref.MessageDescriptor { |
| if depIdx := int(r.depIdxs.Get(i, j)); depIdx < len(es)+len(ms) { |
| return &ms[depIdx-len(es)] |
| } else { |
| return pimpl.Export{}.MessageDescriptorOf(r.goTypes[depIdx]) |
| } |
| } |