Skip to content

Commit c720882

Browse files
mkruskal-googlestapelberg
authored andcommitted
reflect/protodesc: fix handling of import options in dynamic builds
Without this, we allow non-option symbols from import option dependencies to be referenced inside of protos. This can both break valid protos and allow invalid protos in various edge cases, and is inconsistent with protoc. Change-Id: I12e657435173186b8fe4e84315fc2e1b9fd2dc6a Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/711015 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Stapelberg <stapelberg@google.com> Commit-Queue: Michael Stapelberg <stapelberg@google.com>
1 parent e0867da commit c720882

File tree

2 files changed

+65
-2
lines changed

2 files changed

+65
-2
lines changed

reflect/protodesc/desc.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ func (o FileOptions) New(fd *descriptorpb.FileDescriptorProto, r Resolver) (prot
152152
imp := &f.L2.Imports[i]
153153
imps.importPublic(imp.Imports())
154154
}
155+
optionImps := importSet{f.Path(): true}
155156
if len(fd.GetOptionDependency()) > 0 {
156157
optionImports := make(filedesc.FileImports, len(fd.GetOptionDependency()))
157158
for i, path := range fd.GetOptionDependency() {
@@ -165,10 +166,12 @@ func (o FileOptions) New(fd *descriptorpb.FileDescriptorProto, r Resolver) (prot
165166
}
166167
imp.FileDescriptor = f
167168

168-
if imps[imp.Path()] {
169+
if imps[imp.Path()] || optionImps[imp.Path()] {
169170
return nil, errors.New("already imported %q", path)
170171
}
171-
imps[imp.Path()] = true
172+
// This needs to be a separate map so that we don't recognize non-options
173+
// symbols coming from option imports.
174+
optionImps[imp.Path()] = true
172175
}
173176
f.L2.OptionImports = func() protoreflect.FileImports {
174177
return &optionImports

reflect/protodesc/file_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,66 @@ func TestNewFilesImportCycle(t *testing.T) {
10161016
}
10171017
}
10181018

1019+
func TestNewFilesMissingImports(t *testing.T) {
1020+
tests := []struct {
1021+
label string
1022+
files []*descriptorpb.FileDescriptorProto
1023+
}{
1024+
{
1025+
label: "missing import",
1026+
files: []*descriptorpb.FileDescriptorProto{
1027+
mustParseFile(`
1028+
name: "import.proto"
1029+
message_type {
1030+
name: "MissingMessage"
1031+
}
1032+
`),
1033+
mustParseFile(`
1034+
name: "test.proto"
1035+
message_type: [{
1036+
name: "M"
1037+
field: [{name:"field" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"MissingMessage"}]
1038+
}]
1039+
`),
1040+
},
1041+
},
1042+
{
1043+
label: "missing import via option_dependency",
1044+
files: []*descriptorpb.FileDescriptorProto{
1045+
mustParseFile(`
1046+
name: "import.proto"
1047+
message_type {
1048+
name: "MissingMessage"
1049+
}
1050+
`),
1051+
mustParseFile(`
1052+
name: "test.proto"
1053+
edition: EDITION_2024
1054+
option_dependency: "import.proto"
1055+
message_type: [{
1056+
name: "M"
1057+
field: [{name:"field" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:"MissingMessage"}]
1058+
}]
1059+
`),
1060+
},
1061+
},
1062+
}
1063+
for _, tt := range tests {
1064+
t.Run(tt.label, func(t *testing.T) {
1065+
fdset := &descriptorpb.FileDescriptorSet{File: tt.files}
1066+
1067+
_, err := NewFiles(fdset)
1068+
1069+
if err == nil {
1070+
t.Fatal("NewFiles with missing import: success, want error")
1071+
}
1072+
if !strings.Contains(err.Error(), `cannot resolve`) {
1073+
t.Fatalf("NewFiles with missing import: got error \"%v\", want import error", err)
1074+
}
1075+
})
1076+
}
1077+
}
1078+
10191079
func TestSourceLocations(t *testing.T) {
10201080
fd := mustParseFile(`
10211081
name: "comments.proto"

0 commit comments

Comments
 (0)