// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package protoregistry provides data structures to register and lookup // protobuf descriptor types. // // The Files registry contains file descriptors and provides the ability // to iterate over the files or lookup a specific descriptor within the files. // Files only contains protobuf descriptors and has no understanding of Go // type information that may be associated with each descriptor. // // The Types registry contains descriptor types for which there is a known // Go type associated with that descriptor. It provides the ability to iterate // over the registered types or lookup a type by name.
package protoregistry import ( ) // conflictPolicy configures the policy for handling registration conflicts. // // It can be over-written at compile time with a linker-initialized variable: // // go build -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn" // // It can be over-written at program execution with an environment variable: // // GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn ./main // // Neither of the above are covered by the compatibility promise and // may be removed in a future release of this module. var conflictPolicy = "panic" // "panic" | "warn" | "ignore" // ignoreConflict reports whether to ignore a registration conflict // given the descriptor being registered and the error. // It is a variable so that the behavior is easily overridden in another file. var ignoreConflict = func( protoreflect.Descriptor, error) bool { const = "GOLANG_PROTOBUF_REGISTRATION_CONFLICT" const = "https://developers.google.com/protocol-buffers/docs/reference/go/faq#namespace-conflict" := conflictPolicy if := os.Getenv(); != "" { = } switch { case "panic": panic(fmt.Sprintf("%v\nSee %v\n", , )) case "warn": fmt.Fprintf(os.Stderr, "WARNING: %v\nSee %v\n\n", , ) return true case "ignore": return true default: panic("invalid " + + " value: " + os.Getenv()) } } var globalMutex sync.RWMutex // GlobalFiles is a global registry of file descriptors. var GlobalFiles *Files = new(Files) // GlobalTypes is the registry used by default for type lookups // unless a local registry is provided by the user. var GlobalTypes *Types = new(Types) // NotFound is a sentinel error value to indicate that the type was not found. // // Since registry lookup can happen in the critical performance path, resolvers // must return this exact error value, not an error wrapping it. var NotFound = errors.New("not found") // Files is a registry for looking up or iterating over files and the // descriptors contained within them. // The Find and Range methods are safe for concurrent use. type Files struct { // The map of descsByName contains: // EnumDescriptor // EnumValueDescriptor // MessageDescriptor // ExtensionDescriptor // ServiceDescriptor // *packageDescriptor // // Note that files are stored as a slice, since a package may contain // multiple files. Only top-level declarations are registered. // Note that enum values are in the top-level since that are in the same // scope as the parent enum. descsByName map[protoreflect.FullName]interface{} filesByPath map[string][]protoreflect.FileDescriptor numFiles int } type packageDescriptor struct { files []protoreflect.FileDescriptor } // RegisterFile registers the provided file descriptor. // // If any descriptor within the file conflicts with the descriptor of any // previously registered file (e.g., two enums with the same full name), // then the file is not registered and an error is returned. // // It is permitted for multiple files to have the same file path. func ( *Files) ( protoreflect.FileDescriptor) error { if == GlobalFiles { globalMutex.Lock() defer globalMutex.Unlock() } if .descsByName == nil { .descsByName = map[protoreflect.FullName]interface{}{ "": &packageDescriptor{}, } .filesByPath = make(map[string][]protoreflect.FileDescriptor) } := .Path() if := .filesByPath[]; len() > 0 { .checkGenProtoConflict() := errors.New("file %q is already registered", .Path()) = amendErrorWithCaller(, [0], ) if !( == GlobalFiles && ignoreConflict(, )) { return } } for := .Package(); != ""; = .Parent() { switch := .descsByName[]; .(type) { case nil, *packageDescriptor: default: := errors.New("file %q has a package name conflict over %v", .Path(), ) = amendErrorWithCaller(, , ) if == GlobalFiles && ignoreConflict(, ) { = nil } return } } var error var bool rangeTopLevelDescriptors(, func( protoreflect.Descriptor) { if := .descsByName[.FullName()]; != nil { = true = errors.New("file %q has a name conflict over %v", .Path(), .FullName()) = amendErrorWithCaller(, , ) if == GlobalFiles && ignoreConflict(, ) { = nil } } }) if { return } for := .Package(); != ""; = .Parent() { if .descsByName[] == nil { .descsByName[] = &packageDescriptor{} } } := .descsByName[.Package()].(*packageDescriptor) .files = append(.files, ) rangeTopLevelDescriptors(, func( protoreflect.Descriptor) { .descsByName[.FullName()] = }) .filesByPath[] = append(.filesByPath[], ) .numFiles++ return nil } // Several well-known types were hosted in the google.golang.org/genproto module // but were later moved to this module. To avoid a weak dependency on the // genproto module (and its relatively large set of transitive dependencies), // we rely on a registration conflict to determine whether the genproto version // is too old (i.e., does not contain aliases to the new type declarations). func ( *Files) ( string) { if != GlobalFiles { return } var string const = "google.golang.org/genproto" const = "cb27e3aa (May 26th, 2020)" switch { case "google/protobuf/field_mask.proto": = + "/protobuf/field_mask" case "google/protobuf/api.proto": = + "/protobuf/api" case "google/protobuf/type.proto": = + "/protobuf/ptype" case "google/protobuf/source_context.proto": = + "/protobuf/source_context" default: return } := strings.TrimSuffix(strings.TrimPrefix(, "google/protobuf/"), ".proto") = strings.Replace(, "_", "", -1) + "pb" // e.g., "field_mask" => "fieldmaskpb" := "google.golang.org/protobuf/types/known/" + panic(fmt.Sprintf(""+ "duplicate registration of %q\n"+ "\n"+ "The generated definition for this file has moved:\n"+ "\tfrom: %q\n"+ "\tto: %q\n"+ "A dependency on the %q module must\n"+ "be at version %v or higher.\n"+ "\n"+ "Upgrade the dependency by running:\n"+ "\tgo get -u %v\n", , , , , , )) } // FindDescriptorByName looks up a descriptor by the full name. // // This returns (nil, NotFound) if not found. func ( *Files) ( protoreflect.FullName) (protoreflect.Descriptor, error) { if == nil { return nil, NotFound } if == GlobalFiles { globalMutex.RLock() defer globalMutex.RUnlock() } := := nameSuffix("") for != "" { if , := .descsByName[]; { switch d := .(type) { case protoreflect.EnumDescriptor: if .FullName() == { return , nil } case protoreflect.EnumValueDescriptor: if .FullName() == { return , nil } case protoreflect.MessageDescriptor: if .FullName() == { return , nil } if := findDescriptorInMessage(, ); != nil && .FullName() == { return , nil } case protoreflect.ExtensionDescriptor: if .FullName() == { return , nil } case protoreflect.ServiceDescriptor: if .FullName() == { return , nil } if := .Methods().ByName(.Pop()); != nil && .FullName() == { return , nil } } return nil, NotFound } = .Parent() = nameSuffix([len()+len("."):]) } return nil, NotFound } func ( protoreflect.MessageDescriptor, nameSuffix) protoreflect.Descriptor { := .Pop() if == "" { if := .Enums().ByName(); != nil { return } for := .Enums().Len() - 1; >= 0; -- { if := .Enums().Get().Values().ByName(); != nil { return } } if := .Extensions().ByName(); != nil { return } if := .Fields().ByName(); != nil { return } if := .Oneofs().ByName(); != nil { return } } if := .Messages().ByName(); != nil { if == "" { return } return (, ) } return nil } type nameSuffix string func ( *nameSuffix) () ( protoreflect.Name) { if := strings.IndexByte(string(*), '.'); >= 0 { , * = protoreflect.Name((*)[:]), (*)[+1:] } else { , * = protoreflect.Name((*)), "" } return } // FindFileByPath looks up a file by the path. // // This returns (nil, NotFound) if not found. // This returns an error if multiple files have the same path. func ( *Files) ( string) (protoreflect.FileDescriptor, error) { if == nil { return nil, NotFound } if == GlobalFiles { globalMutex.RLock() defer globalMutex.RUnlock() } := .filesByPath[] switch len() { case 0: return nil, NotFound case 1: return [0], nil default: return nil, errors.New("multiple files named %q", ) } } // NumFiles reports the number of registered files, // including duplicate files with the same name. func ( *Files) () int { if == nil { return 0 } if == GlobalFiles { globalMutex.RLock() defer globalMutex.RUnlock() } return .numFiles } // RangeFiles iterates over all registered files while f returns true. // If multiple files have the same name, RangeFiles iterates over all of them. // The iteration order is undefined. func ( *Files) ( func(protoreflect.FileDescriptor) bool) { if == nil { return } if == GlobalFiles { globalMutex.RLock() defer globalMutex.RUnlock() } for , := range .filesByPath { for , := range { if !() { return } } } } // NumFilesByPackage reports the number of registered files in a proto package. func ( *Files) ( protoreflect.FullName) int { if == nil { return 0 } if == GlobalFiles { globalMutex.RLock() defer globalMutex.RUnlock() } , := .descsByName[].(*packageDescriptor) if ! { return 0 } return len(.files) } // RangeFilesByPackage iterates over all registered files in a given proto package // while f returns true. The iteration order is undefined. func ( *Files) ( protoreflect.FullName, func(protoreflect.FileDescriptor) bool) { if == nil { return } if == GlobalFiles { globalMutex.RLock() defer globalMutex.RUnlock() } , := .descsByName[].(*packageDescriptor) if ! { return } for , := range .files { if !() { return } } } // rangeTopLevelDescriptors iterates over all top-level descriptors in a file // which will be directly entered into the registry. func ( protoreflect.FileDescriptor, func(protoreflect.Descriptor)) { := .Enums() for := .Len() - 1; >= 0; -- { (.Get()) := .Get().Values() for := .Len() - 1; >= 0; -- { (.Get()) } } := .Messages() for := .Len() - 1; >= 0; -- { (.Get()) } := .Extensions() for := .Len() - 1; >= 0; -- { (.Get()) } := .Services() for := .Len() - 1; >= 0; -- { (.Get()) } } // MessageTypeResolver is an interface for looking up messages. // // A compliant implementation must deterministically return the same type // if no error is encountered. // // The Types type implements this interface. type MessageTypeResolver interface { // FindMessageByName looks up a message by its full name. // E.g., "google.protobuf.Any" // // This return (nil, NotFound) if not found. FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error) // FindMessageByURL looks up a message by a URL identifier. // See documentation on google.protobuf.Any.type_url for the URL format. // // This returns (nil, NotFound) if not found. FindMessageByURL(url string) (protoreflect.MessageType, error) } // ExtensionTypeResolver is an interface for looking up extensions. // // A compliant implementation must deterministically return the same type // if no error is encountered. // // The Types type implements this interface. type ExtensionTypeResolver interface { // FindExtensionByName looks up a extension field by the field's full name. // Note that this is the full name of the field as determined by // where the extension is declared and is unrelated to the full name of the // message being extended. // // This returns (nil, NotFound) if not found. FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) // FindExtensionByNumber looks up a extension field by the field number // within some parent message, identified by full name. // // This returns (nil, NotFound) if not found. FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) } var ( _ MessageTypeResolver = (*Types)(nil) _ ExtensionTypeResolver = (*Types)(nil) ) // Types is a registry for looking up or iterating over descriptor types. // The Find and Range methods are safe for concurrent use. type Types struct { typesByName typesByName extensionsByMessage extensionsByMessage numEnums int numMessages int numExtensions int } type ( typesByName map[protoreflect.FullName]interface{} extensionsByMessage map[protoreflect.FullName]extensionsByNumber extensionsByNumber map[protoreflect.FieldNumber]protoreflect.ExtensionType ) // RegisterMessage registers the provided message type. // // If a naming conflict occurs, the type is not registered and an error is returned. func ( *Types) ( protoreflect.MessageType) error { // Under rare circumstances getting the descriptor might recursively // examine the registry, so fetch it before locking. := .Descriptor() if == GlobalTypes { globalMutex.Lock() defer globalMutex.Unlock() } if := .register("message", , ); != nil { return } .numMessages++ return nil } // RegisterEnum registers the provided enum type. // // If a naming conflict occurs, the type is not registered and an error is returned. func ( *Types) ( protoreflect.EnumType) error { // Under rare circumstances getting the descriptor might recursively // examine the registry, so fetch it before locking. := .Descriptor() if == GlobalTypes { globalMutex.Lock() defer globalMutex.Unlock() } if := .register("enum", , ); != nil { return } .numEnums++ return nil } // RegisterExtension registers the provided extension type. // // If a naming conflict occurs, the type is not registered and an error is returned. func ( *Types) ( protoreflect.ExtensionType) error { // Under rare circumstances getting the descriptor might recursively // examine the registry, so fetch it before locking. // // A known case where this can happen: Fetching the TypeDescriptor for a // legacy ExtensionDesc can consult the global registry. := .TypeDescriptor() if == GlobalTypes { globalMutex.Lock() defer globalMutex.Unlock() } := .Number() := .ContainingMessage().FullName() if := .extensionsByMessage[][]; != nil { := errors.New("extension number %d is already registered on message %v", , ) = amendErrorWithCaller(, , ) if !( == GlobalTypes && ignoreConflict(, )) { return } } if := .register("extension", , ); != nil { return } if .extensionsByMessage == nil { .extensionsByMessage = make(extensionsByMessage) } if .extensionsByMessage[] == nil { .extensionsByMessage[] = make(extensionsByNumber) } .extensionsByMessage[][] = .numExtensions++ return nil } func ( *Types) ( string, protoreflect.Descriptor, interface{}) error { := .FullName() := .typesByName[] if != nil { := errors.New("%v %v is already registered", , ) = amendErrorWithCaller(, , ) if !( == GlobalTypes && ignoreConflict(, )) { return } } if .typesByName == nil { .typesByName = make(typesByName) } .typesByName[] = return nil } // FindEnumByName looks up an enum by its full name. // E.g., "google.protobuf.Field.Kind". // // This returns (nil, NotFound) if not found. func ( *Types) ( protoreflect.FullName) (protoreflect.EnumType, error) { if == nil { return nil, NotFound } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } if := .typesByName[]; != nil { if , := .(protoreflect.EnumType); != nil { return , nil } return nil, errors.New("found wrong type: got %v, want enum", typeName()) } return nil, NotFound } // FindMessageByName looks up a message by its full name, // e.g. "google.protobuf.Any". // // This returns (nil, NotFound) if not found. func ( *Types) ( protoreflect.FullName) (protoreflect.MessageType, error) { if == nil { return nil, NotFound } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } if := .typesByName[]; != nil { if , := .(protoreflect.MessageType); != nil { return , nil } return nil, errors.New("found wrong type: got %v, want message", typeName()) } return nil, NotFound } // FindMessageByURL looks up a message by a URL identifier. // See documentation on google.protobuf.Any.type_url for the URL format. // // This returns (nil, NotFound) if not found. func ( *Types) ( string) (protoreflect.MessageType, error) { // This function is similar to FindMessageByName but // truncates anything before and including '/' in the URL. if == nil { return nil, NotFound } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } := protoreflect.FullName() if := strings.LastIndexByte(, '/'); >= 0 { = [+len("/"):] } if := .typesByName[]; != nil { if , := .(protoreflect.MessageType); != nil { return , nil } return nil, errors.New("found wrong type: got %v, want message", typeName()) } return nil, NotFound } // FindExtensionByName looks up a extension field by the field's full name. // Note that this is the full name of the field as determined by // where the extension is declared and is unrelated to the full name of the // message being extended. // // This returns (nil, NotFound) if not found. func ( *Types) ( protoreflect.FullName) (protoreflect.ExtensionType, error) { if == nil { return nil, NotFound } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } if := .typesByName[]; != nil { if , := .(protoreflect.ExtensionType); != nil { return , nil } // MessageSet extensions are special in that the name of the extension // is the name of the message type used to extend the MessageSet. // This naming scheme is used by text and JSON serialization. // // This feature is protected by the ProtoLegacy flag since MessageSets // are a proto1 feature that is long deprecated. if flags.ProtoLegacy { if , := .(protoreflect.MessageType); { := .Append(messageset.ExtensionName) if := .typesByName[]; != nil { if , := .(protoreflect.ExtensionType); != nil { if messageset.IsMessageSetExtension(.TypeDescriptor()) { return , nil } } } } } return nil, errors.New("found wrong type: got %v, want extension", typeName()) } return nil, NotFound } // FindExtensionByNumber looks up a extension field by the field number // within some parent message, identified by full name. // // This returns (nil, NotFound) if not found. func ( *Types) ( protoreflect.FullName, protoreflect.FieldNumber) (protoreflect.ExtensionType, error) { if == nil { return nil, NotFound } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } if , := .extensionsByMessage[][]; { return , nil } return nil, NotFound } // NumEnums reports the number of registered enums. func ( *Types) () int { if == nil { return 0 } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } return .numEnums } // RangeEnums iterates over all registered enums while f returns true. // Iteration order is undefined. func ( *Types) ( func(protoreflect.EnumType) bool) { if == nil { return } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } for , := range .typesByName { if , := .(protoreflect.EnumType); { if !() { return } } } } // NumMessages reports the number of registered messages. func ( *Types) () int { if == nil { return 0 } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } return .numMessages } // RangeMessages iterates over all registered messages while f returns true. // Iteration order is undefined. func ( *Types) ( func(protoreflect.MessageType) bool) { if == nil { return } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } for , := range .typesByName { if , := .(protoreflect.MessageType); { if !() { return } } } } // NumExtensions reports the number of registered extensions. func ( *Types) () int { if == nil { return 0 } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } return .numExtensions } // RangeExtensions iterates over all registered extensions while f returns true. // Iteration order is undefined. func ( *Types) ( func(protoreflect.ExtensionType) bool) { if == nil { return } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } for , := range .typesByName { if , := .(protoreflect.ExtensionType); { if !() { return } } } } // NumExtensionsByMessage reports the number of registered extensions for // a given message type. func ( *Types) ( protoreflect.FullName) int { if == nil { return 0 } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } return len(.extensionsByMessage[]) } // RangeExtensionsByMessage iterates over all registered extensions filtered // by a given message type while f returns true. Iteration order is undefined. func ( *Types) ( protoreflect.FullName, func(protoreflect.ExtensionType) bool) { if == nil { return } if == GlobalTypes { globalMutex.RLock() defer globalMutex.RUnlock() } for , := range .extensionsByMessage[] { if !() { return } } } func ( interface{}) string { switch .(type) { case protoreflect.EnumType: return "enum" case protoreflect.MessageType: return "message" case protoreflect.ExtensionType: return "extension" default: return fmt.Sprintf("%T", ) } } func ( error, , interface{}) error { := goPackage() := goPackage() if == "" || == "" || == { return } return errors.New("%s\n\tpreviously from: %q\n\tcurrently from: %q", , , ) } func ( interface{}) string { switch d := .(type) { case protoreflect.EnumType: = .Descriptor() case protoreflect.MessageType: = .Descriptor() case protoreflect.ExtensionType: = .TypeDescriptor() } if , := .(protoreflect.Descriptor); { = .ParentFile() } if , := .(interface{ () string }); { return .() } return "" }