• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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	"google.golang.org/protobuf/internal/filedesc"
14	pimpl "google.golang.org/protobuf/internal/impl"
15	"google.golang.org/protobuf/reflect/protoreflect"
16	"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// # Flattened Ordering
23//
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 filedesc.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(protoreflect.MessageType) error
111		RegisterEnum(protoreflect.EnumType) error
112		RegisterExtension(protoreflect.ExtensionType) error
113	}
114}
115
116// Out is the output of the builder.
117type Out struct {
118	File protoreflect.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 = protoregistry.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 = protoregistry.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].(protoreflect.ProtoMessage)
186				case "EnumOptions":
187					descopts.Enum = messageGoTypes[i].(protoreflect.ProtoMessage)
188				case "EnumValueOptions":
189					descopts.EnumValue = messageGoTypes[i].(protoreflect.ProtoMessage)
190				case "MessageOptions":
191					descopts.Message = messageGoTypes[i].(protoreflect.ProtoMessage)
192				case "FieldOptions":
193					descopts.Field = messageGoTypes[i].(protoreflect.ProtoMessage)
194				case "OneofOptions":
195					descopts.Oneof = messageGoTypes[i].(protoreflect.ProtoMessage)
196				case "ExtensionRangeOptions":
197					descopts.ExtensionRange = messageGoTypes[i].(protoreflect.ProtoMessage)
198				case "ServiceOptions":
199					descopts.Service = messageGoTypes[i].(protoreflect.ProtoMessage)
200				case "MethodOptions":
201					descopts.Method = messageGoTypes[i].(protoreflect.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 protoreflect.EnumKind:
219			j := depIdxs.Get(tb.DependencyIndexes, listExtDeps, depIdx)
220			goType = reflect.TypeOf(tb.GoTypes[j])
221			depIdx++
222		case protoreflect.MessageKind, protoreflect.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[protoreflect.Kind]reflect.Type{
245	protoreflect.BoolKind:     reflect.TypeOf(bool(false)),
246	protoreflect.Int32Kind:    reflect.TypeOf(int32(0)),
247	protoreflect.Sint32Kind:   reflect.TypeOf(int32(0)),
248	protoreflect.Sfixed32Kind: reflect.TypeOf(int32(0)),
249	protoreflect.Int64Kind:    reflect.TypeOf(int64(0)),
250	protoreflect.Sint64Kind:   reflect.TypeOf(int64(0)),
251	protoreflect.Sfixed64Kind: reflect.TypeOf(int64(0)),
252	protoreflect.Uint32Kind:   reflect.TypeOf(uint32(0)),
253	protoreflect.Fixed32Kind:  reflect.TypeOf(uint32(0)),
254	protoreflect.Uint64Kind:   reflect.TypeOf(uint64(0)),
255	protoreflect.Fixed64Kind:  reflect.TypeOf(uint64(0)),
256	protoreflect.FloatKind:    reflect.TypeOf(float32(0)),
257	protoreflect.DoubleKind:   reflect.TypeOf(float64(0)),
258	protoreflect.StringKind:   reflect.TypeOf(string("")),
259	protoreflect.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) (protoreflect.FileDescriptor, error)
277		FindDescriptorByName(protoreflect.FullName) (protoreflect.Descriptor, error)
278		RegisterFile(protoreflect.FileDescriptor) error
279	}
280)
281
282func (r *resolverByIndex) FindEnumByIndex(i, j int32, es []filedesc.Enum, ms []filedesc.Message) protoreflect.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 []filedesc.Enum, ms []filedesc.Message) protoreflect.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}
297