gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }