|
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 } |