blob: 5ac6a15a406e6c372e132d05c4d3052f7043c8db [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package descutil
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/golang/protobuf/proto"
pb "github.com/golang/protobuf/protoc-gen-go/descriptor"
)
// splitFullName splits package name and service/type name.
func splitFullName(fullName string) (pkg string, name string) {
lastDot := strings.LastIndex(fullName, ".")
if lastDot < 0 {
return "", fullName
}
return fullName[:lastDot], fullName[lastDot+1:]
}
////////////////////////////////////////////////////////////////////////////////
// FileDescriptorSet
// FindFile searches for a FileDescriptorProto by name.
func FindFile(s *pb.FileDescriptorSet, name string) int {
for i, x := range s.GetFile() {
if x.GetName() == name {
return i
}
}
return -1
}
// Resolve searches for an object by full name. obj can be one of
// *ServiceDescriptorProto,
// *MethodDescriptorProto,
// *DescriptorProto,
// *FieldDescriptorProto,
// *DescriptorProto,
// *EnumDescriptorProto,
// *EnumValueDescriptorProto or
// nil
//
// For path, see comment in SourceCodeInfo message.
func Resolve(s *pb.FileDescriptorSet, fullName string) (file *pb.FileDescriptorProto, obj interface{}, path []int) {
if fullName == "" {
return nil, nil, nil
}
pkg, name := splitFullName(fullName)
// Check top-level objects.
for _, f := range s.GetFile() {
if f.GetPackage() == pkg {
if i := FindServiceForFile(f, name); i != -1 {
return f, f.Service[i], []int{FileDescriptorProtoServiceTag, i}
}
if i := FindMessageForFile(f, name); i != -1 {
return f, f.MessageType[i], []int{FileDescriptorProtoMessageTag, i}
}
if i := FindEnumForFile(f, name); i != -1 {
return f, f.EnumType[i], []int{FileDescriptorProtoEnumTag, i}
}
}
}
// Recurse.
var parent interface{}
file, parent, path = Resolve(s, pkg)
switch parent := parent.(type) {
case *pb.ServiceDescriptorProto:
if i := FindMethodForService(parent, name); i != -1 {
return file, parent.Method[i], append(path, ServiceDescriptorProtoMethodTag, i)
}
case *pb.DescriptorProto:
if i := FindMessage(parent, name); i != -1 {
return file, parent.NestedType[i], append(path, DescriptorProtoNestedTypeTag, i)
}
if i := FindEnum(parent, name); i != -1 {
return file, parent.EnumType[i], append(path, DescriptorProtoEnumTypeTag, i)
}
if i := FindField(parent, name); i != -1 {
return file, parent.Field[i], append(path, DescriptorProtoFieldTag, i)
}
if i := FindOneOf(parent, name); i != -1 {
return file, parent.OneofDecl[i], append(path, DescriptorProtoOneOfTag, i)
}
case *pb.EnumDescriptorProto:
if i := FindEnumValue(parent, name); i != -1 {
return file, parent.Value[i], append(path, EnumDescriptorProtoValueTag, i)
}
}
return nil, nil, nil
}
// FindService searches for a service by full name.
func FindService(s *pb.FileDescriptorSet, fullName string) (file *pb.FileDescriptorProto, serviceIndex int) {
pkg, name := splitFullName(fullName)
for _, f := range s.GetFile() {
if f.GetPackage() == pkg {
if i := FindServiceForFile(f, name); i != -1 {
return f, i
}
}
}
return nil, -1
}
////////////////////////////////////////////////////////////////////////////////
// FileDescriptorProto
// FindServiceForFile searches for a FileDescriptorProto by name.
func FindServiceForFile(f *pb.FileDescriptorProto, name string) int {
for i, x := range f.GetService() {
if x.GetName() == name {
return i
}
}
return -1
}
// FindMessageForFile searches for a DescriptorProto by name.
func FindMessageForFile(f *pb.FileDescriptorProto, name string) int {
for i, x := range f.GetMessageType() {
if x.GetName() == name {
return i
}
}
return -1
}
// FindEnumForFile searches for an EnumDescriptorProto by name.
func FindEnumForFile(f *pb.FileDescriptorProto, name string) int {
for i, x := range f.GetEnumType() {
if x.GetName() == name {
return i
}
}
return -1
}
////////////////////////////////////////////////////////////////////////////////
// ServiceDescriptorProto
// FindMethodForService searches for a MethodDescriptorProto by name.
func FindMethodForService(s *pb.ServiceDescriptorProto, name string) int {
for i, x := range s.GetMethod() {
if x.GetName() == name {
return i
}
}
return -1
}
////////////////////////////////////////////////////////////////////////////////
// DescriptorProto (a message)
// FindField searches for a FieldDescriptorProto by name.
func FindField(d *pb.DescriptorProto, name string) int {
for i, x := range d.GetField() {
if x.GetName() == name {
return i
}
}
return -1
}
// FindMessage searches for a nested DescriptorProto by name.
func FindMessage(d *pb.DescriptorProto, name string) int {
for i, x := range d.GetNestedType() {
if x.GetName() == name {
return i
}
}
return -1
}
// FindEnum searches for a nested EnumDescriptorProto by name.
func FindEnum(d *pb.DescriptorProto, name string) int {
for i, x := range d.GetEnumType() {
if x.GetName() == name {
return i
}
}
return -1
}
// FindOneOf searches for a nested OneofDescriptorProto by name.
func FindOneOf(d *pb.DescriptorProto, name string) int {
for i, x := range d.GetOneofDecl() {
if x.GetName() == name {
return i
}
}
return -1
}
////////////////////////////////////////////////////////////////////////////////
// FieldDescriptorProto
// Repeated returns true if the field is repeated.
func Repeated(f *pb.FieldDescriptorProto) bool {
return f.GetLabel() == pb.FieldDescriptorProto_LABEL_REPEATED
}
// Required returns true if the field is required.
func Required(f *pb.FieldDescriptorProto) bool {
return f.GetLabel() == pb.FieldDescriptorProto_LABEL_REQUIRED
}
////////////////////////////////////////////////////////////////////////////////
// EnumDescriptorProto
// FindEnumValue searches for an EnumValueDescriptorProto by name.
func FindEnumValue(e *pb.EnumDescriptorProto, name string) int {
for i, x := range e.GetValue() {
if x.GetName() == name {
return i
}
}
return -1
}
// FindValueByNumber searches for an EnumValueDescriptorProto by number.
func FindValueByNumber(e *pb.EnumDescriptorProto, number int32) int {
for i, x := range e.GetValue() {
if x.GetNumber() == number {
return i
}
}
return -1
}
////////////////////////////////////////////////////////////////////////////////
// SourceCodeInfo
// At returns a descriptor proto or its field value at the given path.
// The path has same semantics as
// descriptor.SourceCodeInfo_Location.Path. See its comment for explanation.
//
// For example, given a FileDescriptorProto and path [4, 2],
// At will return the 2nd top-level message DescriptorProto
// because 4 is FileDescriptorProto.MessageType field tag.
//
// Does not supported uninterpreted options, returns (nil, nil).
func At(descProto proto.Message, path []int32) (interface{}, error) {
cur := reflect.ValueOf(descProto)
pathStr := func() string {
s := make([]string, len(path))
for i, x := range path {
s[i] = strconv.FormatInt(int64(x), 10)
}
return fmt.Sprintf("[%s]", strings.Join(s, ","))
}
for i := 0; i < len(path); i++ {
if cur.Kind() == reflect.Slice {
index := int(path[i])
if index < 0 || index >= cur.Len() {
return nil, fmt.Errorf("element #%d of path %s is an index and it is out of bounds", i, pathStr())
}
cur = cur.Index(int(path[i]))
} else if msg, ok := cur.Interface().(proto.Message); ok {
tag := path[i]
// The tag->field index mapping could be precomputed.
var prop *proto.Properties
for _, p := range proto.GetProperties(cur.Type().Elem()).Prop {
if p.Tag == int(tag) {
prop = p
break
}
}
if prop == nil {
// Is this an extension?
if _, err := proto.GetExtension(msg, &proto.ExtensionDesc{Field: tag}); err == nil {
// Yes. Skip it.
return nil, nil
}
return nil, fmt.Errorf("%T has no tag %d", cur.Interface(), tag)
}
cur = cur.Elem().FieldByName(prop.Name)
if cur.Kind() != reflect.Ptr && cur.Kind() != reflect.Slice {
panic("a field value is not a slice or pointer")
}
} else {
return nil, fmt.Errorf("expected end at index %d in path %s", i, pathStr())
}
}
return cur.Interface(), nil
}
// IndexSourceCodeInfo returns a map that maps a pointer to the associated
// source code info, where the pointer points to a descriptor proto or its
// field, e.g. &myFieldDescriptorProto.Name.
//
// IndexSourceCodeInfo can be used to retrieve comments.
//
// Does not support whole-slice locations.
func IndexSourceCodeInfo(f *pb.FileDescriptorProto) (map[interface{}]*pb.SourceCodeInfo_Location, error) {
if f.SourceCodeInfo == nil {
return nil, nil
}
ret := make(map[interface{}]*pb.SourceCodeInfo_Location, len(f.SourceCodeInfo.Location))
for _, loc := range f.SourceCodeInfo.Location {
ptr, err := At(f, loc.Path)
switch v := reflect.ValueOf(ptr); {
case err != nil:
return nil, err
case !v.IsValid():
case v.Kind() == reflect.Slice:
// A slice cannot be used as a map key.
// Whole slice declarations are not supported.
default:
ret[ptr] = loc
}
}
return ret, nil
}