aboutsummaryrefslogtreecommitdiff
path: root/internal/filetype/build.go
blob: 0a0dd35de5a7c49b93079526a5ef1483de5470e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package filetype provides functionality for wrapping descriptors
// with Go type information.
package filetype

import (
	"reflect"

	"google.golang.org/protobuf/internal/descopts"
	fdesc "google.golang.org/protobuf/internal/filedesc"
	pimpl "google.golang.org/protobuf/internal/impl"
	pref "google.golang.org/protobuf/reflect/protoreflect"
	preg "google.golang.org/protobuf/reflect/protoregistry"
)

// Builder constructs type descriptors from a raw file descriptor
// and associated Go types for each enum and message declaration.
//
//
// Flattened Ordering
//
// The protobuf type system represents declarations as a tree. Certain nodes in
// the tree require us to either associate it with a concrete Go type or to
// resolve a dependency, which is information that must be provided separately
// since it cannot be derived from the file descriptor alone.
//
// However, representing a tree as Go literals is difficult to simply do in a
// space and time efficient way. Thus, we store them as a flattened list of
// objects where the serialization order from the tree-based form is important.
//
// The "flattened ordering" is defined as a tree traversal of all enum, message,
// extension, and service declarations using the following algorithm:
//
//	def VisitFileDecls(fd):
//		for e in fd.Enums:      yield e
//		for m in fd.Messages:   yield m
//		for x in fd.Extensions: yield x
//		for s in fd.Services:   yield s
//		for m in fd.Messages:   yield from VisitMessageDecls(m)
//
//	def VisitMessageDecls(md):
//		for e in md.Enums:      yield e
//		for m in md.Messages:   yield m
//		for x in md.Extensions: yield x
//		for m in md.Messages:   yield from VisitMessageDecls(m)
//
// The traversal starts at the root file descriptor and yields each direct
// declaration within each node before traversing into sub-declarations
// that children themselves may have.
type Builder struct {
	// File is the underlying file descriptor builder.
	File fdesc.Builder

	// GoTypes is a unique set of the Go types for all declarations and
	// dependencies. Each type is represented as a zero value of the Go type.
	//
	// Declarations are Go types generated for enums and messages directly
	// declared (not publicly imported) in the proto source file.
	// Messages for map entries are accounted for, but represented by nil.
	// Enum declarations in "flattened ordering" come first, followed by
	// message declarations in "flattened ordering".
	//
	// Dependencies are Go types for enums or messages referenced by
	// message fields (excluding weak fields), for parent extended messages of
	// extension fields, for enums or messages referenced by extension fields,
	// and for input and output messages referenced by service methods.
	// Dependencies must come after declarations, but the ordering of
	// dependencies themselves is unspecified.
	GoTypes []interface{}

	// DependencyIndexes is an ordered list of indexes into GoTypes for the
	// dependencies of messages, extensions, or services.
	//
	// There are 5 sub-lists in "flattened ordering" concatenated back-to-back:
	//	0. Message field dependencies: list of the enum or message type
	//	referred to by every message field.
	//	1. Extension field targets: list of the extended parent message of
	//	every extension.
	//	2. Extension field dependencies: list of the enum or message type
	//	referred to by every extension field.
	//	3. Service method inputs: list of the input message type
	//	referred to by every service method.
	//	4. Service method outputs: list of the output message type
	//	referred to by every service method.
	//
	// The offset into DependencyIndexes for the start of each sub-list
	// is appended to the end in reverse order.
	DependencyIndexes []int32

	// EnumInfos is a list of enum infos in "flattened ordering".
	EnumInfos []pimpl.EnumInfo

	// MessageInfos is a list of message infos in "flattened ordering".
	// If provided, the GoType and PBType for each element is populated.
	//
	// Requirement: len(MessageInfos) == len(Build.Messages)
	MessageInfos []pimpl.MessageInfo

	// ExtensionInfos is a list of extension infos in "flattened ordering".
	// Each element is initialized and registered with the protoregistry package.
	//
	// Requirement: len(LegacyExtensions) == len(Build.Extensions)
	ExtensionInfos []pimpl.ExtensionInfo

	// TypeRegistry is the registry to register each type descriptor.
	// If nil, it uses protoregistry.GlobalTypes.
	TypeRegistry interface {
		RegisterMessage(pref.MessageType) error
		RegisterEnum(pref.EnumType) error
		RegisterExtension(pref.ExtensionType) error
	}
}

