Elia Battiston | be9edc1 | 2022-03-09 11:35:58 +0100 | [diff] [blame] | 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. |
| 7 | package filetype |
| 8 | |
| 9 | import ( |
| 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. |
| 53 | type 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. |
| 118 | type Out struct { |
| 119 | File pref.FileDescriptor |
| 120 | } |
| 121 | |
| 122 | func (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 | |
| 245 | var 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 | |
| 263 | type depIdxs []int32 |
| 264 | |
| 265 | // Get retrieves the jth element of the ith sub-list. |
| 266 | func (x depIdxs) Get(i, j int32) int32 { |
| 267 | return x[x[int32(len(x))-i-1]+j] |
| 268 | } |
| 269 | |
| 270 | type ( |
| 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 | |
| 283 | func (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 | |
| 291 | func (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 | } |