blob: 9196288e4acee670f003ac14846fa183b91a23b7 [file] [log] [blame]
khenaidoo26721882021-08-11 17:42:52 -04001// 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
Abhay Kumar40252eb2025-10-13 13:25:53 +00006// FileDescriptorProto messages to/from [protoreflect.FileDescriptor] values.
khenaidoo26721882021-08-11 17:42:52 -04007//
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.
Abhay Kumar40252eb2025-10-13 13:25:53 +000010// The [protoreflect.FileDescriptor] is a more structured representation of
khenaidoo26721882021-08-11 17:42:52 -040011// the FileDescriptorProto message where references and remote dependencies
12// can be directly followed.
13package protodesc
14
15import (
Abhay Kumar40252eb2025-10-13 13:25:53 +000016 "strings"
17
18 "google.golang.org/protobuf/internal/editionssupport"
khenaidoo26721882021-08-11 17:42:52 -040019 "google.golang.org/protobuf/internal/errors"
20 "google.golang.org/protobuf/internal/filedesc"
21 "google.golang.org/protobuf/internal/pragma"
22 "google.golang.org/protobuf/internal/strs"
23 "google.golang.org/protobuf/proto"
24 "google.golang.org/protobuf/reflect/protoreflect"
25 "google.golang.org/protobuf/reflect/protoregistry"
26
27 "google.golang.org/protobuf/types/descriptorpb"
28)
29
Abhay Kumar40252eb2025-10-13 13:25:53 +000030// Resolver is the resolver used by [NewFile] to resolve dependencies.
khenaidoo26721882021-08-11 17:42:52 -040031// The enums and messages provided must belong to some parent file,
32// which is also registered.
33//
Abhay Kumar40252eb2025-10-13 13:25:53 +000034// It is implemented by [protoregistry.Files].
khenaidoo26721882021-08-11 17:42:52 -040035type Resolver interface {
36 FindFileByPath(string) (protoreflect.FileDescriptor, error)
37 FindDescriptorByName(protoreflect.FullName) (protoreflect.Descriptor, error)
38}
39
40// FileOptions configures the construction of file descriptors.
41type FileOptions struct {
42 pragma.NoUnkeyedLiterals
43
44 // AllowUnresolvable configures New to permissively allow unresolvable
45 // file, enum, or message dependencies. Unresolved dependencies are replaced
46 // by placeholder equivalents.
47 //
48 // The following dependencies may be left unresolved:
49 // • Resolving an imported file.
50 // • Resolving the type for a message field or extension field.
51 // If the kind of the field is unknown, then a placeholder is used for both
52 // the Enum and Message accessors on the protoreflect.FieldDescriptor.
53 // • Resolving an enum value set as the default for an optional enum field.
54 // If unresolvable, the protoreflect.FieldDescriptor.Default is set to the
55 // first value in the associated enum (or zero if the also enum dependency
56 // is also unresolvable). The protoreflect.FieldDescriptor.DefaultEnumValue
57 // is populated with a placeholder.
58 // • Resolving the extended message type for an extension field.
59 // • Resolving the input or output message type for a service method.
60 //
61 // If the unresolved dependency uses a relative name,
62 // then the placeholder will contain an invalid FullName with a "*." prefix,
63 // indicating that the starting prefix of the full name is unknown.
64 AllowUnresolvable bool
65}
66
Abhay Kumar40252eb2025-10-13 13:25:53 +000067// NewFile creates a new [protoreflect.FileDescriptor] from the provided
68// file descriptor message. See [FileOptions.New] for more information.
khenaidoo26721882021-08-11 17:42:52 -040069func NewFile(fd *descriptorpb.FileDescriptorProto, r Resolver) (protoreflect.FileDescriptor, error) {
70 return FileOptions{}.New(fd, r)
71}
72
Abhay Kumar40252eb2025-10-13 13:25:53 +000073// NewFiles creates a new [protoregistry.Files] from the provided
74// FileDescriptorSet message. See [FileOptions.NewFiles] for more information.
khenaidoo26721882021-08-11 17:42:52 -040075func NewFiles(fd *descriptorpb.FileDescriptorSet) (*protoregistry.Files, error) {
76 return FileOptions{}.NewFiles(fd)
77}
78
Abhay Kumar40252eb2025-10-13 13:25:53 +000079// New creates a new [protoreflect.FileDescriptor] from the provided
khenaidoo26721882021-08-11 17:42:52 -040080// file descriptor message. The file must represent a valid proto file according
81// to protobuf semantics. The returned descriptor is a deep copy of the input.
82//
83// Any imported files, enum types, or message types referenced in the file are
84// resolved using the provided registry. When looking up an import file path,
85// the path must be unique. The newly created file descriptor is not registered
86// back into the provided file registry.
87func (o FileOptions) New(fd *descriptorpb.FileDescriptorProto, r Resolver) (protoreflect.FileDescriptor, error) {
88 if r == nil {
89 r = (*protoregistry.Files)(nil) // empty resolver
90 }
91
92 // Handle the file descriptor content.
93 f := &filedesc.File{L2: &filedesc.FileL2{}}
94 switch fd.GetSyntax() {
95 case "proto2", "":
96 f.L1.Syntax = protoreflect.Proto2
Abhay Kumar40252eb2025-10-13 13:25:53 +000097 f.L1.Edition = filedesc.EditionProto2
khenaidoo26721882021-08-11 17:42:52 -040098 case "proto3":
99 f.L1.Syntax = protoreflect.Proto3
Abhay Kumar40252eb2025-10-13 13:25:53 +0000100 f.L1.Edition = filedesc.EditionProto3
101 case "editions":
102 f.L1.Syntax = protoreflect.Editions
103 f.L1.Edition = fromEditionProto(fd.GetEdition())
khenaidoo26721882021-08-11 17:42:52 -0400104 default:
105 return nil, errors.New("invalid syntax: %q", fd.GetSyntax())
106 }
107 f.L1.Path = fd.GetName()
108 if f.L1.Path == "" {
109 return nil, errors.New("file path must be populated")
110 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000111 if f.L1.Syntax == protoreflect.Editions && (fd.GetEdition() < editionssupport.Minimum || fd.GetEdition() > editionssupport.Maximum) {
112 // Allow cmd/protoc-gen-go/testdata to use any edition for easier
113 // testing of upcoming edition features.
114 if !strings.HasPrefix(fd.GetName(), "cmd/protoc-gen-go/testdata/") {
115 return nil, errors.New("use of edition %v not yet supported by the Go Protobuf runtime", fd.GetEdition())
116 }
117 }
khenaidoo26721882021-08-11 17:42:52 -0400118 f.L1.Package = protoreflect.FullName(fd.GetPackage())
119 if !f.L1.Package.IsValid() && f.L1.Package != "" {
120 return nil, errors.New("invalid package: %q", f.L1.Package)
121 }
122 if opts := fd.GetOptions(); opts != nil {
123 opts = proto.Clone(opts).(*descriptorpb.FileOptions)
124 f.L2.Options = func() protoreflect.ProtoMessage { return opts }
125 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000126 initFileDescFromFeatureSet(f, fd.GetOptions().GetFeatures())
khenaidoo26721882021-08-11 17:42:52 -0400127
128 f.L2.Imports = make(filedesc.FileImports, len(fd.GetDependency()))
129 for _, i := range fd.GetPublicDependency() {
130 if !(0 <= i && int(i) < len(f.L2.Imports)) || f.L2.Imports[i].IsPublic {
131 return nil, errors.New("invalid or duplicate public import index: %d", i)
132 }
133 f.L2.Imports[i].IsPublic = true
134 }
khenaidoo26721882021-08-11 17:42:52 -0400135 imps := importSet{f.Path(): true}
136 for i, path := range fd.GetDependency() {
137 imp := &f.L2.Imports[i]
138 f, err := r.FindFileByPath(path)
Abhay Kumar40252eb2025-10-13 13:25:53 +0000139 if err == protoregistry.NotFound && o.AllowUnresolvable {
khenaidoo26721882021-08-11 17:42:52 -0400140 f = filedesc.PlaceholderFile(path)
141 } else if err != nil {
142 return nil, errors.New("could not resolve import %q: %v", path, err)
143 }
144 imp.FileDescriptor = f
145
146 if imps[imp.Path()] {
147 return nil, errors.New("already imported %q", path)
148 }
149 imps[imp.Path()] = true
150 }
151 for i := range fd.GetDependency() {
152 imp := &f.L2.Imports[i]
153 imps.importPublic(imp.Imports())
154 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000155 if len(fd.GetOptionDependency()) > 0 {
156 optionImports := make(filedesc.FileImports, len(fd.GetOptionDependency()))
157 for i, path := range fd.GetOptionDependency() {
158 imp := &optionImports[i]
159 f, err := r.FindFileByPath(path)
160 if err == protoregistry.NotFound {
161 // We always allow option imports to be unresolvable.
162 f = filedesc.PlaceholderFile(path)
163 } else if err != nil {
164 return nil, errors.New("could not resolve import %q: %v", path, err)
165 }
166 imp.FileDescriptor = f
167
168 if imps[imp.Path()] {
169 return nil, errors.New("already imported %q", path)
170 }
171 imps[imp.Path()] = true
172 }
173 f.L2.OptionImports = func() protoreflect.FileImports {
174 return &optionImports
175 }
176 }
khenaidoo26721882021-08-11 17:42:52 -0400177
178 // Handle source locations.
179 f.L2.Locations.File = f
180 for _, loc := range fd.GetSourceCodeInfo().GetLocation() {
181 var l protoreflect.SourceLocation
182 // TODO: Validate that the path points to an actual declaration?
183 l.Path = protoreflect.SourcePath(loc.GetPath())
184 s := loc.GetSpan()
185 switch len(s) {
186 case 3:
187 l.StartLine, l.StartColumn, l.EndLine, l.EndColumn = int(s[0]), int(s[1]), int(s[0]), int(s[2])
188 case 4:
189 l.StartLine, l.StartColumn, l.EndLine, l.EndColumn = int(s[0]), int(s[1]), int(s[2]), int(s[3])
190 default:
191 return nil, errors.New("invalid span: %v", s)
192 }
193 // TODO: Validate that the span information is sensible?
194 // See https://github.com/protocolbuffers/protobuf/issues/6378.
195 if false && (l.EndLine < l.StartLine || l.StartLine < 0 || l.StartColumn < 0 || l.EndColumn < 0 ||
196 (l.StartLine == l.EndLine && l.EndColumn <= l.StartColumn)) {
197 return nil, errors.New("invalid span: %v", s)
198 }
199 l.LeadingDetachedComments = loc.GetLeadingDetachedComments()
200 l.LeadingComments = loc.GetLeadingComments()
201 l.TrailingComments = loc.GetTrailingComments()
202 f.L2.Locations.List = append(f.L2.Locations.List, l)
203 }
204
205 // Step 1: Allocate and derive the names for all declarations.
206 // This copies all fields from the descriptor proto except:
207 // google.protobuf.FieldDescriptorProto.type_name
208 // google.protobuf.FieldDescriptorProto.default_value
209 // google.protobuf.FieldDescriptorProto.oneof_index
210 // google.protobuf.FieldDescriptorProto.extendee
211 // google.protobuf.MethodDescriptorProto.input
212 // google.protobuf.MethodDescriptorProto.output
213 var err error
214 sb := new(strs.Builder)
215 r1 := make(descsByName)
216 if f.L1.Enums.List, err = r1.initEnumDeclarations(fd.GetEnumType(), f, sb); err != nil {
217 return nil, err
218 }
219 if f.L1.Messages.List, err = r1.initMessagesDeclarations(fd.GetMessageType(), f, sb); err != nil {
220 return nil, err
221 }
222 if f.L1.Extensions.List, err = r1.initExtensionDeclarations(fd.GetExtension(), f, sb); err != nil {
223 return nil, err
224 }
225 if f.L1.Services.List, err = r1.initServiceDeclarations(fd.GetService(), f, sb); err != nil {
226 return nil, err
227 }
228
229 // Step 2: Resolve every dependency reference not handled by step 1.
230 r2 := &resolver{local: r1, remote: r, imports: imps, allowUnresolvable: o.AllowUnresolvable}
231 if err := r2.resolveMessageDependencies(f.L1.Messages.List, fd.GetMessageType()); err != nil {
232 return nil, err
233 }
234 if err := r2.resolveExtensionDependencies(f.L1.Extensions.List, fd.GetExtension()); err != nil {
235 return nil, err
236 }
237 if err := r2.resolveServiceDependencies(f.L1.Services.List, fd.GetService()); err != nil {
238 return nil, err
239 }
240
241 // Step 3: Validate every enum, message, and extension declaration.
242 if err := validateEnumDeclarations(f.L1.Enums.List, fd.GetEnumType()); err != nil {
243 return nil, err
244 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000245 if err := validateMessageDeclarations(f, f.L1.Messages.List, fd.GetMessageType()); err != nil {
khenaidoo26721882021-08-11 17:42:52 -0400246 return nil, err
247 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000248 if err := validateExtensionDeclarations(f, f.L1.Extensions.List, fd.GetExtension()); err != nil {
khenaidoo26721882021-08-11 17:42:52 -0400249 return nil, err
250 }
251
252 return f, nil
253}
254
255type importSet map[string]bool
256
257func (is importSet) importPublic(imps protoreflect.FileImports) {
258 for i := 0; i < imps.Len(); i++ {
259 if imp := imps.Get(i); imp.IsPublic {
260 is[imp.Path()] = true
261 is.importPublic(imp.Imports())
262 }
263 }
264}
265
Abhay Kumar40252eb2025-10-13 13:25:53 +0000266// NewFiles creates a new [protoregistry.Files] from the provided
khenaidoo26721882021-08-11 17:42:52 -0400267// FileDescriptorSet message. The descriptor set must include only
268// valid files according to protobuf semantics. The returned descriptors
269// are a deep copy of the input.
270func (o FileOptions) NewFiles(fds *descriptorpb.FileDescriptorSet) (*protoregistry.Files, error) {
271 files := make(map[string]*descriptorpb.FileDescriptorProto)
272 for _, fd := range fds.File {
273 if _, ok := files[fd.GetName()]; ok {
274 return nil, errors.New("file appears multiple times: %q", fd.GetName())
275 }
276 files[fd.GetName()] = fd
277 }
278 r := &protoregistry.Files{}
279 for _, fd := range files {
280 if err := o.addFileDeps(r, fd, files); err != nil {
281 return nil, err
282 }
283 }
284 return r, nil
285}
286func (o FileOptions) addFileDeps(r *protoregistry.Files, fd *descriptorpb.FileDescriptorProto, files map[string]*descriptorpb.FileDescriptorProto) error {
287 // Set the entry to nil while descending into a file's dependencies to detect cycles.
288 files[fd.GetName()] = nil
289 for _, dep := range fd.Dependency {
290 depfd, ok := files[dep]
291 if depfd == nil {
292 if ok {
293 return errors.New("import cycle in file: %q", dep)
294 }
295 continue
296 }
297 if err := o.addFileDeps(r, depfd, files); err != nil {
298 return err
299 }
300 }
301 // Delete the entry once dependencies are processed.
302 delete(files, fd.GetName())
303 f, err := o.New(fd, r)
304 if err != nil {
305 return err
306 }
307 return r.RegisterFile(f)
308}