Skip to content

Commit 3cbbe29

Browse files
authored
reflection: don't serialize placeholders (#6771)
1 parent 4a84ce6 commit 3cbbe29

File tree

2 files changed

+209
-0
lines changed

2 files changed

+209
-0
lines changed

reflection/serverreflection.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,20 @@ type serverReflectionServer struct {
176176
// wire format ([]byte). The fileDescriptors will include fd and all the
177177
// transitive dependencies of fd with names not in sentFileDescriptors.
178178
func (s *serverReflectionServer) fileDescWithDependencies(fd protoreflect.FileDescriptor, sentFileDescriptors map[string]bool) ([][]byte, error) {
179+
if fd.IsPlaceholder() {
180+
// If the given root file is a placeholder, treat it
181+
// as missing instead of serializing it.
182+
return nil, protoregistry.NotFound
183+
}
179184
var r [][]byte
180185
queue := []protoreflect.FileDescriptor{fd}
181186
for len(queue) > 0 {
182187
currentfd := queue[0]
183188
queue = queue[1:]
189+
if currentfd.IsPlaceholder() {
190+
// Skip any missing files in the dependency graph.
191+
continue
192+
}
184193
if sent := sentFileDescriptors[currentfd.Path()]; len(r) == 0 || !sent {
185194
sentFileDescriptors[currentfd.Path()] = true
186195
fdProto := protodesc.ToFileDescriptorProto(currentfd)

reflection/serverreflection_test.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,206 @@ func (x) TestAllExtensionNumbersForTypeName(t *testing.T) {
170170
}
171171
}
172172

173+
func (x) TestFileDescWithDependencies(t *testing.T) {
174+
depFile, err := protodesc.NewFile(
175+
&descriptorpb.FileDescriptorProto{
176+
Name: proto.String("dep.proto"),
177+
}, nil,
178+
)
179+
if err != nil {
180+
t.Fatalf("unexpected error: %s", err)
181+
}
182+
183+
deps := &protoregistry.Files{}
184+
if err := deps.RegisterFile(depFile); err != nil {
185+
t.Fatalf("unexpected error: %s", err)
186+
}
187+
188+
rootFileProto := &descriptorpb.FileDescriptorProto{
189+
Name: proto.String("root.proto"),
190+
Dependency: []string{
191+
"google/protobuf/descriptor.proto",
192+
"reflection/grpc_testing/proto2_ext2.proto",
193+
"dep.proto",
194+
},
195+
}
196+
197+
// dep.proto is in deps; the other imports come from protoregistry.GlobalFiles
198+
resolver := &combinedResolver{first: protoregistry.GlobalFiles, second: deps}
199+
rootFile, err := protodesc.NewFile(rootFileProto, resolver)
200+
if err != nil {
201+
t.Fatalf("unexpected error: %s", err)
202+
}
203+
204+
// Create a file hierarchy that contains a placeholder for dep.proto
205+
placeholderDep := placeholderFile{depFile}
206+
placeholderDeps := &protoregistry.Files{}
207+
if err := placeholderDeps.RegisterFile(placeholderDep); err != nil {
208+
t.Fatalf("unexpected error: %s", err)
209+
}
210+
resolver = &combinedResolver{first: protoregistry.GlobalFiles, second: placeholderDeps}
211+
212+
rootFileHasPlaceholderDep, err := protodesc.NewFile(rootFileProto, resolver)
213+
if err != nil {
214+
t.Fatalf("unexpected error: %s", err)
215+
}
216+
217+
rootFileIsPlaceholder := placeholderFile{rootFile}
218+
219+
// Full transitive dependency graph of root.proto includes five files:
220+
// - root.proto
221+
// - google/protobuf/descriptor.proto
222+
// - reflection/grpc_testing/proto2_ext2.proto
223+
// - reflection/grpc_testing/proto2.proto
224+
// - dep.proto
225+
226+
for _, test := range []struct {
227+
name string
228+
sent []string
229+
root protoreflect.FileDescriptor
230+
expect []string
231+
}{
232+
{
233+
name: "send_all",
234+
root: rootFile,
235+
// expect full transitive closure
236+
expect: []string{
237+
"root.proto",
238+
"google/protobuf/descriptor.proto",
239+
"reflection/grpc_testing/proto2_ext2.proto",
240+
"reflection/grpc_testing/proto2.proto",
241+
"dep.proto",
242+
},
243+
},
244+
{
245+
name: "already_sent",
246+
sent: []string{
247+
"root.proto",
248+
"google/protobuf/descriptor.proto",
249+
"reflection/grpc_testing/proto2_ext2.proto",
250+
"reflection/grpc_testing/proto2.proto",
251+
"dep.proto",
252+
},
253+
root: rootFile,
254+
// expect only the root to be re-sent
255+
expect: []string{"root.proto"},
256+
},
257+
{
258+
name: "some_already_sent",
259+
sent: []string{
260+
"reflection/grpc_testing/proto2_ext2.proto",
261+
"reflection/grpc_testing/proto2.proto",
262+
},
263+
root: rootFile,
264+
expect: []string{
265+
"root.proto",
266+
"google/protobuf/descriptor.proto",
267+
"dep.proto",
268+
},
269+
},
270+
{
271+
name: "root_is_placeholder",
272+
root: rootFileIsPlaceholder,
273+
// expect error, no files
274+
},
275+
{
276+
name: "placeholder_skipped",
277+
root: rootFileHasPlaceholderDep,
278+
// dep.proto is a placeholder so is skipped
279+
expect: []string{
280+
"root.proto",
281+
"google/protobuf/descriptor.proto",
282+
"reflection/grpc_testing/proto2_ext2.proto",
283+
"reflection/grpc_testing/proto2.proto",
284+
},
285+
},
286+
{
287+
name: "placeholder_skipped_and_some_sent",
288+
sent: []string{
289+
"reflection/grpc_testing/proto2_ext2.proto",
290+
"reflection/grpc_testing/proto2.proto",
291+
},
292+
root: rootFileHasPlaceholderDep,
293+
expect: []string{
294+
"root.proto",
295+
"google/protobuf/descriptor.proto",
296+
},
297+
},
298+
} {
299+
t.Run(test.name, func(t *testing.T) {
300+
s := NewServerV1(ServerOptions{}).(*serverReflectionServer)
301+
302+
sent := map[string]bool{}
303+
for _, path := range test.sent {
304+
sent[path] = true
305+
}
306+
307+
descriptors, err := s.fileDescWithDependencies(test.root, sent)
308+
if len(test.expect) == 0 {
309+
// if we're not expecting any files then we're expecting an error
310+
if err == nil {
311+
t.Fatalf("expecting an error; instead got %d files", len(descriptors))
312+
}
313+
return
314+
}
315+
316+
checkDescriptorResults(t, descriptors, test.expect)
317+
})
318+
}
319+
}
320+
321+
func checkDescriptorResults(t *testing.T, descriptors [][]byte, expect []string) {
322+
t.Helper()
323+
if len(descriptors) != len(expect) {
324+
t.Errorf("expected result to contain %d descriptor(s); instead got %d", len(expect), len(descriptors))
325+
}
326+
names := map[string]struct{}{}
327+
for i, desc := range descriptors {
328+
var descProto descriptorpb.FileDescriptorProto
329+
if err := proto.Unmarshal(desc, &descProto); err != nil {
330+
t.Fatalf("could not unmarshal descriptor result #%d", i+1)
331+
}
332+
names[descProto.GetName()] = struct{}{}
333+
}
334+
actual := make([]string, 0, len(names))
335+
for name := range names {
336+
actual = append(actual, name)
337+
}
338+
sort.Strings(actual)
339+
sort.Strings(expect)
340+
if !reflect.DeepEqual(actual, expect) {
341+
t.Fatalf("expected file descriptors for %v; instead got %v", expect, actual)
342+
}
343+
}
344+
345+
type placeholderFile struct {
346+
protoreflect.FileDescriptor
347+
}
348+
349+
func (placeholderFile) IsPlaceholder() bool {
350+
return true
351+
}
352+
353+
type combinedResolver struct {
354+
first, second protodesc.Resolver
355+
}
356+
357+
func (r *combinedResolver) FindFileByPath(path string) (protoreflect.FileDescriptor, error) {
358+
file, err := r.first.FindFileByPath(path)
359+
if err == nil {
360+
return file, nil
361+
}
362+
return r.second.FindFileByPath(path)
363+
}
364+
365+
func (r *combinedResolver) FindDescriptorByName(name protoreflect.FullName) (protoreflect.Descriptor, error) {
366+
desc, err := r.first.FindDescriptorByName(name)
367+
if err == nil {
368+
return desc, nil
369+
}
370+
return r.second.FindDescriptorByName(name)
371+
}
372+
173373
// Do end2end tests.
174374

175375
type server struct {

0 commit comments

Comments
 (0)