desc.go (10574B)
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 }