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 }