reflection: don't serialize placeholders (#6771)

This commit is contained in:
Joshua Humphries 2023-11-14 15:13:44 -05:00 committed by GitHub
parent 4a84ce61ec
commit 3cbbe2947f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 209 additions and 0 deletions

View File

@ -176,11 +176,20 @@ type serverReflectionServer struct {
// wire format ([]byte). The fileDescriptors will include fd and all the
// transitive dependencies of fd with names not in sentFileDescriptors.
func (s *serverReflectionServer) fileDescWithDependencies(fd protoreflect.FileDescriptor, sentFileDescriptors map[string]bool) ([][]byte, error) {
if fd.IsPlaceholder() {
// If the given root file is a placeholder, treat it
// as missing instead of serializing it.
return nil, protoregistry.NotFound
}
var r [][]byte
queue := []protoreflect.FileDescriptor{fd}
for len(queue) > 0 {
currentfd := queue[0]
queue = queue[1:]
if currentfd.IsPlaceholder() {
// Skip any missing files in the dependency graph.
continue
}
if sent := sentFileDescriptors[currentfd.Path()]; len(r) == 0 || !sent {
sentFileDescriptors[currentfd.Path()] = true
fdProto := protodesc.ToFileDescriptorProto(currentfd)

View File

@ -170,6 +170,206 @@ func (x) TestAllExtensionNumbersForTypeName(t *testing.T) {
}
}
func (x) TestFileDescWithDependencies(t *testing.T) {
depFile, err := protodesc.NewFile(
&descriptorpb.FileDescriptorProto{
Name: proto.String("dep.proto"),
}, nil,
)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
deps := &protoregistry.Files{}
if err := deps.RegisterFile(depFile); err != nil {
t.Fatalf("unexpected error: %s", err)
}
rootFileProto := &descriptorpb.FileDescriptorProto{
Name: proto.String("root.proto"),
Dependency: []string{
"google/protobuf/descriptor.proto",
"reflection/grpc_testing/proto2_ext2.proto",
"dep.proto",
},
}
// dep.proto is in deps; the other imports come from protoregistry.GlobalFiles
resolver := &combinedResolver{first: protoregistry.GlobalFiles, second: deps}
rootFile, err := protodesc.NewFile(rootFileProto, resolver)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
// Create a file hierarchy that contains a placeholder for dep.proto
placeholderDep := placeholderFile{depFile}
placeholderDeps := &protoregistry.Files{}
if err := placeholderDeps.RegisterFile(placeholderDep); err != nil {
t.Fatalf("unexpected error: %s", err)
}
resolver = &combinedResolver{first: protoregistry.GlobalFiles, second: placeholderDeps}
rootFileHasPlaceholderDep, err := protodesc.NewFile(rootFileProto, resolver)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
rootFileIsPlaceholder := placeholderFile{rootFile}
// Full transitive dependency graph of root.proto includes five files:
// - root.proto
// - google/protobuf/descriptor.proto
// - reflection/grpc_testing/proto2_ext2.proto
// - reflection/grpc_testing/proto2.proto
// - dep.proto
for _, test := range []struct {
name string
sent []string
root protoreflect.FileDescriptor
expect []string
}{
{
name: "send_all",
root: rootFile,
// expect full transitive closure
expect: []string{
"root.proto",
"google/protobuf/descriptor.proto",
"reflection/grpc_testing/proto2_ext2.proto",
"reflection/grpc_testing/proto2.proto",
"dep.proto",
},
},
{
name: "already_sent",
sent: []string{
"root.proto",
"google/protobuf/descriptor.proto",
"reflection/grpc_testing/proto2_ext2.proto",
"reflection/grpc_testing/proto2.proto",
"dep.proto",
},
root: rootFile,
// expect only the root to be re-sent
expect: []string{"root.proto"},
},
{
name: "some_already_sent",
sent: []string{
"reflection/grpc_testing/proto2_ext2.proto",
"reflection/grpc_testing/proto2.proto",
},
root: rootFile,
expect: []string{
"root.proto",
"google/protobuf/descriptor.proto",
"dep.proto",
},
},
{
name: "root_is_placeholder",
root: rootFileIsPlaceholder,
// expect error, no files
},
{
name: "placeholder_skipped",
root: rootFileHasPlaceholderDep,
// dep.proto is a placeholder so is skipped
expect: []string{
"root.proto",
"google/protobuf/descriptor.proto",
"reflection/grpc_testing/proto2_ext2.proto",
"reflection/grpc_testing/proto2.proto",
},
},
{
name: "placeholder_skipped_and_some_sent",
sent: []string{
"reflection/grpc_testing/proto2_ext2.proto",
"reflection/grpc_testing/proto2.proto",
},
root: rootFileHasPlaceholderDep,
expect: []string{
"root.proto",
"google/protobuf/descriptor.proto",
},
},
} {
t.Run(test.name, func(t *testing.T) {
s := NewServerV1(ServerOptions{}).(*serverReflectionServer)
sent := map[string]bool{}
for _, path := range test.sent {
sent[path] = true
}
descriptors, err := s.fileDescWithDependencies(test.root, sent)
if len(test.expect) == 0 {
// if we're not expecting any files then we're expecting an error
if err == nil {
t.Fatalf("expecting an error; instead got %d files", len(descriptors))
}
return
}
checkDescriptorResults(t, descriptors, test.expect)
})
}
}
func checkDescriptorResults(t *testing.T, descriptors [][]byte, expect []string) {
t.Helper()
if len(descriptors) != len(expect) {
t.Errorf("expected result to contain %d descriptor(s); instead got %d", len(expect), len(descriptors))
}
names := map[string]struct{}{}
for i, desc := range descriptors {
var descProto descriptorpb.FileDescriptorProto
if err := proto.Unmarshal(desc, &descProto); err != nil {
t.Fatalf("could not unmarshal descriptor result #%d", i+1)
}
names[descProto.GetName()] = struct{}{}
}
actual := make([]string, 0, len(names))
for name := range names {
actual = append(actual, name)
}
sort.Strings(actual)
sort.Strings(expect)
if !reflect.DeepEqual(actual, expect) {
t.Fatalf("expected file descriptors for %v; instead got %v", expect, actual)
}
}
type placeholderFile struct {
protoreflect.FileDescriptor
}
func (placeholderFile) IsPlaceholder() bool {
return true
}
type combinedResolver struct {
first, second protodesc.Resolver
}
func (r *combinedResolver) FindFileByPath(path string) (protoreflect.FileDescriptor, error) {
file, err := r.first.FindFileByPath(path)
if err == nil {
return file, nil
}
return r.second.FindFileByPath(path)
}
func (r *combinedResolver) FindDescriptorByName(name protoreflect.FullName) (protoreflect.Descriptor, error) {
desc, err := r.first.FindDescriptorByName(name)
if err == nil {
return desc, nil
}
return r.second.FindDescriptorByName(name)
}
// Do end2end tests.
type server struct {