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