blob: 8fd09ac18f46a24c1baa74d6ff4337a1a5c3a16c [file] [log] [blame]
khenaidoo0927c722021-12-15 16:49:32 -05001package desc
2
3import (
Abhay Kumar40252eb2025-10-13 13:25:53 +00004 "errors"
khenaidoo0927c722021-12-15 16:49:32 -05005 "fmt"
6 "reflect"
7 "sync"
8
9 "github.com/golang/protobuf/proto"
Abhay Kumar40252eb2025-10-13 13:25:53 +000010 "google.golang.org/protobuf/reflect/protoreflect"
11 "google.golang.org/protobuf/reflect/protoregistry"
12 "google.golang.org/protobuf/types/descriptorpb"
khenaidoo0927c722021-12-15 16:49:32 -050013
Abhay Kumar40252eb2025-10-13 13:25:53 +000014 "github.com/jhump/protoreflect/desc/sourceinfo"
khenaidoo0927c722021-12-15 16:49:32 -050015 "github.com/jhump/protoreflect/internal"
16)
17
Abhay Kumar40252eb2025-10-13 13:25:53 +000018// The global cache is used to store descriptors that wrap items in
19// protoregistry.GlobalTypes and protoregistry.GlobalFiles. This prevents
20// repeating work to re-wrap underlying global descriptors.
khenaidoo0927c722021-12-15 16:49:32 -050021var (
Abhay Kumar40252eb2025-10-13 13:25:53 +000022 // We put all wrapped file and message descriptors in this cache.
23 loadedDescriptors = lockingCache{cache: mapCache{}}
24
25 // Unfortunately, we need a different mechanism for enums for
26 // compatibility with old APIs, which required that they were
27 // registered in a different way :(
28 loadedEnumsMu sync.RWMutex
29 loadedEnums = map[reflect.Type]*EnumDescriptor{}
khenaidoo0927c722021-12-15 16:49:32 -050030)
31
32// LoadFileDescriptor creates a file descriptor using the bytes returned by
33// proto.FileDescriptor. Descriptors are cached so that they do not need to be
34// re-processed if the same file is fetched again later.
35func LoadFileDescriptor(file string) (*FileDescriptor, error) {
Abhay Kumar40252eb2025-10-13 13:25:53 +000036 d, err := sourceinfo.GlobalFiles.FindFileByPath(file)
37 if errors.Is(err, protoregistry.NotFound) {
38 // for backwards compatibility, see if this matches a known old
39 // alias for the file (older versions of libraries that registered
40 // the files using incorrect/non-canonical paths)
41 if alt := internal.StdFileAliases[file]; alt != "" {
42 d, err = sourceinfo.GlobalFiles.FindFileByPath(alt)
khenaidoo0927c722021-12-15 16:49:32 -050043 }
44 }
Abhay Kumar40252eb2025-10-13 13:25:53 +000045 if err != nil {
46 if !errors.Is(err, protoregistry.NotFound) {
47 return nil, internal.ErrNoSuchFile(file)
48 }
49 return nil, err
khenaidoo0927c722021-12-15 16:49:32 -050050 }
Abhay Kumar40252eb2025-10-13 13:25:53 +000051 if fd := loadedDescriptors.get(d); fd != nil {
52 return fd.(*FileDescriptor), nil
53 }
khenaidoo0927c722021-12-15 16:49:32 -050054
Abhay Kumar40252eb2025-10-13 13:25:53 +000055 var fd *FileDescriptor
56 loadedDescriptors.withLock(func(cache descriptorCache) {
57 fd, err = wrapFile(d, cache)
58 })
59 return fd, err
khenaidoo0927c722021-12-15 16:49:32 -050060}
61
62// LoadMessageDescriptor loads descriptor using the encoded descriptor proto returned by
63// Message.Descriptor() for the given message type. If the given type is not recognized,
64// then a nil descriptor is returned.
65func LoadMessageDescriptor(message string) (*MessageDescriptor, error) {
Abhay Kumar40252eb2025-10-13 13:25:53 +000066 mt, err := sourceinfo.GlobalTypes.FindMessageByName(protoreflect.FullName(message))
67 if err != nil {
68 if errors.Is(err, protoregistry.NotFound) {
69 return nil, nil
70 }
71 return nil, err
72 }
73 return loadMessageDescriptor(mt.Descriptor())
khenaidoo0927c722021-12-15 16:49:32 -050074}
75
Abhay Kumar40252eb2025-10-13 13:25:53 +000076func loadMessageDescriptor(md protoreflect.MessageDescriptor) (*MessageDescriptor, error) {
77 d := loadedDescriptors.get(md)
78 if d != nil {
79 return d.(*MessageDescriptor), nil
khenaidoo0927c722021-12-15 16:49:32 -050080 }
81
Abhay Kumar40252eb2025-10-13 13:25:53 +000082 var err error
83 loadedDescriptors.withLock(func(cache descriptorCache) {
84 d, err = wrapMessage(md, cache)
85 })
khenaidoo0927c722021-12-15 16:49:32 -050086 if err != nil {
87 return nil, err
88 }
Abhay Kumar40252eb2025-10-13 13:25:53 +000089 return d.(*MessageDescriptor), err
khenaidoo0927c722021-12-15 16:49:32 -050090}
91
92// LoadMessageDescriptorForType loads descriptor using the encoded descriptor proto returned
93// by message.Descriptor() for the given message type. If the given type is not recognized,
94// then a nil descriptor is returned.
95func LoadMessageDescriptorForType(messageType reflect.Type) (*MessageDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -050096 m, err := messageFromType(messageType)
97 if err != nil {
98 return nil, err
99 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000100 return LoadMessageDescriptorForMessage(m)
khenaidoo0927c722021-12-15 16:49:32 -0500101}
102
103// LoadMessageDescriptorForMessage loads descriptor using the encoded descriptor proto
104// returned by message.Descriptor(). If the given type is not recognized, then a nil
105// descriptor is returned.
106func LoadMessageDescriptorForMessage(message proto.Message) (*MessageDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500107 // efficiently handle dynamic messages
108 type descriptorable interface {
109 GetMessageDescriptor() *MessageDescriptor
110 }
111 if d, ok := message.(descriptorable); ok {
112 return d.GetMessageDescriptor(), nil
113 }
114
Abhay Kumar40252eb2025-10-13 13:25:53 +0000115 var md protoreflect.MessageDescriptor
116 if m, ok := message.(protoreflect.ProtoMessage); ok {
117 md = m.ProtoReflect().Descriptor()
118 } else {
119 md = proto.MessageReflect(message).Descriptor()
khenaidoo0927c722021-12-15 16:49:32 -0500120 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000121 return loadMessageDescriptor(sourceinfo.WrapMessage(md))
khenaidoo0927c722021-12-15 16:49:32 -0500122}
123
Abhay Kumar40252eb2025-10-13 13:25:53 +0000124func messageFromType(mt reflect.Type) (proto.Message, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500125 if mt.Kind() != reflect.Ptr {
126 mt = reflect.PtrTo(mt)
127 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000128 m, ok := reflect.Zero(mt).Interface().(proto.Message)
khenaidoo0927c722021-12-15 16:49:32 -0500129 if !ok {
130 return nil, fmt.Errorf("failed to create message from type: %v", mt)
131 }
132 return m, nil
133}
134
khenaidoo0927c722021-12-15 16:49:32 -0500135// interface implemented by all generated enums
136type protoEnum interface {
137 EnumDescriptor() ([]byte, []int)
138}
139
140// NB: There is no LoadEnumDescriptor that takes a fully-qualified enum name because
141// it is not useful since protoc-gen-go does not expose the name anywhere in generated
142// code or register it in a way that is it accessible for reflection code. This also
143// means we have to cache enum descriptors differently -- we can only cache them as
144// they are requested, as opposed to caching all enum types whenever a file descriptor
145// is cached. This is because we need to know the generated type of the enums, and we
146// don't know that at the time of caching file descriptors.
147
148// LoadEnumDescriptorForType loads descriptor using the encoded descriptor proto returned
149// by enum.EnumDescriptor() for the given enum type.
150func LoadEnumDescriptorForType(enumType reflect.Type) (*EnumDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500151 // we cache descriptors using non-pointer type
152 if enumType.Kind() == reflect.Ptr {
153 enumType = enumType.Elem()
154 }
155 e := getEnumFromCache(enumType)
156 if e != nil {
157 return e, nil
158 }
159 enum, err := enumFromType(enumType)
160 if err != nil {
161 return nil, err
162 }
163
Abhay Kumar40252eb2025-10-13 13:25:53 +0000164 return loadEnumDescriptor(enumType, enum)
165}
166
167func getEnumFromCache(t reflect.Type) *EnumDescriptor {
168 loadedEnumsMu.RLock()
169 defer loadedEnumsMu.RUnlock()
170 return loadedEnums[t]
171}
172
173func putEnumInCache(t reflect.Type, d *EnumDescriptor) {
174 loadedEnumsMu.Lock()
175 defer loadedEnumsMu.Unlock()
176 loadedEnums[t] = d
khenaidoo0927c722021-12-15 16:49:32 -0500177}
178
179// LoadEnumDescriptorForEnum loads descriptor using the encoded descriptor proto
180// returned by enum.EnumDescriptor().
181func LoadEnumDescriptorForEnum(enum protoEnum) (*EnumDescriptor, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500182 et := reflect.TypeOf(enum)
183 // we cache descriptors using non-pointer type
184 if et.Kind() == reflect.Ptr {
185 et = et.Elem()
186 enum = reflect.Zero(et).Interface().(protoEnum)
187 }
188 e := getEnumFromCache(et)
189 if e != nil {
190 return e, nil
191 }
192
Abhay Kumar40252eb2025-10-13 13:25:53 +0000193 return loadEnumDescriptor(et, enum)
khenaidoo0927c722021-12-15 16:49:32 -0500194}
195
196func enumFromType(et reflect.Type) (protoEnum, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500197 e, ok := reflect.Zero(et).Interface().(protoEnum)
198 if !ok {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000199 if et.Kind() != reflect.Ptr {
200 et = et.Elem()
201 }
202 e, ok = reflect.Zero(et).Interface().(protoEnum)
203 }
204 if !ok {
khenaidoo0927c722021-12-15 16:49:32 -0500205 return nil, fmt.Errorf("failed to create enum from type: %v", et)
206 }
207 return e, nil
208}
209
Abhay Kumar40252eb2025-10-13 13:25:53 +0000210func getDescriptorForEnum(enum protoEnum) (*descriptorpb.FileDescriptorProto, []int, error) {
khenaidoo0927c722021-12-15 16:49:32 -0500211 fdb, path := enum.EnumDescriptor()
Abhay Kumar40252eb2025-10-13 13:25:53 +0000212 name := fmt.Sprintf("%T", enum)
khenaidoo0927c722021-12-15 16:49:32 -0500213 fd, err := internal.DecodeFileDescriptor(name, fdb)
Abhay Kumar40252eb2025-10-13 13:25:53 +0000214 return fd, path, err
215}
216
217func loadEnumDescriptor(et reflect.Type, enum protoEnum) (*EnumDescriptor, error) {
218 fdp, path, err := getDescriptorForEnum(enum)
khenaidoo0927c722021-12-15 16:49:32 -0500219 if err != nil {
220 return nil, err
221 }
Abhay Kumar40252eb2025-10-13 13:25:53 +0000222
223 fd, err := LoadFileDescriptor(fdp.GetName())
224 if err != nil {
225 return nil, err
khenaidoo0927c722021-12-15 16:49:32 -0500226 }
227
Abhay Kumar40252eb2025-10-13 13:25:53 +0000228 ed := findEnum(fd, path)
229 putEnumInCache(et, ed)
khenaidoo0927c722021-12-15 16:49:32 -0500230 return ed, nil
231}
232
khenaidoo0927c722021-12-15 16:49:32 -0500233func findEnum(fd *FileDescriptor, path []int) *EnumDescriptor {
234 if len(path) == 1 {
235 return fd.GetEnumTypes()[path[0]]
236 }
237 md := fd.GetMessageTypes()[path[0]]
238 for _, i := range path[1 : len(path)-1] {
239 md = md.GetNestedMessageTypes()[i]
240 }
241 return md.GetNestedEnumTypes()[path[len(path)-1]]
242}
243
244// LoadFieldDescriptorForExtension loads the field descriptor that corresponds to the given
245// extension description.
246func LoadFieldDescriptorForExtension(ext *proto.ExtensionDesc) (*FieldDescriptor, error) {
Abhay Kumar40252eb2025-10-13 13:25:53 +0000247 file, err := LoadFileDescriptor(ext.Filename)
khenaidoo0927c722021-12-15 16:49:32 -0500248 if err != nil {
249 return nil, err
250 }
251 field, ok := file.FindSymbol(ext.Name).(*FieldDescriptor)
252 // make sure descriptor agrees with attributes of the ExtensionDesc
253 if !ok || !field.IsExtension() || field.GetOwner().GetFullyQualifiedName() != proto.MessageName(ext.ExtendedType) ||
254 field.GetNumber() != ext.Field {
255 return nil, fmt.Errorf("file descriptor contained unexpected object with name %s", ext.Name)
256 }
257 return field, nil
258}