1// Copyright 2019 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package filetype provides functionality for wrapping descriptors 6// with Go type information. 7package filetype 8 9import ( 10 "reflect" 11 12 "google.golang.org/protobuf/internal/descopts" 13 fdesc "google.golang.org/protobuf/internal/filedesc" 14 pimpl "google.golang.org/protobuf/internal/impl" 15 pref "google.golang.org/protobuf/reflect/protoreflect" 16 preg "google.golang.org/protobuf/reflect/protoregistry" 17) 18 19// Builder constructs type descriptors from a raw file descriptor 20// and associated Go types for each enum and message declaration. 21// 22// 23// Flattened Ordering 24// 25// The protobuf type system represents declarations as a tree. Certain nodes in 26// the tree require us to either associate it with a concrete Go type or to 27// resolve a dependency, which is information that must be provided separately 28// since it cannot be derived from the file descriptor alone. 29// 30// However, representing a tree as Go literals is difficult to simply do in a 31// space and time efficient way. Thus, we store them as a flattened list of 32// objects where the serialization order from the tree-based form is important. 33// 34// The "flattened ordering" is defined as a tree traversal of all enum, message, 35// extension, and service declarations using the following algorithm: 36// 37// def VisitFileDecls(fd): 38// for e in fd.Enums: yield e 39// for m in fd.Messages: yield m 40// for x in fd.Extensions: yield x 41// for s in fd.Services: yield s 42// for m in fd.Messages: yield from VisitMessageDecls(m) 43// 44// def VisitMessageDecls(md): 45// for e in md.Enums: yield e 46// for m in md.Messages: yield m 47// for x in md.Extensions: yield x 48// for m in md.Messages: yield from VisitMessageDecls(m) 49// 50// The traversal starts at the root file descriptor and yields each direct 51// declaration within each node before traversing into sub-declarations 52// that children themselves may have. 53type Builder struct { 54 // File is the underlying file descriptor builder. 55 File fdesc.Builder 56 57 // GoTypes is a unique set of the Go types for all declarations and 58 // dependencies. Each type is represented as a zero value of the Go type. 59 // 60 // Declarations are Go types generated for enums and messages directly 61 // declared (not publicly imported) in the proto source file. 62 // Messages for map entries are accounted for, but represented by nil. 63 // Enum declarations in "flattened ordering" come first, followed by 64 // message declarations in "flattened ordering". 65 // 66 // Dependencies are Go types for enums or messages referenced by 67 // message fields (excluding weak fields), for parent extended messages of 68 // extension fields, for enums or messages referenced by extension fields, 69 // and for input and output messages referenced by service methods. 70 // Dependencies must come after declarations, but the ordering of 71 // dependencies themselves is unspecified. 72 GoTypes []interface{} 73 74 // DependencyIndexes is an ordered list of indexes into GoTypes for the 75 // dependencies of messages, extensions, or services. 76 // 77 // There are 5 sub-lists in "flattened ordering" concatenated back-to-back: 78 // 0. Message field dependencies: list of the enum or message type 79 // referred to by every message field. 80 // 1. Extension field targets: list of the extended parent message of 81 // every extension. 82 // 2. Extension field dependencies: list of the enum or message type 83 // referred to by every extension field. 84 // 3. Service method inputs: list of the input message type 85 // referred to by every service method. 86 // 4. Service method outputs: list of the output message type 87 // referred to by every service method. 88 // 89 // The offset into DependencyIndexes for the start of each sub-list 90 // is appended to the end in reverse order. 91 DependencyIndexes []int32 92 93 // EnumInfos is a list of enum infos in "flattened ordering". 94 EnumInfos []pimpl.EnumInfo 95 96 // MessageInfos is a list of message infos in "flattened ordering". 97 // If provided, the GoType and PBType for each element is populated. 98 // 99 // Requirement: len(MessageInfos) == len(Build.Messages) 100 MessageInfos []pimpl.MessageInfo 101 102 // ExtensionInfos is a list of extension infos in "flattened ordering". 103 // Each element is initialized and registered with the protoregistry package. 104 // 105 // Requirement: len(LegacyExtensions) == len(Build.Extensions) 106 ExtensionInfos []pimpl.ExtensionInfo 107 108 // TypeRegistry is the registry to register each type descriptor. 109 // If nil, it uses protoregistry.GlobalTypes. 110 TypeRegistry interface { 111 RegisterMessage(pref.MessageType) error 112 RegisterEnum(pref.EnumType) error 113 RegisterExtension(pref.ExtensionType) error 114 } 115} 116 117// Out is the output of the builder. 118type Out struct { 119 File pref.FileDescriptor 120} 121 122func (tb Builder) Build() (out Out) { 123 // Replace the resolver with one that resolves dependencies by index, 124 // which is faster and more reliable than relying on the global registry. 125 if tb.File.FileRegistry == nil { 126 tb.File.FileRegistry = preg.GlobalFiles 127 } 128 tb.File.FileRegistry = &resolverByIndex{ 129 goTypes: tb.GoTypes, 130 depIdxs: tb.DependencyIndexes, 131 fileRegistry: tb.File.FileRegistry, 132 } 133 134 // Initialize registry if unpopulated. 135 if tb.TypeRegistry == nil { 136 tb.TypeRegistry = preg.GlobalTypes 137 } 138 139 fbOut := tb.File.Build() 140 out.File = fbOut.File 141 142 // Process enums. 143 enumGoTypes := tb.GoTypes[:len(fbOut.Enums)] 144 if len(tb.EnumInfos) != len(fbOut.Enums) { 145 panic("mismatching enum lengths") 146 } 147 if len(fbOut.Enums) > 0 { 148 for i := range fbOut.Enums { 149 tb.EnumInfos[i] = pimpl.EnumInfo{ 150 GoReflectType: reflect.TypeOf(enumGoTypes[i]), 151 Desc: &fbOut.Enums[i], 152 } 153 // Register enum types. 154 if err := tb.TypeRegistry.RegisterEnum(&tb.EnumInfos[i]); err != nil { 155 panic(err) 156 } 157 } 158 } 159 160 // Process messages. 161 messageGoTypes := tb.GoTypes[len(fbOut.Enums):][:len(fbOut.Messages)] 162 if len(tb.MessageInfos) != len(fbOut.Messages) { 163 panic("mismatching message lengths") 164 } 165 if len(fbOut.Messages) > 0 { 166 for i := range fbOut.Messages { 167 if messageGoTypes[i] == nil { 168 continue // skip map entry 169 } 170 171 tb.MessageInfos[i].GoReflectType = reflect.TypeOf(messageGoTypes[i]) 172 tb.MessageInfos[i].Desc = &fbOut.Messages[i] 173 174 // Register message types. 175 if err := tb.TypeRegistry.RegisterMessage(&tb.MessageInfos[i]); err != nil { 176 panic(err) 177 } 178 } 179 180 // As a special-case for descriptor.proto, 181 // locally register concrete message type for the options. 182 if out.File.Path() == "google/protobuf/descriptor.proto" && out.File.Package() == "google.protobuf" { 183 for i := range fbOut.Messages { 184 switch fbOut.Messages[i].Name() { 185 case "FileOptions": 186 descopts.File = messageGoTypes[i].(pref.ProtoMessage) 187 case "EnumOptions": 188 descopts.Enum = messageGoTypes[i].(pref.ProtoMessage) 189 case "EnumValueOptions": 190 descopts.EnumValue = messageGoTypes[i].(pref.ProtoMessage) 191 case "MessageOptions": 192 descopts.Message = messageGoTypes[i].(pref.ProtoMessage) 193 case "FieldOptions": 194 descopts.Field = messageGoTypes[i].(pref.ProtoMessage) 195 case "OneofOptions": 196 descopts.Oneof = messageGoTypes[i].(pref.ProtoMessage) 197 case "ExtensionRangeOptions": 198 descopts.ExtensionRange = messageGoTypes[i].(pref.ProtoMessage) 199 case "ServiceOptions": 200 descopts.Service = messageGoTypes[i].(pref.ProtoMessage) 201 case "MethodOptions": 202 descopts.Method = messageGoTypes[i].(pref.ProtoMessage) 203 } 204 } 205 } 206 } 207 208 // Process extensions. 209 if len(tb.ExtensionInfos) != len(fbOut.Extensions) { 210 panic("mismatching extension lengths") 211 } 212 var depIdx int32 213 for i := range fbOut.Extensions { 214 // For enum and message kinds, determine the referent Go type so 215 // that we can construct their constructors. 216 const listExtDeps = 2 217 var goType reflect.Type 218 switch fbOut.Extensions[i].L1.Kind { 219 case pref.EnumKind: 220 j := depIdxs.Get(tb.DependencyIndexes, listExtDeps, depIdx) 221 goType = reflect.TypeOf(tb.GoTypes[j]) 222 depIdx++ 223 case pref.MessageKind, pref.GroupKind: 224 j := depIdxs.Get(tb.DependencyIndexes, listExtDeps, depIdx) 225 goType = reflect.TypeOf(tb.GoTypes[j]) 226 depIdx++ 227 default: 228 goType = goTypeForPBKind[fbOut.Extensions[i].L1.Kind] 229 } 230 if fbOut.Extensions[i].IsList() { 231 goType = reflect.SliceOf(goType) 232 } 233 234 pimpl.InitExtensionInfo(&tb.ExtensionInfos[i], &fbOut.Extensions[i], goType) 235 236 // Register extension types. 237 if err := tb.TypeRegistry.RegisterExtension(&tb.ExtensionInfos[i]); err != nil { 238 panic(err) 239 } 240 } 241 242 return out 243} 244 245var goTypeForPBKind = map[pref.Kind]reflect.Type{ 246 pref.BoolKind: reflect.TypeOf(bool(false)), 247 pref.Int32Kind: reflect.TypeOf(int32(0)), 248 pref.Sint32Kind: reflect.TypeOf(int32(0)), 249 pref.Sfixed32Kind: reflect.TypeOf(int32(0)), 250 pref.Int64Kind: reflect.TypeOf(int64(0)), 251 pref.Sint64Kind: reflect.TypeOf(int64(0)), 252 pref.Sfixed64Kind: reflect.TypeOf(int64(0)), 253 pref.Uint32Kind: reflect.TypeOf(uint32(0)), 254 pref.Fixed32Kind: reflect.TypeOf(uint32(0)), 255 pref.Uint64Kind: reflect.TypeOf(uint64(0)), 256 pref.Fixed64Kind: reflect.TypeOf(uint64(0)), 257 pref.FloatKind: reflect.TypeOf(float32(0)), 258 pref.DoubleKind: reflect.TypeOf(float64(0)), 259 pref.StringKind: reflect.TypeOf(string("")), 260 pref.BytesKind: reflect.TypeOf([]byte(nil)), 261} 262 263type depIdxs []int32 264 265// Get retrieves the jth element of the ith sub-list. 266func (x depIdxs) Get(i, j int32) int32 { 267 return x[x[int32(len(x))-i-1]+j] 268} 269 270type ( 271 resolverByIndex struct { 272 goTypes []interface{} 273 depIdxs depIdxs 274 fileRegistry 275 } 276 fileRegistry interface { 277 FindFileByPath(string) (pref.FileDescriptor, error) 278 FindDescriptorByName(pref.FullName) (pref.Descriptor, error) 279 RegisterFile(pref.FileDescriptor) error 280 } 281) 282 283func (r *resolverByIndex) FindEnumByIndex(i, j int32, es []fdesc.Enum, ms []fdesc.Message) pref.EnumDescriptor { 284 if depIdx := int(r.depIdxs.Get(i, j)); int(depIdx) < len(es)+len(ms) { 285 return &es[depIdx] 286 } else { 287 return pimpl.Export{}.EnumDescriptorOf(r.goTypes[depIdx]) 288 } 289} 290 291func (r *resolverByIndex) FindMessageByIndex(i, j int32, es []fdesc.Enum, ms []fdesc.Message) pref.MessageDescriptor { 292 if depIdx := int(r.depIdxs.Get(i, j)); depIdx < len(es)+len(ms) { 293 return &ms[depIdx-len(es)] 294 } else { 295 return pimpl.Export{}.MessageDescriptorOf(r.goTypes[depIdx]) 296 } 297} 298