desc.go raw

   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  	"strings"
  17  
  18  	"google.golang.org/protobuf/internal/editionssupport"
  19  	"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  
  30  // Resolver is the resolver used by [NewFile] to resolve dependencies.
  31  // The enums and messages provided must belong to some parent file,
  32  // which is also registered.
  33  //
  34  // It is implemented by [protoregistry.Files].
  35  type 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.
  41  type 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  
  67  // NewFile creates a new [protoreflect.FileDescriptor] from the provided
  68  // file descriptor message. See [FileOptions.New] for more information.
  69  func NewFile(fd *descriptorpb.FileDescriptorProto, r Resolver) (protoreflect.FileDescriptor, error) {
  70  	return FileOptions{}.New(fd, r)
  71  }
  72  
  73  // NewFiles creates a new [protoregistry.Files] from the provided
  74  // FileDescriptorSet message. See [FileOptions.NewFiles] for more information.
  75  func NewFiles(fd *descriptorpb.FileDescriptorSet) (*protoregistry.Files, error) {
  76  	return FileOptions{}.NewFiles(fd)
  77  }
  78  
  79  // New creates a new [protoreflect.FileDescriptor] from the provided
  80  // 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.
  87  func (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
  97  		f.L1.Edition = filedesc.EditionProto2
  98  	case "proto3":
  99  		f.L1.Syntax = protoreflect.Proto3
 100  		f.L1.Edition = filedesc.EditionProto3
 101  	case "editions":
 102  		f.L1.Syntax = protoreflect.Editions
 103  		f.L1.Edition = fromEditionProto(fd.GetEdition())
 104  	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  	}
 111  	if f.L1.Syntax == protoreflect.Editions &&
 112  		(fd.GetEdition() < editionssupport.Minimum || fd.GetEdition() > editionssupport.Maximum) &&
 113  		fd.GetEdition() != descriptorpb.Edition_EDITION_UNSTABLE {
 114  		// Allow cmd/protoc-gen-go/testdata to use any edition for easier
 115  		// testing of upcoming edition features.
 116  		if !strings.HasPrefix(fd.GetName(), "cmd/protoc-gen-go/testdata/") {
 117  			return nil, errors.New("use of edition %v not yet supported by the Go Protobuf runtime", fd.GetEdition())
 118  		}
 119  	}
 120  	f.L1.Package = protoreflect.FullName(fd.GetPackage())
 121  	if !f.L1.Package.IsValid() && f.L1.Package != "" {
 122  		return nil, errors.New("invalid package: %q", f.L1.Package)
 123  	}
 124  	if opts := fd.GetOptions(); opts != nil {
 125  		opts = proto.Clone(opts).(*descriptorpb.FileOptions)
 126  		f.L2.Options = func() protoreflect.ProtoMessage { return opts }
 127  	}
 128  	initFileDescFromFeatureSet(f, fd.GetOptions().GetFeatures())
 129  
 130  	f.L2.Imports = make(filedesc.FileImports, len(fd.GetDependency()))
 131  	for _, i := range fd.GetPublicDependency() {
 132  		if !(0 <= i && int(i) < len(f.L2.Imports)) || f.L2.Imports[i].IsPublic {
 133  			return nil, errors.New("invalid or duplicate public import index: %d", i)
 134  		}
 135  		f.L2.Imports[i].IsPublic = true
 136  	}
 137  	imps := importSet{f.Path(): true}
 138  	for i, path := range fd.GetDependency() {
 139  		imp := &f.L2.Imports[i]
 140  		f, err := r.FindFileByPath(path)
 141  		if err == protoregistry.NotFound && o.AllowUnresolvable {
 142  			f = filedesc.PlaceholderFile(path)
 143  		} else if err != nil {
 144  			return nil, errors.New("could not resolve import %q: %v", path, err)
 145  		}
 146  		imp.FileDescriptor = f
 147  
 148  		if imps[imp.Path()] {
 149  			return nil, errors.New("already imported %q", path)
 150  		}
 151  		imps[imp.Path()] = true
 152  	}
 153  	for i := range fd.GetDependency() {
 154  		imp := &f.L2.Imports[i]
 155  		imps.importPublic(imp.Imports())
 156  	}
 157  	optionImps := importSet{f.Path(): true}
 158  	if len(fd.GetOptionDependency()) > 0 {
 159  		optionImports := make(filedesc.FileImports, len(fd.GetOptionDependency()))
 160  		for i, path := range fd.GetOptionDependency() {
 161  			imp := &optionImports[i]
 162  			f, err := r.FindFileByPath(path)
 163  			if err == protoregistry.NotFound {
 164  				// We always allow option imports to be unresolvable.
 165  				f = filedesc.PlaceholderFile(path)
 166  			} else if err != nil {
 167  				return nil, errors.New("could not resolve import %q: %v", path, err)
 168  			}
 169  			imp.FileDescriptor = f
 170  
 171  			if imps[imp.Path()] || optionImps[imp.Path()] {
 172  				return nil, errors.New("already imported %q", path)
 173  			}
 174  			// This needs to be a separate map so that we don't recognize non-options
 175  			// symbols coming from option imports.
 176  			optionImps[imp.Path()] = true
 177  		}
 178  		f.L2.OptionImports = func() protoreflect.FileImports {
 179  			return &optionImports
 180  		}
 181  	}
 182  
 183  	// Handle source locations.
 184  	f.L2.Locations.File = f
 185  	for _, loc := range fd.GetSourceCodeInfo().GetLocation() {
 186  		var l protoreflect.SourceLocation
 187  		// TODO: Validate that the path points to an actual declaration?
 188  		l.Path = protoreflect.SourcePath(loc.GetPath())
 189  		s := loc.GetSpan()
 190  		switch len(s) {
 191  		case 3:
 192  			l.StartLine, l.StartColumn, l.EndLine, l.EndColumn = int(s[0]), int(s[1]), int(s[0]), int(s[2])
 193  		case 4:
 194  			l.StartLine, l.StartColumn, l.EndLine, l.EndColumn = int(s[0]), int(s[1]), int(s[2]), int(s[3])
 195  		default:
 196  			return nil, errors.New("invalid span: %v", s)
 197  		}
 198  		// TODO: Validate that the span information is sensible?
 199  		// See https://github.com/protocolbuffers/protobuf/issues/6378.
 200  		if false && (l.EndLine < l.StartLine || l.StartLine < 0 || l.StartColumn < 0 || l.EndColumn < 0 ||
 201  			(l.StartLine == l.EndLine && l.EndColumn <= l.StartColumn)) {
 202  			return nil, errors.New("invalid span: %v", s)
 203  		}
 204  		l.LeadingDetachedComments = loc.GetLeadingDetachedComments()
 205  		l.LeadingComments = loc.GetLeadingComments()
 206  		l.TrailingComments = loc.GetTrailingComments()
 207  		f.L2.Locations.List = append(f.L2.Locations.List, l)
 208  	}
 209  
 210  	// Step 1: Allocate and derive the names for all declarations.
 211  	// This copies all fields from the descriptor proto except:
 212  	//	google.protobuf.FieldDescriptorProto.type_name
 213  	//	google.protobuf.FieldDescriptorProto.default_value
 214  	//	google.protobuf.FieldDescriptorProto.oneof_index
 215  	//	google.protobuf.FieldDescriptorProto.extendee
 216  	//	google.protobuf.MethodDescriptorProto.input
 217  	//	google.protobuf.MethodDescriptorProto.output
 218  	var err error
 219  	sb := new(strs.Builder)
 220  	r1 := make(descsByName)
 221  	if f.L1.Enums.List, err = r1.initEnumDeclarations(fd.GetEnumType(), f, sb); err != nil {
 222  		return nil, err
 223  	}
 224  	if f.L1.Messages.List, err = r1.initMessagesDeclarations(fd.GetMessageType(), f, sb); err != nil {
 225  		return nil, err
 226  	}
 227  	if f.L1.Extensions.List, err = r1.initExtensionDeclarations(fd.GetExtension(), f, sb); err != nil {
 228  		return nil, err
 229  	}
 230  	if f.L1.Services.List, err = r1.initServiceDeclarations(fd.GetService(), f, sb); err != nil {
 231  		return nil, err
 232  	}
 233  
 234  	// Step 2: Resolve every dependency reference not handled by step 1.
 235  	r2 := &resolver{local: r1, remote: r, imports: imps, allowUnresolvable: o.AllowUnresolvable}
 236  	if err := r2.resolveMessageDependencies(f.L1.Messages.List, fd.GetMessageType()); err != nil {
 237  		return nil, err
 238  	}
 239  	if err := r2.resolveExtensionDependencies(f.L1.Extensions.List, fd.GetExtension()); err != nil {
 240  		return nil, err
 241  	}
 242  	if err := r2.resolveServiceDependencies(f.L1.Services.List, fd.GetService()); err != nil {
 243  		return nil, err
 244  	}
 245  
 246  	// Step 3: Validate every enum, message, and extension declaration.
 247  	if err := validateEnumDeclarations(f.L1.Enums.List, fd.GetEnumType()); err != nil {
 248  		return nil, err
 249  	}
 250  	if err := validateMessageDeclarations(f, f.L1.Messages.List, fd.GetMessageType()); err != nil {
 251  		return nil, err
 252  	}
 253  	if err := validateExtensionDeclarations(f, f.L1.Extensions.List, fd.GetExtension()); err != nil {
 254  		return nil, err
 255  	}
 256  
 257  	return f, nil
 258  }
 259  
 260  type importSet map[string]bool
 261  
 262  func (is importSet) importPublic(imps protoreflect.FileImports) {
 263  	for i := 0; i < imps.Len(); i++ {
 264  		if imp := imps.Get(i); imp.IsPublic {
 265  			is[imp.Path()] = true
 266  			is.importPublic(imp.Imports())
 267  		}
 268  	}
 269  }
 270  
 271  // NewFiles creates a new [protoregistry.Files] from the provided
 272  // FileDescriptorSet message. The descriptor set must include only
 273  // valid files according to protobuf semantics. The returned descriptors
 274  // are a deep copy of the input.
 275  func (o FileOptions) NewFiles(fds *descriptorpb.FileDescriptorSet) (*protoregistry.Files, error) {
 276  	files := make(map[string]*descriptorpb.FileDescriptorProto)
 277  	for _, fd := range fds.File {
 278  		if _, ok := files[fd.GetName()]; ok {
 279  			return nil, errors.New("file appears multiple times: %q", fd.GetName())
 280  		}
 281  		files[fd.GetName()] = fd
 282  	}
 283  	r := &protoregistry.Files{}
 284  	for _, fd := range files {
 285  		if err := o.addFileDeps(r, fd, files); err != nil {
 286  			return nil, err
 287  		}
 288  	}
 289  	return r, nil
 290  }
 291  func (o FileOptions) addFileDeps(r *protoregistry.Files, fd *descriptorpb.FileDescriptorProto, files map[string]*descriptorpb.FileDescriptorProto) error {
 292  	// Set the entry to nil while descending into a file's dependencies to detect cycles.
 293  	files[fd.GetName()] = nil
 294  	for _, dep := range fd.Dependency {
 295  		depfd, ok := files[dep]
 296  		if depfd == nil {
 297  			if ok {
 298  				return errors.New("import cycle in file: %q", dep)
 299  			}
 300  			continue
 301  		}
 302  		if err := o.addFileDeps(r, depfd, files); err != nil {
 303  			return err
 304  		}
 305  	}
 306  	// Delete the entry once dependencies are processed.
 307  	delete(files, fd.GetName())
 308  	f, err := o.New(fd, r)
 309  	if err != nil {
 310  		return err
 311  	}
 312  	return r.RegisterFile(f)
 313  }
 314