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