blob: e4dfb1205063b9b4a9895a1be8c8cfddd291ad81 [file] [log] [blame]
amit.ghosh258d14c2020-10-02 15:13:38 +02001// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package protodesc provides functionality for converting
6// FileDescriptorProto messages to/from protoreflect.FileDescriptor values.
7//
8// The google.protobuf.FileDescriptorProto is a protobuf message that describes
9// the type information for a .proto file in a form that is easily serializable.
10// The protoreflect.FileDescriptor is a more structured representation of
11// the FileDescriptorProto message where references and remote dependencies
12// can be directly followed.
13package protodesc
14
15import (
16 "google.golang.org/protobuf/internal/errors"
17 "google.golang.org/protobuf/internal/filedesc"
18 "google.golang.org/protobuf/internal/pragma"
19 "google.golang.org/protobuf/internal/strs"
20 "google.golang.org/protobuf/proto"
21 "google.golang.org/protobuf/reflect/protoreflect"
22 "google.golang.org/protobuf/reflect/protoregistry"
23
24 "google.golang.org/protobuf/types/descriptorpb"
25)
26
27// Resolver is the resolver used by NewFile to resolve dependencies.
28// The enums and messages provided must belong to some parent file,
29// which is also registered.
30//
31// It is implemented by protoregistry.Files.
32type Resolver interface {
33 FindFileByPath(string) (protoreflect.FileDescriptor, error)
34 FindDescriptorByName(protoreflect.FullName) (protoreflect.Descriptor, error)
35}
36
37// FileOptions configures the construction of file descriptors.
38type FileOptions struct {
39 pragma.NoUnkeyedLiterals
40
41 // AllowUnresolvable configures New to permissively allow unresolvable
42 // file, enum, or message dependencies. Unresolved dependencies are replaced
43 // by placeholder equivalents.
44 //
45 // The following dependencies may be left unresolved:
46 // • Resolving an imported file.
47 // • Resolving the type for a message field or extension field.
48 // If the kind of the field is unknown, then a placeholder is used for both
49 // the Enum and Message accessors on the protoreflect.FieldDescriptor.
50 // • Resolving an enum value set as the default for an optional enum field.
51 // If unresolvable, the protoreflect.FieldDescriptor.Default is set to the
52 // first value in the associated enum (or zero if the also enum dependency
53 // is also unresolvable). The protoreflect.FieldDescriptor.DefaultEnumValue
54 // is populated with a placeholder.
55 // • Resolving the extended message type for an extension field.
56 // • Resolving the input or output message type for a service method.
57 //
58 // If the unresolved dependency uses a relative name,
59 // then the placeholder will contain an invalid FullName with a "*." prefix,
60 // indicating that the starting prefix of the full name is unknown.
61 AllowUnresolvable bool
62}
63
64// NewFile creates a new protoreflect.FileDescriptor from the provided
65// file descriptor message. See FileOptions.New for more information.
66func NewFile(fd *descriptorpb.FileDescriptorProto, r Resolver) (protoreflect.FileDescriptor, error) {
67 return FileOptions{}.New(fd, r)
68}
69
70// NewFiles creates a new protoregistry.Files from the provided
71// FileDescriptorSet message. See FileOptions.NewFiles for more information.
72func NewFiles(fd *descriptorpb.FileDescriptorSet) (*protoregistry.Files, error) {
73 return FileOptions{}.NewFiles(fd)
74}
75
76// New creates a new protoreflect.FileDescriptor from the provided
77// file descriptor message. The file must represent a valid proto file according
78// to protobuf semantics. The returned descriptor is a deep copy of the input.
79//
80// Any imported files, enum types, or message types referenced in the file are
81// resolved using the provided registry. When looking up an import file path,
82// the path must be unique. The newly created file descriptor is not registered
83// back into the provided file registry.
84func (o FileOptions) New(fd *descriptorpb.FileDescriptorProto, r Resolver) (protoreflect.FileDescriptor, error) {
85 if r == nil {
86 r = (*protoregistry.Files)(nil) // empty resolver
87 }
88
89 // Handle the file descriptor content.
90 f := &filedesc.File{L2: &filedesc.FileL2{}}
91 switch fd.GetSyntax() {
92 case "proto2", "":
93 f.L1.Syntax = protoreflect.Proto2
94 case "proto3":
95 f.L1.Syntax = protoreflect.Proto3
96 default:
97 return nil, errors.New("invalid syntax: %q", fd.GetSyntax())
98 }
99 f.L1.Path = fd.GetName()
100 if f.L1.Path == "" {
101 return nil, errors.New("file path must be populated")
102 }
103 f.L1.Package = protoreflect.FullName(fd.GetPackage())
104 if !f.L1.Package.IsValid() && f.L1.Package != "" {
105 return nil, errors.New("invalid package: %q", f.L1.Package)
106 }
107 if opts := fd.GetOptions(); opts != nil {
108 opts = proto.Clone(opts).(*descriptorpb.FileOptions)
109 f.L2.Options = func() protoreflect.ProtoMessage { return opts }
110 }
111
112 f.L2.Imports = make(filedesc.FileImports, len(fd.GetDependency()))
113 for _, i := range fd.GetPublicDependency() {
114 if !(0 <= i && int(i) < len(f.L2.Imports)) || f.L2.Imports[i].IsPublic {
115 return nil, errors.New("invalid or duplicate public import index: %d", i)
116 }
117 f.L2.Imports[i].IsPublic = true
118 }
119 for _, i := range fd.GetWeakDependency() {
120 if !(0 <= i && int(i) < len(f.L2.Imports)) || f.L2.Imports[i].IsWeak {
121 return nil, errors.New("invalid or duplicate weak import index: %d", i)
122 }
123 f.L2.Imports[i].IsWeak = true
124 }
125 imps := importSet{f.Path(): true}
126 for i, path := range fd.GetDependency() {
127 imp := &f.L2.Imports[i]
128 f, err := r.FindFileByPath(path)
129 if err == protoregistry.NotFound && (o.AllowUnresolvable || imp.IsWeak) {
130 f = filedesc.PlaceholderFile(path)
131 } else if err != nil {
132 return nil, errors.New("could not resolve import %q: %v", path, err)
133 }
134 imp.FileDescriptor = f
135
136 if imps[imp.Path()] {
137 return nil, errors.New("already imported %q", path)
138 }
139 imps[imp.Path()] = true
140 }
141 for i := range fd.GetDependency() {
142 imp := &f.L2.Imports[i]
143 imps.importPublic(imp.Imports())
144 }
145
146 // Handle source locations.
David K. Bainbridgec415efe2021-08-19 13:05:21 +0000147 f.L2.Locations.File = f
amit.ghosh258d14c2020-10-02 15:13:38 +0200148 for _, loc := range fd.GetSourceCodeInfo().GetLocation() {
149 var l protoreflect.SourceLocation
150 // TODO: Validate that the path points to an actual declaration?
151 l.Path = protoreflect.SourcePath(loc.GetPath())
152 s := loc.GetSpan()
153 switch len(s) {
154 case 3:
155 l.StartLine, l.StartColumn, l.EndLine, l.EndColumn = int(s[0]), int(s[1]), int(s[0]), int(s[2])
156 case 4:
157 l.StartLine, l.StartColumn, l.EndLine, l.EndColumn = int(s[0]), int(s[1]), int(s[2]), int(s[3])
158 default:
159 return nil, errors.New("invalid span: %v", s)
160 }
161 // TODO: Validate that the span information is sensible?
162 // See https://github.com/protocolbuffers/protobuf/issues/6378.
163 if false && (l.EndLine < l.StartLine || l.StartLine < 0 || l.StartColumn < 0 || l.EndColumn < 0 ||
164 (l.StartLine == l.EndLine && l.EndColumn <= l.StartColumn)) {
165 return nil, errors.New("invalid span: %v", s)
166 }
167 l.LeadingDetachedComments = loc.GetLeadingDetachedComments()
168 l.LeadingComments = loc.GetLeadingComments()
169 l.TrailingComments = loc.GetTrailingComments()
170 f.L2.Locations.List = append(f.L2.Locations.List, l)
171 }
172
173 // Step 1: Allocate and derive the names for all declarations.
174 // This copies all fields from the descriptor proto except:
175 // google.protobuf.FieldDescriptorProto.type_name
176 // google.protobuf.FieldDescriptorProto.default_value
177 // google.protobuf.FieldDescriptorProto.oneof_index
178 // google.protobuf.FieldDescriptorProto.extendee
179 // google.protobuf.MethodDescriptorProto.input
180 // google.protobuf.MethodDescriptorProto.output
181 var err error
182 sb := new(strs.Builder)
183 r1 := make(descsByName)
184 if f.L1.Enums.List, err = r1.initEnumDeclarations(fd.GetEnumType(), f, sb); err != nil {
185 return nil, err
186 }
187 if f.L1.Messages.List, err = r1.initMessagesDeclarations(fd.GetMessageType(), f, sb); err != nil {
188 return nil, err
189 }
190 if f.L1.Extensions.List, err = r1.initExtensionDeclarations(fd.GetExtension(), f, sb); err != nil {
191 return nil, err
192 }
193 if f.L1.Services.List, err = r1.initServiceDeclarations(fd.GetService(), f, sb); err != nil {
194 return nil, err
195 }
196
197 // Step 2: Resolve every dependency reference not handled by step 1.
198 r2 := &resolver{local: r1, remote: r, imports: imps, allowUnresolvable: o.AllowUnresolvable}
199 if err := r2.resolveMessageDependencies(f.L1.Messages.List, fd.GetMessageType()); err != nil {
200 return nil, err
201 }
202 if err := r2.resolveExtensionDependencies(f.L1.Extensions.List, fd.GetExtension()); err != nil {
203 return nil, err
204 }
205 if err := r2.resolveServiceDependencies(f.L1.Services.List, fd.GetService()); err != nil {
206 return nil, err
207 }
208
209 // Step 3: Validate every enum, message, and extension declaration.
210 if err := validateEnumDeclarations(f.L1.Enums.List, fd.GetEnumType()); err != nil {
211 return nil, err
212 }
213 if err := validateMessageDeclarations(f.L1.Messages.List, fd.GetMessageType()); err != nil {
214 return nil, err
215 }
216 if err := validateExtensionDeclarations(f.L1.Extensions.List, fd.GetExtension()); err != nil {
217 return nil, err
218 }
219
220 return f, nil
221}
222
223type importSet map[string]bool
224
225func (is importSet) importPublic(imps protoreflect.FileImports) {
226 for i := 0; i < imps.Len(); i++ {
227 if imp := imps.Get(i); imp.IsPublic {
228 is[imp.Path()] = true
229 is.importPublic(imp.Imports())
230 }
231 }
232}
233
234// NewFiles creates a new protoregistry.Files from the provided
235// FileDescriptorSet message. The descriptor set must include only
236// valid files according to protobuf semantics. The returned descriptors
237// are a deep copy of the input.
238func (o FileOptions) NewFiles(fds *descriptorpb.FileDescriptorSet) (*protoregistry.Files, error) {
239 files := make(map[string]*descriptorpb.FileDescriptorProto)
240 for _, fd := range fds.File {
241 if _, ok := files[fd.GetName()]; ok {
242 return nil, errors.New("file appears multiple times: %q", fd.GetName())
243 }
244 files[fd.GetName()] = fd
245 }
246 r := &protoregistry.Files{}
247 for _, fd := range files {
248 if err := o.addFileDeps(r, fd, files); err != nil {
249 return nil, err
250 }
251 }
252 return r, nil
253}
254func (o FileOptions) addFileDeps(r *protoregistry.Files, fd *descriptorpb.FileDescriptorProto, files map[string]*descriptorpb.FileDescriptorProto) error {
255 // Set the entry to nil while descending into a file's dependencies to detect cycles.
256 files[fd.GetName()] = nil
257 for _, dep := range fd.Dependency {
258 depfd, ok := files[dep]
259 if depfd == nil {
260 if ok {
261 return errors.New("import cycle in file: %q", dep)
262 }
263 continue
264 }
265 if err := o.addFileDeps(r, depfd, files); err != nil {
266 return err
267 }
268 }
269 // Delete the entry once dependencies are processed.
270 delete(files, fd.GetName())
271 f, err := o.New(fd, r)
272 if err != nil {
273 return err
274 }
275 return r.RegisterFile(f)
276}