Elia Battiston | be9edc1 | 2022-03-09 11:35:58 +0100 | [diff] [blame] | 1 | // 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. |
| 13 | package protodesc |
| 14 | |
| 15 | import ( |
| 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. |
| 32 | type 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. |
| 38 | type 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. |
| 66 | func 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. |
| 72 | func 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. |
| 84 | func (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. |
| 147 | f.L2.Locations.File = f |
| 148 | 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 | |
| 223 | type importSet map[string]bool |
| 224 | |
| 225 | func (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. |
| 238 | func (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 | } |
| 254 | func (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 | } |