reflect/protodesc: add validation for NewFile
This covers most of the TODO around validation. I left open the ones
that we didn't have clear consensus on yet.
Change-Id: I336c53173ee8d7447558b1e3a0c1ef945e986cd5
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/175140
Reviewed-by: Joe Tsai <joetsai@google.com>
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
diff --git a/cmd/protoc-gen-go/testdata/import_public/a.pb.go b/cmd/protoc-gen-go/testdata/import_public/a.pb.go
index b33e9f0..c881ea1 100644
--- a/cmd/protoc-gen-go/testdata/import_public/a.pb.go
+++ b/cmd/protoc-gen-go/testdata/import_public/a.pb.go
@@ -5,7 +5,6 @@
import (
sub "github.com/golang/protobuf/v2/cmd/protoc-gen-go/testdata/import_public/sub"
- sub2 "github.com/golang/protobuf/v2/cmd/protoc-gen-go/testdata/import_public/sub2"
protoreflect "github.com/golang/protobuf/v2/reflect/protoreflect"
protoregistry "github.com/golang/protobuf/v2/reflect/protoregistry"
protoiface "github.com/golang/protobuf/v2/runtime/protoiface"
@@ -53,10 +52,6 @@
var E_ExtensionField = sub.E_ExtensionField
-// Symbols defined in public import of import_public/sub2/a.proto
-
-type Sub2Message = sub2.Sub2Message
-
type Public struct {
M *sub.M `protobuf:"bytes,1,opt,name=m" json:"m,omitempty"`
E *sub.E `protobuf:"varint,2,opt,name=e,enum=goproto.protoc.import_public.sub.E" json:"e,omitempty"`
@@ -118,25 +113,24 @@
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70,
0x75, 0x62, 0x6c, 0x69, 0x63, 0x1a, 0x19, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75,
0x62, 0x6c, 0x69, 0x63, 0x2f, 0x73, 0x75, 0x62, 0x2f, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
- 0x1a, 0x1a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f,
- 0x73, 0x75, 0x62, 0x32, 0x2f, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x69, 0x6d,
- 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f, 0x62, 0x2e, 0x70, 0x72,
- 0x6f, 0x74, 0x6f, 0x22, 0xa9, 0x01, 0x0a, 0x06, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x12, 0x31,
- 0x0a, 0x01, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x70, 0x72,
- 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72,
- 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x73, 0x75, 0x62, 0x2e, 0x4d, 0x52, 0x01,
- 0x6d, 0x12, 0x31, 0x0a, 0x01, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x67,
- 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d,
- 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x73, 0x75, 0x62, 0x2e,
- 0x45, 0x52, 0x01, 0x65, 0x12, 0x39, 0x0a, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x18, 0x03, 0x20,
- 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72,
- 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c,
- 0x69, 0x63, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x42,
- 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f,
- 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x32,
- 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d,
- 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x69, 0x6d, 0x70, 0x6f,
- 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x50, 0x00, 0x50, 0x01, 0x50, 0x02,
+ 0x1a, 0x15, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f,
+ 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa9, 0x01, 0x0a, 0x06, 0x50, 0x75, 0x62, 0x6c,
+ 0x69, 0x63, 0x12, 0x31, 0x0a, 0x01, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e,
+ 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69,
+ 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x73, 0x75, 0x62,
+ 0x2e, 0x4d, 0x52, 0x01, 0x6d, 0x12, 0x31, 0x0a, 0x01, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e,
+ 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e,
+ 0x73, 0x75, 0x62, 0x2e, 0x45, 0x52, 0x01, 0x65, 0x12, 0x39, 0x0a, 0x05, 0x6c, 0x6f, 0x63, 0x61,
+ 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f,
+ 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x05, 0x6c, 0x6f,
+ 0x63, 0x61, 0x6c, 0x42, 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
+ 0x6d, 0x2f, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
+ 0x66, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d,
+ 0x67, 0x65, 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f,
+ 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x50, 0x00, 0x50,
+ 0x01,
}
var (
diff --git a/cmd/protoc-gen-go/testdata/import_public/a.proto b/cmd/protoc-gen-go/testdata/import_public/a.proto
index 091dcac..6807950 100644
--- a/cmd/protoc-gen-go/testdata/import_public/a.proto
+++ b/cmd/protoc-gen-go/testdata/import_public/a.proto
@@ -9,7 +9,6 @@
option go_package = "github.com/golang/protobuf/v2/cmd/protoc-gen-go/testdata/import_public";
import public "import_public/sub/a.proto"; // Different Go package.
-import public "import_public/sub2/a.proto"; // Different Go package.
import public "import_public/b.proto"; // Same Go package.
message Public {
diff --git a/cmd/protoc-gen-go/testdata/import_public/c.pb.go b/cmd/protoc-gen-go/testdata/import_public/c.pb.go
new file mode 100644
index 0000000..49a8fb5
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/import_public/c.pb.go
@@ -0,0 +1,129 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: import_public/c.proto
+
+package import_public
+
+import (
+ sub2 "github.com/golang/protobuf/v2/cmd/protoc-gen-go/testdata/import_public/sub2"
+ protoreflect "github.com/golang/protobuf/v2/reflect/protoreflect"
+ protoregistry "github.com/golang/protobuf/v2/reflect/protoregistry"
+ protoiface "github.com/golang/protobuf/v2/runtime/protoiface"
+ protoimpl "github.com/golang/protobuf/v2/runtime/protoimpl"
+ sync "sync"
+)
+
+const _ = protoimpl.EnforceVersion(protoimpl.Version - 0)
+
+type UsingPublicImport struct {
+ // Local is declared in b.proto, which is a public import of a.proto.
+ Local *Local `protobuf:"bytes,1,opt,name=local" json:"local,omitempty"`
+ // Sub2Message is declared in sub2/a.proto, which is a public import of
+ // sub/a.proto, which is a public import of a.proto.
+ Sub2 *sub2.Sub2Message `protobuf:"bytes,2,opt,name=sub2" json:"sub2,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized protoimpl.UnknownFields `json:"-"`
+ XXX_sizecache protoimpl.SizeCache `json:"-"`
+}
+
+func (x *UsingPublicImport) Reset() {
+ *x = UsingPublicImport{}
+}
+
+func (x *UsingPublicImport) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UsingPublicImport) ProtoMessage() {}
+
+func (x *UsingPublicImport) ProtoReflect() protoreflect.Message {
+ return file_import_public_c_proto_msgTypes[0].MessageOf(x)
+}
+
+func (m *UsingPublicImport) XXX_Methods() *protoiface.Methods {
+ return file_import_public_c_proto_msgTypes[0].Methods()
+}
+
+// Deprecated: Use UsingPublicImport.ProtoReflect.Type instead.
+func (*UsingPublicImport) Descriptor() ([]byte, []int) {
+ return file_import_public_c_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *UsingPublicImport) GetLocal() *Local {
+ if x != nil {
+ return x.Local
+ }
+ return nil
+}
+
+func (x *UsingPublicImport) GetSub2() *sub2.Sub2Message {
+ if x != nil {
+ return x.Sub2
+ }
+ return nil
+}
+
+var File_import_public_c_proto protoreflect.FileDescriptor
+
+var file_import_public_c_proto_rawDesc = []byte{
+ 0x0a, 0x15, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f,
+ 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1c, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+ 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70,
+ 0x75, 0x62, 0x6c, 0x69, 0x63, 0x1a, 0x15, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75,
+ 0x62, 0x6c, 0x69, 0x63, 0x2f, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x92, 0x01, 0x0a,
+ 0x11, 0x55, 0x73, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x6d, 0x70, 0x6f,
+ 0x72, 0x74, 0x12, 0x39, 0x0a, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x0b, 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
+ 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x42, 0x0a,
+ 0x04, 0x73, 0x75, 0x62, 0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x67, 0x6f,
+ 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2e, 0x69, 0x6d, 0x70,
+ 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x73, 0x75, 0x62, 0x32, 0x2e,
+ 0x53, 0x75, 0x62, 0x32, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x04, 0x73, 0x75, 0x62,
+ 0x32, 0x42, 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
+ 0x76, 0x32, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65,
+ 0x6e, 0x2d, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x69, 0x6d,
+ 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
+}
+
+var (
+ file_import_public_c_proto_rawDescOnce sync.Once
+ file_import_public_c_proto_rawDescData = file_import_public_c_proto_rawDesc
+)
+
+func file_import_public_c_proto_rawDescGZIP() []byte {
+ file_import_public_c_proto_rawDescOnce.Do(func() {
+ file_import_public_c_proto_rawDescData = protoimpl.X.CompressGZIP(file_import_public_c_proto_rawDescData)
+ })
+ return file_import_public_c_proto_rawDescData
+}
+
+var file_import_public_c_proto_msgTypes = make([]protoimpl.MessageType, 1)
+var file_import_public_c_proto_goTypes = []interface{}{
+ (*UsingPublicImport)(nil), // 0: goproto.protoc.import_public.UsingPublicImport
+ (*Local)(nil), // 1: goproto.protoc.import_public.Local
+ (*sub2.Sub2Message)(nil), // 2: goproto.protoc.import_public.sub2.Sub2Message
+}
+var file_import_public_c_proto_depIdxs = []int32{
+ 1, // goproto.protoc.import_public.UsingPublicImport.local:type_name -> goproto.protoc.import_public.Local
+ 2, // goproto.protoc.import_public.UsingPublicImport.sub2:type_name -> goproto.protoc.import_public.sub2.Sub2Message
+}
+
+func init() { file_import_public_c_proto_init() }
+func file_import_public_c_proto_init() {
+ if File_import_public_c_proto != nil {
+ return
+ }
+ file_import_public_a_proto_init()
+ File_import_public_c_proto = protoimpl.FileBuilder{
+ RawDescriptor: file_import_public_c_proto_rawDesc,
+ GoTypes: file_import_public_c_proto_goTypes,
+ DependencyIndexes: file_import_public_c_proto_depIdxs,
+ MessageOutputTypes: file_import_public_c_proto_msgTypes,
+ FilesRegistry: protoregistry.GlobalFiles,
+ TypesRegistry: protoregistry.GlobalTypes,
+ }.Init()
+ file_import_public_c_proto_rawDesc = nil
+ file_import_public_c_proto_goTypes = nil
+ file_import_public_c_proto_depIdxs = nil
+}
diff --git a/cmd/protoc-gen-go/testdata/import_public/c.proto b/cmd/protoc-gen-go/testdata/import_public/c.proto
new file mode 100644
index 0000000..6a3de06
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/import_public/c.proto
@@ -0,0 +1,19 @@
+// Copyright 2018 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.
+
+syntax = "proto2";
+
+package goproto.protoc.import_public;
+
+option go_package = "github.com/golang/protobuf/v2/cmd/protoc-gen-go/testdata/import_public";
+
+import "import_public/a.proto";
+
+message UsingPublicImport {
+ // Local is declared in b.proto, which is a public import of a.proto.
+ optional Local local = 1;
+ // Sub2Message is declared in sub2/a.proto, which is a public import of
+ // sub/a.proto, which is a public import of a.proto.
+ optional sub2.Sub2Message sub2 = 2; // declared in sub2/a.proto
+}
diff --git a/internal/prototype/protofile_list.go b/internal/prototype/protofile_list.go
index f136b5f..426e695 100644
--- a/internal/prototype/protofile_list.go
+++ b/internal/prototype/protofile_list.go
@@ -29,6 +29,9 @@
func (p *names) Format(s fmt.State, r rune) { pfmt.FormatList(s, r, p) }
func (p *names) ProtoInternal(pragma.DoNotImplement) {}
+// Names returns a Names list from a slice of names.
+func Names(s []pref.Name) pref.Names { return (*names)(&s) }
+
type numbersMeta struct {
once sync.Once
ns []pref.FieldNumber
@@ -68,6 +71,9 @@
func (p *fieldRanges) Format(s fmt.State, r rune) { pfmt.FormatList(s, r, p) }
func (p *fieldRanges) ProtoInternal(pragma.DoNotImplement) {}
+// FieldRanges returns a FieldRanges list from a slice of ranges.
+func FieldRanges(s [][2]pref.FieldNumber) pref.FieldRanges { return (*fieldRanges)(&s) }
+
type enumRanges [][2]pref.EnumNumber
func (p *enumRanges) Len() int { return len(*p) }
@@ -83,6 +89,9 @@
func (p *enumRanges) Format(s fmt.State, r rune) { pfmt.FormatList(s, r, p) }
func (p *enumRanges) ProtoInternal(pragma.DoNotImplement) {}
+// EnumRanges returns an EnumRanges list from a slice of ranges.
+func EnumRanges(s [][2]pref.EnumNumber) pref.EnumRanges { return (*enumRanges)(&s) }
+
type fileImports []pref.FileImport
func (p *fileImports) Len() int { return len(*p) }
diff --git a/reflect/protodesc/protodesc.go b/reflect/protodesc/protodesc.go
index 406e37f..c1ed334 100644
--- a/reflect/protodesc/protodesc.go
+++ b/reflect/protodesc/protodesc.go
@@ -23,14 +23,7 @@
// that we don't directly use?
//
// For example:
-// * That field numbers don't overlap with reserved numbers.
-// * That field names don't overlap with reserved names.
-// * That enum numbers don't overlap with reserved numbers.
-// * That enum names don't overlap with reserved names.
-// * That "extendee" is not set for a message field.
-// * That "oneof_index" is not set for an extension field.
// * That "json_name" is not set for an extension field. Maybe, maybe not.
-// * That "type_name" is not set on a field for non-enums and non-messages.
// * That "weak" is not set for an extension field (double check this).
// TODO: Store the input file descriptor to implement:
@@ -96,8 +89,10 @@
}
}
+ imps := importedFiles(f.Imports)
+
var err error
- f.Messages, err = messagesFromDescriptorProto(fd.GetMessageType(), f.Syntax, r)
+ f.Messages, err = messagesFromDescriptorProto(fd.GetMessageType(), imps, r)
if err != nil {
return nil, err
}
@@ -105,11 +100,11 @@
if err != nil {
return nil, err
}
- f.Extensions, err = extensionsFromDescriptorProto(fd.GetExtension(), r)
+ f.Extensions, err = extensionsFromDescriptorProto(fd.GetExtension(), imps, r)
if err != nil {
return nil, err
}
- f.Services, err = servicesFromDescriptorProto(fd.GetService(), r)
+ f.Services, err = servicesFromDescriptorProto(fd.GetService(), imps, r)
if err != nil {
return nil, err
}
@@ -117,16 +112,71 @@
return prototype.NewFile(&f)
}
-func messagesFromDescriptorProto(mds []*descriptorpb.DescriptorProto, syntax protoreflect.Syntax, r *protoregistry.Files) (ms []prototype.Message, err error) {
+type importSet map[protoreflect.FileDescriptor]bool
+
+func importedFiles(imps []protoreflect.FileImport) importSet {
+ ret := make(importSet)
+ for _, imp := range imps {
+ ret[imp.FileDescriptor] = true
+ addPublicImports(imp.FileDescriptor, ret)
+ }
+ return ret
+}
+
+func addPublicImports(fd protoreflect.FileDescriptor, out importSet) {
+ imps := fd.Imports()
+ for i := 0; i < imps.Len(); i++ {
+ imp := imps.Get(i)
+ if imp.IsPublic {
+ out[imp.FileDescriptor] = true
+ addPublicImports(imp.FileDescriptor, out)
+ }
+ }
+}
+
+func messagesFromDescriptorProto(mds []*descriptorpb.DescriptorProto, imps importSet, r *protoregistry.Files) (ms []prototype.Message, err error) {
for _, md := range mds {
var m prototype.Message
m.Name = protoreflect.Name(md.GetName())
m.Options = md.GetOptions()
m.IsMapEntry = md.GetOptions().GetMapEntry()
+
+ for _, s := range md.GetReservedName() {
+ m.ReservedNames = append(m.ReservedNames, protoreflect.Name(s))
+ }
+ for _, rr := range md.GetReservedRange() {
+ m.ReservedRanges = append(m.ReservedRanges, [2]protoreflect.FieldNumber{
+ protoreflect.FieldNumber(rr.GetStart()),
+ protoreflect.FieldNumber(rr.GetEnd()),
+ })
+ }
+ for _, xr := range md.GetExtensionRange() {
+ m.ExtensionRanges = append(m.ExtensionRanges, [2]protoreflect.FieldNumber{
+ protoreflect.FieldNumber(xr.GetStart()),
+ protoreflect.FieldNumber(xr.GetEnd()),
+ })
+ m.ExtensionRangeOptions = append(m.ExtensionRangeOptions, xr.GetOptions())
+ }
+ resNames := prototype.Names(m.ReservedNames)
+ resRanges := prototype.FieldRanges(m.ReservedRanges)
+ extRanges := prototype.FieldRanges(m.ExtensionRanges)
+
for _, fd := range md.GetField() {
+ if fd.GetExtendee() != "" {
+ return nil, errors.New("message field may not have extendee")
+ }
var f prototype.Field
f.Name = protoreflect.Name(fd.GetName())
+ if resNames.Has(f.Name) {
+ return nil, errors.New("%v contains field with reserved name %q", m.Name, f.Name)
+ }
f.Number = protoreflect.FieldNumber(fd.GetNumber())
+ if resRanges.Has(f.Number) {
+ return nil, errors.New("%v contains field with reserved number %d", m.Name, f.Number)
+ }
+ if extRanges.Has(f.Number) {
+ return nil, errors.New("%v contains field with number %d in extension range", m.Name, f.Number)
+ }
f.Cardinality = protoreflect.Cardinality(fd.GetLabel())
f.Kind = protoreflect.Kind(fd.GetType())
opts := fd.GetOptions()
@@ -155,7 +205,7 @@
}
switch f.Kind {
case protoreflect.EnumKind:
- f.EnumType, err = findEnumDescriptor(fd.GetTypeName(), r)
+ f.EnumType, err = findEnumDescriptor(fd.GetTypeName(), imps, r)
if err != nil {
return nil, err
}
@@ -163,13 +213,17 @@
f.EnumType = prototype.PlaceholderEnum(f.EnumType.FullName())
}
case protoreflect.MessageKind, protoreflect.GroupKind:
- f.MessageType, err = findMessageDescriptor(fd.GetTypeName(), r)
+ f.MessageType, err = findMessageDescriptor(fd.GetTypeName(), imps, r)
if err != nil {
return nil, err
}
if opts.GetWeak() && !f.MessageType.IsPlaceholder() {
f.MessageType = prototype.PlaceholderMessage(f.MessageType.FullName())
}
+ default:
+ if fd.GetTypeName() != "" {
+ return nil, errors.New("field of kind %v has type_name set", f.Kind)
+ }
}
m.Fields = append(m.Fields, f)
}
@@ -179,24 +233,8 @@
Options: od.Options,
})
}
- for _, s := range md.GetReservedName() {
- m.ReservedNames = append(m.ReservedNames, protoreflect.Name(s))
- }
- for _, rr := range md.GetReservedRange() {
- m.ReservedRanges = append(m.ReservedRanges, [2]protoreflect.FieldNumber{
- protoreflect.FieldNumber(rr.GetStart()),
- protoreflect.FieldNumber(rr.GetEnd()),
- })
- }
- for _, xr := range md.GetExtensionRange() {
- m.ExtensionRanges = append(m.ExtensionRanges, [2]protoreflect.FieldNumber{
- protoreflect.FieldNumber(xr.GetStart()),
- protoreflect.FieldNumber(xr.GetEnd()),
- })
- m.ExtensionRangeOptions = append(m.ExtensionRangeOptions, xr.GetOptions())
- }
- m.Messages, err = messagesFromDescriptorProto(md.GetNestedType(), syntax, r)
+ m.Messages, err = messagesFromDescriptorProto(md.GetNestedType(), imps, r)
if err != nil {
return nil, err
}
@@ -204,7 +242,7 @@
if err != nil {
return nil, err
}
- m.Extensions, err = extensionsFromDescriptorProto(md.GetExtension(), r)
+ m.Extensions, err = extensionsFromDescriptorProto(md.GetExtension(), imps, r)
if err != nil {
return nil, err
}
@@ -219,13 +257,6 @@
var e prototype.Enum
e.Name = protoreflect.Name(ed.GetName())
e.Options = ed.GetOptions()
- for _, vd := range ed.GetValue() {
- e.Values = append(e.Values, prototype.EnumValue{
- Name: protoreflect.Name(vd.GetName()),
- Number: protoreflect.EnumNumber(vd.GetNumber()),
- Options: vd.Options,
- })
- }
for _, s := range ed.GetReservedName() {
e.ReservedNames = append(e.ReservedNames, protoreflect.Name(s))
}
@@ -235,13 +266,33 @@
protoreflect.EnumNumber(rr.GetEnd()),
})
}
+ resNames := prototype.Names(e.ReservedNames)
+ resRanges := prototype.EnumRanges(e.ReservedRanges)
+
+ for _, vd := range ed.GetValue() {
+ v := prototype.EnumValue{
+ Name: protoreflect.Name(vd.GetName()),
+ Number: protoreflect.EnumNumber(vd.GetNumber()),
+ Options: vd.Options,
+ }
+ if resNames.Has(v.Name) {
+ return nil, errors.New("enum %v contains value with reserved name %q", e.Name, v.Name)
+ }
+ if resRanges.Has(v.Number) {
+ return nil, errors.New("enum %v contains value with reserved number %d", e.Name, v.Number)
+ }
+ e.Values = append(e.Values, v)
+ }
es = append(es, e)
}
return es, nil
}
-func extensionsFromDescriptorProto(xds []*descriptorpb.FieldDescriptorProto, r *protoregistry.Files) (xs []prototype.Extension, err error) {
+func extensionsFromDescriptorProto(xds []*descriptorpb.FieldDescriptorProto, imps importSet, r *protoregistry.Files) (xs []prototype.Extension, err error) {
for _, xd := range xds {
+ if xd.OneofIndex != nil {
+ return nil, errors.New("extension may not have oneof_index")
+ }
var x prototype.Extension
x.Name = protoreflect.Name(xd.GetName())
x.Number = protoreflect.FieldNumber(xd.GetNumber())
@@ -256,17 +307,21 @@
}
switch x.Kind {
case protoreflect.EnumKind:
- x.EnumType, err = findEnumDescriptor(xd.GetTypeName(), r)
+ x.EnumType, err = findEnumDescriptor(xd.GetTypeName(), imps, r)
if err != nil {
return nil, err
}
case protoreflect.MessageKind, protoreflect.GroupKind:
- x.MessageType, err = findMessageDescriptor(xd.GetTypeName(), r)
+ x.MessageType, err = findMessageDescriptor(xd.GetTypeName(), imps, r)
if err != nil {
return nil, err
}
+ default:
+ if xd.GetTypeName() != "" {
+ return nil, errors.New("extension of kind %v has type_name set", x.Kind)
+ }
}
- x.ExtendedType, err = findMessageDescriptor(xd.GetExtendee(), r)
+ x.ExtendedType, err = findMessageDescriptor(xd.GetExtendee(), imps, r)
if err != nil {
return nil, err
}
@@ -275,7 +330,7 @@
return xs, nil
}
-func servicesFromDescriptorProto(sds []*descriptorpb.ServiceDescriptorProto, r *protoregistry.Files) (ss []prototype.Service, err error) {
+func servicesFromDescriptorProto(sds []*descriptorpb.ServiceDescriptorProto, imps importSet, r *protoregistry.Files) (ss []prototype.Service, err error) {
for _, sd := range sds {
var s prototype.Service
s.Name = protoreflect.Name(sd.GetName())
@@ -284,11 +339,11 @@
var m prototype.Method
m.Name = protoreflect.Name(md.GetName())
m.Options = md.GetOptions()
- m.InputType, err = findMessageDescriptor(md.GetInputType(), r)
+ m.InputType, err = findMessageDescriptor(md.GetInputType(), imps, r)
if err != nil {
return nil, err
}
- m.OutputType, err = findMessageDescriptor(md.GetOutputType(), r)
+ m.OutputType, err = findMessageDescriptor(md.GetOutputType(), imps, r)
if err != nil {
return nil, err
}
@@ -306,7 +361,7 @@
// simplifies our implementation as we won't need to implement C++'s namespace
// scoping rules.
-func findMessageDescriptor(s string, r *protoregistry.Files) (protoreflect.MessageDescriptor, error) {
+func findMessageDescriptor(s string, imps importSet, r *protoregistry.Files) (protoreflect.MessageDescriptor, error) {
if !strings.HasPrefix(s, ".") {
return nil, errors.New("identifier name must be fully qualified with a leading dot: %v", s)
}
@@ -315,10 +370,33 @@
if err != nil {
return prototype.PlaceholderMessage(name), nil
}
+ if err := validateFileInImports(md, imps); err != nil {
+ return nil, err
+ }
return md, nil
}
-func findEnumDescriptor(s string, r *protoregistry.Files) (protoreflect.EnumDescriptor, error) {
+func validateFileInImports(d protoreflect.Descriptor, imps importSet) error {
+ fd := fileDescriptor(d)
+ if fd == nil {
+ return errors.New("%v has no parent FileDescriptor", d.FullName())
+ }
+ if !imps[fd] {
+ return errors.New("reference to type %v without import of %v", d.FullName(), fd.Path())
+ }
+ return nil
+}
+
+func fileDescriptor(d protoreflect.Descriptor) protoreflect.FileDescriptor {
+ for ; d != nil; d, _ = d.Parent() {
+ if fd, ok := d.(protoreflect.FileDescriptor); ok {
+ return fd
+ }
+ }
+ return nil
+}
+
+func findEnumDescriptor(s string, imps importSet, r *protoregistry.Files) (protoreflect.EnumDescriptor, error) {
if !strings.HasPrefix(s, ".") {
return nil, errors.New("identifier name must be fully qualified with a leading dot: %v", s)
}
@@ -327,6 +405,9 @@
if err != nil {
return prototype.PlaceholderEnum(name), nil
}
+ if err := validateFileInImports(ed, imps); err != nil {
+ return nil, err
+ }
return ed, nil
}
diff --git a/reflect/protodesc/protodesc_test.go b/reflect/protodesc/protodesc_test.go
new file mode 100644
index 0000000..b82078e
--- /dev/null
+++ b/reflect/protodesc/protodesc_test.go
@@ -0,0 +1,575 @@
+package protodesc
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/golang/protobuf/v2/internal/scalar"
+ "github.com/golang/protobuf/v2/reflect/protoregistry"
+
+ descriptorpb "github.com/golang/protobuf/v2/types/descriptor"
+)
+
+// Tests validation logic for malformed descriptors.
+func TestNewFile_ValidationErrors(t *testing.T) {
+ testCases := []struct {
+ name string
+ deps []*descriptorpb.FileDescriptorProto
+ fd *descriptorpb.FileDescriptorProto
+ wantErr string
+ }{{
+ name: "field number reserved",
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("field-number-reserved.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("BadMessage"),
+ ReservedRange: []*descriptorpb.DescriptorProto_ReservedRange{{
+ Start: scalar.Int32(3),
+ End: scalar.Int32(4),
+ }},
+ Field: []*descriptorpb.FieldDescriptorProto{{
+ Name: scalar.String("good_field"),
+ Number: scalar.Int32(1),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+ }, {
+ Name: scalar.String("bad_field"),
+ Number: scalar.Int32(3),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+ }},
+ }},
+ },
+ wantErr: "reserved number 3",
+ }, {
+ name: "field name reserved",
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("field-name-reserved.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("BadMessage"),
+ ReservedName: []string{"bad_field", "baz"},
+ Field: []*descriptorpb.FieldDescriptorProto{{
+ Name: scalar.String("good_field"),
+ Number: scalar.Int32(1),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+ }, {
+ Name: scalar.String("bad_field"),
+ Number: scalar.Int32(3),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+ }},
+ }},
+ },
+ wantErr: `reserved name "bad_field"`,
+ }, {
+ name: "normal field with extendee",
+ deps: []*descriptorpb.FileDescriptorProto{{
+ Name: scalar.String("extensible.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("ExtensibleMessage"),
+ ExtensionRange: []*descriptorpb.DescriptorProto_ExtensionRange{{
+ Start: scalar.Int32(1000),
+ End: scalar.Int32(2000),
+ }},
+ }},
+ }},
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("field-with-extendee.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ Dependency: []string{"extensible.proto"},
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("BadMessage"),
+ Field: []*descriptorpb.FieldDescriptorProto{{
+ Name: scalar.String("good_field"),
+ Number: scalar.Int32(1),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+ }, {
+ Name: scalar.String("bad_field"),
+ Number: scalar.Int32(3),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+ Extendee: scalar.String(".foo.ExtensibleMessage"),
+ }},
+ }},
+ },
+ wantErr: "may not have extendee",
+ }, {
+ name: "type_name on int32 field",
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("int32-with-type-name.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ EnumType: []*descriptorpb.EnumDescriptorProto{{
+ Name: scalar.String("AnEnum"),
+ Value: []*descriptorpb.EnumValueDescriptorProto{{
+ Name: scalar.String("UNKNOWN"),
+ Number: scalar.Int32(0),
+ }},
+ }},
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("BadMessage"),
+ Field: []*descriptorpb.FieldDescriptorProto{{
+ Name: scalar.String("good_field"),
+ Number: scalar.Int32(1),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+ }, {
+ Name: scalar.String("bad_field"),
+ Number: scalar.Int32(3),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum(),
+ TypeName: scalar.String("AnEnum"),
+ }},
+ }},
+ },
+ wantErr: "type_name",
+ }, {
+ name: "type_name on string extension",
+ deps: []*descriptorpb.FileDescriptorProto{{
+ Name: scalar.String("extensible.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("ExtensibleMessage"),
+ ExtensionRange: []*descriptorpb.DescriptorProto_ExtensionRange{{
+ Start: scalar.Int32(1000),
+ End: scalar.Int32(2000),
+ }},
+ }},
+ }},
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("string-ext-with-type-name.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("bar"),
+ Dependency: []string{"extensible.proto"},
+ EnumType: []*descriptorpb.EnumDescriptorProto{{
+ Name: scalar.String("AnEnum"),
+ Value: []*descriptorpb.EnumValueDescriptorProto{{
+ Name: scalar.String("UNKNOWN"),
+ Number: scalar.Int32(0),
+ }},
+ }},
+ Extension: []*descriptorpb.FieldDescriptorProto{{
+ Name: scalar.String("my_ext"),
+ Number: scalar.Int32(1000),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+ Extendee: scalar.String(".foo.ExtensibleMessage"),
+ TypeName: scalar.String("AnEnum"),
+ }},
+ },
+ wantErr: "type_name",
+ }, {
+ name: "oneof_index on extension",
+ deps: []*descriptorpb.FileDescriptorProto{{
+ Name: scalar.String("extensible.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("ExtensibleMessage"),
+ ExtensionRange: []*descriptorpb.DescriptorProto_ExtensionRange{{
+ Start: scalar.Int32(1000),
+ End: scalar.Int32(2000),
+ }},
+ }},
+ }},
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("ext-with-oneof-index.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("bar"),
+ Dependency: []string{"extensible.proto"},
+ Extension: []*descriptorpb.FieldDescriptorProto{{
+ Name: scalar.String("my_ext"),
+ Number: scalar.Int32(1000),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+ Extendee: scalar.String(".foo.ExtensibleMessage"),
+ OneofIndex: scalar.Int32(0),
+ }},
+ },
+ wantErr: "oneof_index",
+ }, {
+ name: "enum with reserved number",
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("enum-with-reserved-number.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ EnumType: []*descriptorpb.EnumDescriptorProto{{
+ Name: scalar.String("AnEnum"),
+ ReservedRange: []*descriptorpb.EnumDescriptorProto_EnumReservedRange{{
+ Start: scalar.Int32(5),
+ End: scalar.Int32(6),
+ }, {
+ Start: scalar.Int32(10),
+ End: scalar.Int32(12),
+ }},
+ Value: []*descriptorpb.EnumValueDescriptorProto{{
+ Name: scalar.String("UNKNOWN"),
+ Number: scalar.Int32(0),
+ }, {
+ Name: scalar.String("FOO"),
+ Number: scalar.Int32(1),
+ }, {
+ Name: scalar.String("BAR"),
+ Number: scalar.Int32(2),
+ }, {
+ Name: scalar.String("BAD"),
+ Number: scalar.Int32(11),
+ }},
+ }},
+ },
+ wantErr: "reserved number 11",
+ }, {
+ name: "enum with reserved number",
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("enum-with-reserved-name.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("ParentMessage"),
+ EnumType: []*descriptorpb.EnumDescriptorProto{{
+ Name: scalar.String("AnEnum"),
+ ReservedName: []string{"ABC", "XYZ"},
+ Value: []*descriptorpb.EnumValueDescriptorProto{{
+ Name: scalar.String("UNKNOWN"),
+ Number: scalar.Int32(0),
+ }, {
+ Name: scalar.String("FOO"),
+ Number: scalar.Int32(1),
+ }, {
+ Name: scalar.String("BAR"),
+ Number: scalar.Int32(2),
+ }, {
+ Name: scalar.String("XYZ"),
+ Number: scalar.Int32(3),
+ }},
+ }},
+ }},
+ },
+ wantErr: `reserved name "XYZ"`,
+ }, {
+ name: "message dependency without import",
+ deps: []*descriptorpb.FileDescriptorProto{{
+ Name: scalar.String("foo.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("Foo"),
+ }},
+ }},
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("message-dependency-without-import.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("bar"),
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("Bar"),
+ Field: []*descriptorpb.FieldDescriptorProto{{
+ Name: scalar.String("id"),
+ Number: scalar.Int32(1),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+ }, {
+ Name: scalar.String("foo"),
+ Number: scalar.Int32(2),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+ TypeName: scalar.String(".foo.Foo"),
+ }},
+ }},
+ },
+ wantErr: "foo.Foo without import of foo.proto",
+ }, {
+ name: "enum dependency without import",
+ deps: []*descriptorpb.FileDescriptorProto{{
+ Name: scalar.String("foo.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ EnumType: []*descriptorpb.EnumDescriptorProto{{
+ Name: scalar.String("Foo"),
+ Value: []*descriptorpb.EnumValueDescriptorProto{{
+ Name: scalar.String("UNKNOWN"),
+ Number: scalar.Int32(0),
+ }},
+ }},
+ }},
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("enum-dependency-without-import.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("bar"),
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("Bar"),
+ Field: []*descriptorpb.FieldDescriptorProto{{
+ Name: scalar.String("id"),
+ Number: scalar.Int32(1),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+ }, {
+ Name: scalar.String("foo"),
+ Number: scalar.Int32(2),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+ TypeName: scalar.String(".foo.Foo"),
+ }},
+ }},
+ },
+ wantErr: "foo.Foo without import of foo.proto",
+ }, {
+ name: "message dependency on without import on file imported by a public import",
+ deps: []*descriptorpb.FileDescriptorProto{{
+ Name: scalar.String("foo.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("Foo"),
+ }},
+ }, {
+ Name: scalar.String("baz.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ Dependency: []string{"foo.proto"},
+ }, {
+ Name: scalar.String("old-baz.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ Dependency: []string{"baz.proto"},
+ PublicDependency: []int32{0},
+ }},
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("message-dependency-without-import.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("bar"),
+ Dependency: []string{"old-baz.proto"},
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("Bar"),
+ Field: []*descriptorpb.FieldDescriptorProto{{
+ Name: scalar.String("id"),
+ Number: scalar.Int32(1),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+ }, {
+ Name: scalar.String("foo"),
+ Number: scalar.Int32(2),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+ TypeName: scalar.String(".foo.Foo"),
+ }},
+ }},
+ },
+ wantErr: "foo.Foo without import of foo.proto",
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ r := new(protoregistry.Files)
+ for _, dep := range tc.deps {
+ f, err := NewFile(dep, r)
+ if err != nil {
+ t.Fatalf("Error creating dependency: %v", err)
+ }
+ if err := r.Register(f); err != nil {
+ t.Fatalf("Error adding dependency: %v", err)
+ }
+ }
+ if _, err := NewFile(tc.fd, r); err == nil || !strings.Contains(err.Error(), tc.wantErr) {
+ t.Errorf("NewFile: got err = %v; want error containing %q", err, tc.wantErr)
+ }
+ })
+ }
+}
+
+// Sanity checks for well-formed descriptors. Most behavior with well-formed descriptors is covered
+// by other tests that rely on generated descriptors.
+func TestNewFile_ValidationOK(t *testing.T) {
+ testCases := []struct {
+ name string
+ deps []*descriptorpb.FileDescriptorProto
+ fd *descriptorpb.FileDescriptorProto
+ }{{
+ name: "self contained file",
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("self-contained.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ EnumType: []*descriptorpb.EnumDescriptorProto{{
+ Name: scalar.String("TopLevelEnum"),
+ Value: []*descriptorpb.EnumValueDescriptorProto{{
+ Name: scalar.String("UNKNOWN"),
+ Number: scalar.Int32(0),
+ }},
+ }},
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("TopLevelMessage"),
+ EnumType: []*descriptorpb.EnumDescriptorProto{{
+ Name: scalar.String("NestedEnum"),
+ Value: []*descriptorpb.EnumValueDescriptorProto{{
+ Name: scalar.String("UNKNOWN"),
+ Number: scalar.Int32(0),
+ }},
+ }},
+ NestedType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("NestedMessage"),
+ }},
+ Field: []*descriptorpb.FieldDescriptorProto{{
+ Name: scalar.String("id"),
+ Number: scalar.Int32(1),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+ }, {
+ Name: scalar.String("top_level_enum"),
+ Number: scalar.Int32(2),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+ TypeName: scalar.String(".foo.TopLevelEnum"),
+ }, {
+ Name: scalar.String("nested_enum"),
+ Number: scalar.Int32(3),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+ TypeName: scalar.String(".foo.TopLevelMessage.NestedEnum"),
+ }, {
+ Name: scalar.String("nested_message"),
+ Number: scalar.Int32(4),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+ TypeName: scalar.String(".foo.TopLevelMessage.NestedMessage"),
+ }},
+ }},
+ },
+ }, {
+ name: "external types with explicit import",
+ deps: []*descriptorpb.FileDescriptorProto{{
+ Name: scalar.String("foo.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("FooMessage"),
+ }},
+ EnumType: []*descriptorpb.EnumDescriptorProto{{
+ Name: scalar.String("BarEnum"),
+ Value: []*descriptorpb.EnumValueDescriptorProto{{
+ Name: scalar.String("UNKNOWN"),
+ Number: scalar.Int32(0),
+ }},
+ }},
+ }},
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("external-types-with-explicit-import.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("bar"),
+ Dependency: []string{"foo.proto"},
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("Bar"),
+ Field: []*descriptorpb.FieldDescriptorProto{{
+ Name: scalar.String("id"),
+ Number: scalar.Int32(1),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+ }, {
+ Name: scalar.String("foo"),
+ Number: scalar.Int32(2),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+ TypeName: scalar.String(".foo.FooMessage"),
+ }, {
+ Name: scalar.String("bar"),
+ Number: scalar.Int32(3),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+ TypeName: scalar.String(".foo.BarEnum"),
+ }},
+ }},
+ },
+ }, {
+ name: "external types with transitive public imports",
+ deps: []*descriptorpb.FileDescriptorProto{{
+ Name: scalar.String("quux.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("QuuxMessage"),
+ }},
+ }, {
+ Name: scalar.String("foo.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ Dependency: []string{"quux.proto"},
+ PublicDependency: []int32{0},
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("FooMessage"),
+ }},
+ EnumType: []*descriptorpb.EnumDescriptorProto{{
+ Name: scalar.String("BarEnum"),
+ Value: []*descriptorpb.EnumValueDescriptorProto{{
+ Name: scalar.String("UNKNOWN"),
+ Number: scalar.Int32(0),
+ }},
+ }},
+ }, {
+ Name: scalar.String("old-name.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("foo"),
+ Dependency: []string{"foo.proto"},
+ PublicDependency: []int32{0},
+ }},
+ fd: &descriptorpb.FileDescriptorProto{
+ Name: scalar.String("external-types-with-public-import.proto"),
+ Syntax: scalar.String("proto2"),
+ Package: scalar.String("bar"),
+ Dependency: []string{"old-name.proto"},
+ MessageType: []*descriptorpb.DescriptorProto{{
+ Name: scalar.String("Bar"),
+ Field: []*descriptorpb.FieldDescriptorProto{{
+ Name: scalar.String("id"),
+ Number: scalar.Int32(1),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
+ }, {
+ Name: scalar.String("foo"),
+ Number: scalar.Int32(2),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+ TypeName: scalar.String(".foo.FooMessage"),
+ }, {
+ Name: scalar.String("bar"),
+ Number: scalar.Int32(3),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_ENUM.Enum(),
+ TypeName: scalar.String(".foo.BarEnum"),
+ }, {
+ Name: scalar.String("quux"),
+ Number: scalar.Int32(4),
+ Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
+ Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(),
+ TypeName: scalar.String(".foo.QuuxMessage"),
+ }},
+ }},
+ },
+ }}
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ r := new(protoregistry.Files)
+ for _, dep := range tc.deps {
+ f, err := NewFile(dep, r)
+ if err != nil {
+ t.Fatalf("Error creating dependency: %v", err)
+ }
+ if err := r.Register(f); err != nil {
+ t.Fatalf("Error adding dependency: %v", err)
+ }
+ }
+ if _, err := NewFile(tc.fd, r); err != nil {
+ t.Errorf("NewFile: got err = %v", err)
+ }
+ })
+ }
+}