github.com/jhump/protoreflect@v1.16.0/desc/sourceinfo/registry.go (about) 1 // Package sourceinfo provides the ability to register and query source code info 2 // for file descriptors that are compiled into the binary. This data is registered 3 // by code generated from the protoc-gen-gosrcinfo plugin. 4 // 5 // The standard descriptors bundled into the compiled binary are stripped of source 6 // code info, to reduce binary size and reduce runtime memory footprint. However, 7 // the source code info can be very handy and worth the size cost when used with 8 // gRPC services and the server reflection service. Without source code info, the 9 // descriptors that a client downloads from the reflection service have no comments. 10 // But the presence of comments, and the ability to show them to humans, can greatly 11 // improve the utility of user agents that use the reflection service. 12 // 13 // When the protoc-gen-gosrcinfo plugin is used, the desc.Load* methods, which load 14 // descriptors for compiled-in elements, will automatically include source code 15 // info, using the data registered with this package. 16 // 17 // In order to make the reflection service use this functionality, you will need to 18 // be using v1.45 or higher of the Go runtime for gRPC (google.golang.org/grpc). The 19 // following snippet demonstrates how to do this in your server. Do this instead of 20 // using the reflection.Register function: 21 // 22 // refSvr := reflection.NewServer(reflection.ServerOptions{ 23 // Services: grpcServer, 24 // DescriptorResolver: sourceinfo.GlobalFiles, 25 // ExtensionResolver: sourceinfo.GlobalFiles, 26 // }) 27 // grpc_reflection_v1alpha.RegisterServerReflectionServer(grpcServer, refSvr) 28 package sourceinfo 29 30 import ( 31 "bytes" 32 "compress/gzip" 33 "fmt" 34 "github.com/golang/protobuf/proto" 35 "io/ioutil" 36 "sync" 37 38 "google.golang.org/protobuf/reflect/protodesc" 39 "google.golang.org/protobuf/reflect/protoreflect" 40 "google.golang.org/protobuf/reflect/protoregistry" 41 "google.golang.org/protobuf/types/descriptorpb" 42 ) 43 44 var ( 45 // GlobalFiles is a registry of descriptors that include source code info, if the 46 // files they belong to were processed with protoc-gen-gosrcinfo. 47 // 48 // If is mean to serve as a drop-in alternative to protoregistry.GlobalFiles that 49 // can include source code info in the returned descriptors. 50 GlobalFiles Resolver = registry{} 51 52 // GlobalTypes is a registry of descriptors that include source code info, if the 53 // files they belong to were processed with protoc-gen-gosrcinfo. 54 // 55 // If is mean to serve as a drop-in alternative to protoregistry.GlobalTypes that 56 // can include source code info in the returned descriptors. 57 GlobalTypes TypeResolver = registry{} 58 59 mu sync.RWMutex 60 sourceInfoByFile = map[string]*descriptorpb.SourceCodeInfo{} 61 fileDescriptors = map[protoreflect.FileDescriptor]protoreflect.FileDescriptor{} 62 ) 63 64 // Resolver can resolve file names into file descriptors and also provides methods for 65 // resolving extensions. 66 type Resolver interface { 67 protodesc.Resolver 68 protoregistry.ExtensionTypeResolver 69 RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool) 70 } 71 72 // NB: These interfaces are far from ideal. Ideally, Resolver would have 73 // * EITHER been named FileResolver and not included the extension methods. 74 // * OR also included message methods (i.e. embed protoregistry.MessageTypeResolver). 75 // Now (since it's been released) we can't add the message methods to the interface as 76 // that's not a backwards-compatible change. So we have to introduce the new interface 77 // below, which is now a little confusing since it has some overlap with Resolver. 78 79 // TypeResolver can resolve message names and URLs into message descriptors and also 80 // provides methods for resolving extensions. 81 type TypeResolver interface { 82 protoregistry.MessageTypeResolver 83 protoregistry.ExtensionTypeResolver 84 RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool) 85 } 86 87 // RegisterSourceInfo registers the given source code info for the file descriptor 88 // with the given path/name. 89 // 90 // This is automatically used from older generated code if using a previous release of 91 // the protoc-gen-gosrcinfo plugin. 92 func RegisterSourceInfo(file string, srcInfo *descriptorpb.SourceCodeInfo) { 93 mu.Lock() 94 defer mu.Unlock() 95 sourceInfoByFile[file] = srcInfo 96 } 97 98 // RegisterEncodedSourceInfo registers the given source code info, which is a serialized 99 // and gzipped form of a google.protobuf.SourceCodeInfo message. 100 // 101 // This is automatically used from generated code if using the protoc-gen-gosrcinfo 102 // plugin. 103 func RegisterEncodedSourceInfo(file string, data []byte) error { 104 zipReader, err := gzip.NewReader(bytes.NewReader(data)) 105 if err != nil { 106 return err 107 } 108 defer func() { 109 _ = zipReader.Close() 110 }() 111 unzipped, err := ioutil.ReadAll(zipReader) 112 if err != nil { 113 return err 114 } 115 var srcInfo descriptorpb.SourceCodeInfo 116 if err := proto.Unmarshal(unzipped, &srcInfo); err != nil { 117 return err 118 } 119 RegisterSourceInfo(file, &srcInfo) 120 return nil 121 } 122 123 // SourceInfoForFile queries for any registered source code info for the file 124 // descriptor with the given path/name. It returns nil if no source code info 125 // was registered. 126 func SourceInfoForFile(file string) *descriptorpb.SourceCodeInfo { 127 mu.RLock() 128 defer mu.RUnlock() 129 return sourceInfoByFile[file] 130 } 131 132 func canWrap(d protoreflect.Descriptor) bool { 133 srcInfo := SourceInfoForFile(d.ParentFile().Path()) 134 return len(srcInfo.GetLocation()) > 0 135 } 136 137 func getFile(fd protoreflect.FileDescriptor) protoreflect.FileDescriptor { 138 if fd == nil { 139 return nil 140 } 141 142 mu.RLock() 143 result := fileDescriptors[fd] 144 mu.RUnlock() 145 146 if result != nil { 147 return result 148 } 149 150 mu.Lock() 151 defer mu.Unlock() 152 // double-check, in case it was added to map while upgrading lock 153 result = fileDescriptors[fd] 154 if result != nil { 155 return result 156 } 157 158 srcInfo := sourceInfoByFile[fd.Path()] 159 if len(srcInfo.GetLocation()) > 0 { 160 result = &fileDescriptor{ 161 FileDescriptor: fd, 162 locs: &sourceLocations{ 163 orig: srcInfo.Location, 164 }, 165 } 166 } else { 167 // nothing to do; don't bother wrapping 168 result = fd 169 } 170 fileDescriptors[fd] = result 171 return result 172 } 173 174 type registry struct{} 175 176 var _ protodesc.Resolver = ®istry{} 177 178 func (r registry) FindFileByPath(path string) (protoreflect.FileDescriptor, error) { 179 fd, err := protoregistry.GlobalFiles.FindFileByPath(path) 180 if err != nil { 181 return nil, err 182 } 183 return getFile(fd), nil 184 } 185 186 func (r registry) FindDescriptorByName(name protoreflect.FullName) (protoreflect.Descriptor, error) { 187 d, err := protoregistry.GlobalFiles.FindDescriptorByName(name) 188 if !canWrap(d) { 189 return d, nil 190 } 191 if err != nil { 192 return nil, err 193 } 194 switch d := d.(type) { 195 case protoreflect.FileDescriptor: 196 return getFile(d), nil 197 case protoreflect.MessageDescriptor: 198 return messageDescriptor{d}, nil 199 case protoreflect.ExtensionTypeDescriptor: 200 return extensionDescriptor{d}, nil 201 case protoreflect.FieldDescriptor: 202 return fieldDescriptor{d}, nil 203 case protoreflect.OneofDescriptor: 204 return oneOfDescriptor{d}, nil 205 case protoreflect.EnumDescriptor: 206 return enumDescriptor{d}, nil 207 case protoreflect.EnumValueDescriptor: 208 return enumValueDescriptor{d}, nil 209 case protoreflect.ServiceDescriptor: 210 return serviceDescriptor{d}, nil 211 case protoreflect.MethodDescriptor: 212 return methodDescriptor{d}, nil 213 default: 214 return nil, fmt.Errorf("unrecognized descriptor type: %T", d) 215 } 216 } 217 218 func (r registry) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error) { 219 mt, err := protoregistry.GlobalTypes.FindMessageByName(message) 220 if err != nil { 221 return nil, err 222 } 223 if !canWrap(mt.Descriptor()) { 224 return mt, nil 225 } 226 return messageType{mt}, nil 227 } 228 229 func (r registry) FindMessageByURL(url string) (protoreflect.MessageType, error) { 230 mt, err := protoregistry.GlobalTypes.FindMessageByURL(url) 231 if err != nil { 232 return nil, err 233 } 234 if !canWrap(mt.Descriptor()) { 235 return mt, nil 236 } 237 return messageType{mt}, nil 238 } 239 240 func (r registry) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) { 241 xt, err := protoregistry.GlobalTypes.FindExtensionByName(field) 242 if err != nil { 243 return nil, err 244 } 245 if !canWrap(xt.TypeDescriptor()) { 246 return xt, nil 247 } 248 return extensionType{xt}, nil 249 } 250 251 func (r registry) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) { 252 xt, err := protoregistry.GlobalTypes.FindExtensionByNumber(message, field) 253 if err != nil { 254 return nil, err 255 } 256 if !canWrap(xt.TypeDescriptor()) { 257 return xt, nil 258 } 259 return extensionType{xt}, nil 260 } 261 262 func (r registry) RangeExtensionsByMessage(message protoreflect.FullName, fn func(protoreflect.ExtensionType) bool) { 263 protoregistry.GlobalTypes.RangeExtensionsByMessage(message, func(xt protoreflect.ExtensionType) bool { 264 if canWrap(xt.TypeDescriptor()) { 265 xt = extensionType{xt} 266 } 267 return fn(xt) 268 }) 269 }