...

Source file src/google.golang.org/protobuf/reflect/protodesc/desc.go

Documentation: google.golang.org/protobuf/reflect/protodesc

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

View as plain text