github.com/jhump/protoreflect@v1.16.0/desc/load.go (about)

     1  package desc
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sync"
     7  
     8  	"github.com/golang/protobuf/proto"
     9  	"google.golang.org/protobuf/reflect/protoreflect"
    10  	"google.golang.org/protobuf/reflect/protoregistry"
    11  	"google.golang.org/protobuf/types/descriptorpb"
    12  
    13  	"github.com/jhump/protoreflect/desc/sourceinfo"
    14  	"github.com/jhump/protoreflect/internal"
    15  )
    16  
    17  // The global cache is used to store descriptors that wrap items in
    18  // protoregistry.GlobalTypes and protoregistry.GlobalFiles. This prevents
    19  // repeating work to re-wrap underlying global descriptors.
    20  var (
    21  	// We put all wrapped file and message descriptors in this cache.
    22  	loadedDescriptors = lockingCache{cache: mapCache{}}
    23  
    24  	// Unfortunately, we need a different mechanism for enums for
    25  	// compatibility with old APIs, which required that they were
    26  	// registered in a different way :(
    27  	loadedEnumsMu sync.RWMutex
    28  	loadedEnums   = map[reflect.Type]*EnumDescriptor{}
    29  )
    30  
    31  // LoadFileDescriptor creates a file descriptor using the bytes returned by
    32  // proto.FileDescriptor. Descriptors are cached so that they do not need to be
    33  // re-processed if the same file is fetched again later.
    34  func LoadFileDescriptor(file string) (*FileDescriptor, error) {
    35  	d, err := sourceinfo.GlobalFiles.FindFileByPath(file)
    36  	if err == protoregistry.NotFound {
    37  		// for backwards compatibility, see if this matches a known old
    38  		// alias for the file (older versions of libraries that registered
    39  		// the files using incorrect/non-canonical paths)
    40  		if alt := internal.StdFileAliases[file]; alt != "" {
    41  			d, err = sourceinfo.GlobalFiles.FindFileByPath(alt)
    42  		}
    43  	}
    44  	if err != nil {
    45  		if err != protoregistry.NotFound {
    46  			return nil, internal.ErrNoSuchFile(file)
    47  		}
    48  		return nil, err
    49  	}
    50  	if fd := loadedDescriptors.get(d); fd != nil {
    51  		return fd.(*FileDescriptor), nil
    52  	}
    53  
    54  	var fd *FileDescriptor
    55  	loadedDescriptors.withLock(func(cache descriptorCache) {
    56  		fd, err = wrapFile(d, cache)
    57  	})
    58  	return fd, err
    59  }
    60  
    61  // LoadMessageDescriptor loads descriptor using the encoded descriptor proto returned by
    62  // Message.Descriptor() for the given message type. If the given type is not recognized,
    63  // then a nil descriptor is returned.
    64  func LoadMessageDescriptor(message string) (*MessageDescriptor, error) {
    65  	mt, err := sourceinfo.GlobalTypes.FindMessageByName(protoreflect.FullName(message))
    66  	if err != nil {
    67  		if err == protoregistry.NotFound {
    68  			return nil, nil
    69  		}
    70  		return nil, err
    71  	}
    72  	return loadMessageDescriptor(mt.Descriptor())
    73  }
    74  
    75  func loadMessageDescriptor(md protoreflect.MessageDescriptor) (*MessageDescriptor, error) {
    76  	d := loadedDescriptors.get(md)
    77  	if d != nil {
    78  		return d.(*MessageDescriptor), nil
    79  	}
    80  
    81  	var err error
    82  	loadedDescriptors.withLock(func(cache descriptorCache) {
    83  		d, err = wrapMessage(md, cache)
    84  	})
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	return d.(*MessageDescriptor), err
    89  }
    90  
    91  // LoadMessageDescriptorForType loads descriptor using the encoded descriptor proto returned
    92  // by message.Descriptor() for the given message type. If the given type is not recognized,
    93  // then a nil descriptor is returned.
    94  func LoadMessageDescriptorForType(messageType reflect.Type) (*MessageDescriptor, error) {
    95  	m, err := messageFromType(messageType)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	return LoadMessageDescriptorForMessage(m)
   100  }
   101  
   102  // LoadMessageDescriptorForMessage loads descriptor using the encoded descriptor proto
   103  // returned by message.Descriptor(). If the given type is not recognized, then a nil
   104  // descriptor is returned.
   105  func LoadMessageDescriptorForMessage(message proto.Message) (*MessageDescriptor, error) {
   106  	// efficiently handle dynamic messages
   107  	type descriptorable interface {
   108  		GetMessageDescriptor() *MessageDescriptor
   109  	}
   110  	if d, ok := message.(descriptorable); ok {
   111  		return d.GetMessageDescriptor(), nil
   112  	}
   113  
   114  	var md protoreflect.MessageDescriptor
   115  	if m, ok := message.(protoreflect.ProtoMessage); ok {
   116  		md = m.ProtoReflect().Descriptor()
   117  	} else {
   118  		md = proto.MessageReflect(message).Descriptor()
   119  	}
   120  	return loadMessageDescriptor(sourceinfo.WrapMessage(md))
   121  }
   122  
   123  func messageFromType(mt reflect.Type) (proto.Message, error) {
   124  	if mt.Kind() != reflect.Ptr {
   125  		mt = reflect.PtrTo(mt)
   126  	}
   127  	m, ok := reflect.Zero(mt).Interface().(proto.Message)
   128  	if !ok {
   129  		return nil, fmt.Errorf("failed to create message from type: %v", mt)
   130  	}
   131  	return m, nil
   132  }
   133  
   134  // interface implemented by all generated enums
   135  type protoEnum interface {
   136  	EnumDescriptor() ([]byte, []int)
   137  }
   138  
   139  // NB: There is no LoadEnumDescriptor that takes a fully-qualified enum name because
   140  // it is not useful since protoc-gen-go does not expose the name anywhere in generated
   141  // code or register it in a way that is it accessible for reflection code. This also
   142  // means we have to cache enum descriptors differently -- we can only cache them as
   143  // they are requested, as opposed to caching all enum types whenever a file descriptor
   144  // is cached. This is because we need to know the generated type of the enums, and we
   145  // don't know that at the time of caching file descriptors.
   146  
   147  // LoadEnumDescriptorForType loads descriptor using the encoded descriptor proto returned
   148  // by enum.EnumDescriptor() for the given enum type.
   149  func LoadEnumDescriptorForType(enumType reflect.Type) (*EnumDescriptor, error) {
   150  	// we cache descriptors using non-pointer type
   151  	if enumType.Kind() == reflect.Ptr {
   152  		enumType = enumType.Elem()
   153  	}
   154  	e := getEnumFromCache(enumType)
   155  	if e != nil {
   156  		return e, nil
   157  	}
   158  	enum, err := enumFromType(enumType)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	return loadEnumDescriptor(enumType, enum)
   164  }
   165  
   166  func getEnumFromCache(t reflect.Type) *EnumDescriptor {
   167  	loadedEnumsMu.RLock()
   168  	defer loadedEnumsMu.RUnlock()
   169  	return loadedEnums[t]
   170  }
   171  
   172  func putEnumInCache(t reflect.Type, d *EnumDescriptor) {
   173  	loadedEnumsMu.Lock()
   174  	defer loadedEnumsMu.Unlock()
   175  	loadedEnums[t] = d
   176  }
   177  
   178  // LoadEnumDescriptorForEnum loads descriptor using the encoded descriptor proto
   179  // returned by enum.EnumDescriptor().
   180  func LoadEnumDescriptorForEnum(enum protoEnum) (*EnumDescriptor, error) {
   181  	et := reflect.TypeOf(enum)
   182  	// we cache descriptors using non-pointer type
   183  	if et.Kind() == reflect.Ptr {
   184  		et = et.Elem()
   185  		enum = reflect.Zero(et).Interface().(protoEnum)
   186  	}
   187  	e := getEnumFromCache(et)
   188  	if e != nil {
   189  		return e, nil
   190  	}
   191  
   192  	return loadEnumDescriptor(et, enum)
   193  }
   194  
   195  func enumFromType(et reflect.Type) (protoEnum, error) {
   196  	e, ok := reflect.Zero(et).Interface().(protoEnum)
   197  	if !ok {
   198  		if et.Kind() != reflect.Ptr {
   199  			et = et.Elem()
   200  		}
   201  		e, ok = reflect.Zero(et).Interface().(protoEnum)
   202  	}
   203  	if !ok {
   204  		return nil, fmt.Errorf("failed to create enum from type: %v", et)
   205  	}
   206  	return e, nil
   207  }
   208  
   209  func getDescriptorForEnum(enum protoEnum) (*descriptorpb.FileDescriptorProto, []int, error) {
   210  	fdb, path := enum.EnumDescriptor()
   211  	name := fmt.Sprintf("%T", enum)
   212  	fd, err := internal.DecodeFileDescriptor(name, fdb)
   213  	return fd, path, err
   214  }
   215  
   216  func loadEnumDescriptor(et reflect.Type, enum protoEnum) (*EnumDescriptor, error) {
   217  	fdp, path, err := getDescriptorForEnum(enum)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	fd, err := LoadFileDescriptor(fdp.GetName())
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	ed := findEnum(fd, path)
   228  	putEnumInCache(et, ed)
   229  	return ed, nil
   230  }
   231  
   232  func findEnum(fd *FileDescriptor, path []int) *EnumDescriptor {
   233  	if len(path) == 1 {
   234  		return fd.GetEnumTypes()[path[0]]
   235  	}
   236  	md := fd.GetMessageTypes()[path[0]]
   237  	for _, i := range path[1 : len(path)-1] {
   238  		md = md.GetNestedMessageTypes()[i]
   239  	}
   240  	return md.GetNestedEnumTypes()[path[len(path)-1]]
   241  }
   242  
   243  // LoadFieldDescriptorForExtension loads the field descriptor that corresponds to the given
   244  // extension description.
   245  func LoadFieldDescriptorForExtension(ext *proto.ExtensionDesc) (*FieldDescriptor, error) {
   246  	file, err := LoadFileDescriptor(ext.Filename)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  	field, ok := file.FindSymbol(ext.Name).(*FieldDescriptor)
   251  	// make sure descriptor agrees with attributes of the ExtensionDesc
   252  	if !ok || !field.IsExtension() || field.GetOwner().GetFullyQualifiedName() != proto.MessageName(ext.ExtendedType) ||
   253  		field.GetNumber() != ext.Field {
   254  		return nil, fmt.Errorf("file descriptor contained unexpected object with name %s", ext.Name)
   255  	}
   256  	return field, nil
   257  }