blob: 0a0dd35de5a7c49b93079526a5ef1483de5470e0 [file] [log] [blame]
khenaidood948f772021-08-11 17:49:24 -04001// 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}