// Out is the output of the builder.
type Out struct {
	File pref.FileDescriptor
}

func (tb Builder) Build() (out Out) {
	// Replace the resolver with one that resolves dependencies by index,
	// which is faster and more reliable than relying on the global registry.
	if tb.File.FileRegistry == nil {
		tb.File.FileRegistry = preg.GlobalFiles
	}
	tb.File.FileRegistry = &resolverByIndex{
		goTypes:      tb.GoTypes,
		depIdxs:      tb.DependencyIndexes,
		fileRegistry: tb.File.FileRegistry,
	}

	// Initialize registry if unpopulated.
	if tb.TypeRegistry == nil {
		tb.TypeRegistry = preg.GlobalTypes
	}

	fbOut := tb.File.Build()
	out.File = fbOut.File

	// Process enums.
	enumGoTypes := tb.GoTypes[:len(fbOut.Enums)]
	if len(tb.EnumInfos) != len(fbOut.Enums) {
		panic("mismatching enum lengths")
	}
	if len(fbOut.Enums) > 0 {
		for i := range fbOut.Enums {
			tb.EnumInfos[i] = pimpl.EnumInfo{
				GoReflectType: reflect.TypeOf(enumGoTypes[i]),
				Desc:          &fbOut.Enums[i],
			}
			// Register enum types.
			if err := tb.TypeRegistry.RegisterEnum(&tb.EnumInfos[i]); err != nil {
				panic(err)
			}
		}
	}

	// Process messages.
	messageGoTypes := tb.GoTypes[len(fbOut.Enums):][:len(fbOut.Messages)]
	if len(tb.MessageInfos) != len(fbOut.Messages) {
		panic("mismatching message lengths")
	}
	if len(fbOut.Messages) > 0 {
		for i := range fbOut.Messages {
			if messageGoTypes[i] == nil {
				continue // skip map entry
			}

			tb.MessageInfos[i].GoReflectType = reflect.TypeOf(messageGoTypes[i])
			tb.MessageInfos[i].Desc = &fbOut.Messages[i]

			// Register message types.
			if err := tb.TypeRegistry.RegisterMessage(&tb.MessageInfos[i]); err != nil {
				panic(err)
			}
		}

		// As a special-case for descriptor.proto,
		// locally register concrete message type for the options.
		if out.File.Path() == "google/protobuf/descriptor.proto" && out.File.Package() == "google.protobuf" {
			for i := range fbOut.Messages {
				switch fbOut.Messages[i].Name() {
				case "FileOptions":
					descopts.File = messageGoTypes[i].(pref.ProtoMessage)
				case "EnumOptions":
					descopts.Enum = messageGoTypes[i].(pref.ProtoMessage)
				case "EnumValueOptions":
					descopts.EnumValue = messageGoTypes[i].(pref.ProtoMessage)
				case "MessageOptions":
					descopts.Message = messageGoTypes[i].(pref.ProtoMessage)
				case "FieldOptions":
					descopts.Field = messageGoTypes[i].(pref.ProtoMessage)
				case "OneofOptions":
					descopts.Oneof = messageGoTypes[i].(pref.ProtoMessage)
				case "ExtensionRangeOptions":
					descopts.ExtensionRange = messageGoTypes[i].(pref.ProtoMessage)
				case "ServiceOptions":
					descopts.Service = messageGoTypes[i].(pref.ProtoMessage)
				case "MethodOptions":
					descopts.Method = messageGoTypes[i].(pref.ProtoMessage)
				}
			}
		}
	}

	// Process extensions.
	if len(tb.ExtensionInfos) != len(fbOut.Extensions) {
		panic("mismatching extension lengths")
	}
	var depIdx int32
	for i := range fbOut.Extensions {
		// For enum and message kinds, determine the referent Go type so
		// that we can construct their constructors.
		const listExtDeps = 2
		var goType reflect.Type
		switch fbOut.Extensions[i].L1.Kind {
		case pref.EnumKind:
			j := depIdxs.Get(tb.DependencyIndexes, listExtDeps, depIdx)
			goType = reflect.TypeOf(tb.GoTypes[j])
			depIdx++
		case pref.MessageKind, pref.GroupKind:
			j := depIdxs.Get(tb.DependencyIndexes, listExtDeps, depIdx)
			goType = reflect.TypeOf(tb.GoTypes[j])
			depIdx++
		default:
			goType = goTypeForPBKind[fbOut.Extensions[i].L1.Kind]
		}
		if fbOut.Extensions[i].IsList() {
			goType = reflect.SliceOf(goType)
		}

		pimpl.InitExtensionInfo(&tb.ExtensionInfos[i], &fbOut.Extensions[i], goType)

		// Register extension types.
		if err := tb.TypeRegistry.RegisterExtension(&tb.ExtensionInfos[i]); err != nil {
			panic(err)
		}
	}

	return out
}

var goTypeForPBKind = map[pref.Kind]reflect.Type{
	pref.BoolKind:     reflect.TypeOf(bool(false)),
	pref.Int32Kind:    reflect.TypeOf(int32(0)),
	pref.Sint32Kind:   reflect.TypeOf(int32(0)),
	pref.Sfixed32Kind: reflect.TypeOf(int32(0)),
	pref.Int64Kind:    reflect.TypeOf(int64(0)),
	pref.Sint64Kind:   reflect.TypeOf(int64(0)),
	pref.Sfixed64Kind: reflect.TypeOf(int64(0)),
	pref.Uint32Kind:   reflect.TypeOf(uint32(0)),
	pref.Fixed32Kind:  reflect.TypeOf(uint32(0)),
	pref.Uint64Kind:   reflect.TypeOf(uint64(0)),
	pref.Fixed64Kind:  reflect.TypeOf(uint64(0)),
	pref.FloatKind:    reflect.TypeOf(float32(0)),
	pref.DoubleKind:   reflect.TypeOf(float64(0)),
	pref.StringKind:   reflect.TypeOf(string("")),
	pref.BytesKind:    reflect.TypeOf([]byte(nil)),
}

type depIdxs []int32

// Get retrieves the jth element of the ith sub-list.
func (x depIdxs) Get(i, j int32) int32 {
	return x[x[int32(len(x))-i-1]+j]
}

type (
	resolverByIndex struct {
		goTypes []interface{}
		depIdxs depIdxs
		fileRegistry
	}
	fileRegistry interface {
		FindFileByPath(string) (pref.FileDescriptor, error)
		FindDescriptorByName(pref.FullName) (pref.Descriptor, error)
		RegisterFile(pref.FileDescriptor) error
	}
)

func (r *resolverByIndex) FindEnumByIndex(i, j int32, es []fdesc.Enum, ms []fdesc.Message) pref.EnumDescriptor {
	if depIdx := int(r.depIdxs.Get(i, j)); int(depIdx) < len(es)+len(ms) {
		return &es[depIdx]
	} else {
		return pimpl.Export{}.EnumDescriptorOf(r.goTypes[depIdx])
	}
}

func (r *resolverByIndex) FindMessageByIndex(i, j int32, es []fdesc.Enum, ms []fdesc.Message) pref.MessageDescriptor {
	if depIdx := int(r.depIdxs.Get(i, j)); depIdx < len(es)+len(ms) {
		return &ms[depIdx-len(es)]
	} else {
		return pimpl.Export{}.MessageDescriptorOf(r.goTypes[depIdx])
	}
}