...

Source file src/google.golang.org/protobuf/internal/impl/legacy_enum.go

Documentation: google.golang.org/protobuf/internal/impl

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package impl
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  	"strings"
    11  	"sync"
    12  
    13  	"google.golang.org/protobuf/internal/filedesc"
    14  	"google.golang.org/protobuf/internal/strs"
    15  	"google.golang.org/protobuf/reflect/protoreflect"
    16  )
    17  
    18  // legacyEnumName returns the name of enums used in legacy code.
    19  // It is neither the protobuf full name nor the qualified Go name,
    20  // but rather an odd hybrid of both.
    21  func legacyEnumName(ed protoreflect.EnumDescriptor) string {
    22  	var protoPkg string
    23  	enumName := string(ed.FullName())
    24  	if fd := ed.ParentFile(); fd != nil {
    25  		protoPkg = string(fd.Package())
    26  		enumName = strings.TrimPrefix(enumName, protoPkg+".")
    27  	}
    28  	if protoPkg == "" {
    29  		return strs.GoCamelCase(enumName)
    30  	}
    31  	return protoPkg + "." + strs.GoCamelCase(enumName)
    32  }
    33  
    34  // legacyWrapEnum wraps v as a protoreflect.Enum,
    35  // where v must be a int32 kind and not implement the v2 API already.
    36  func legacyWrapEnum(v reflect.Value) protoreflect.Enum {
    37  	et := legacyLoadEnumType(v.Type())
    38  	return et.New(protoreflect.EnumNumber(v.Int()))
    39  }
    40  
    41  var legacyEnumTypeCache sync.Map // map[reflect.Type]protoreflect.EnumType
    42  
    43  // legacyLoadEnumType dynamically loads a protoreflect.EnumType for t,
    44  // where t must be an int32 kind and not implement the v2 API already.
    45  func legacyLoadEnumType(t reflect.Type) protoreflect.EnumType {
    46  	// Fast-path: check if a EnumType is cached for this concrete type.
    47  	if et, ok := legacyEnumTypeCache.Load(t); ok {
    48  		return et.(protoreflect.EnumType)
    49  	}
    50  
    51  	// Slow-path: derive enum descriptor and initialize EnumType.
    52  	var et protoreflect.EnumType
    53  	ed := LegacyLoadEnumDesc(t)
    54  	et = &legacyEnumType{
    55  		desc:   ed,
    56  		goType: t,
    57  	}
    58  	if et, ok := legacyEnumTypeCache.LoadOrStore(t, et); ok {
    59  		return et.(protoreflect.EnumType)
    60  	}
    61  	return et
    62  }
    63  
    64  type legacyEnumType struct {
    65  	desc   protoreflect.EnumDescriptor
    66  	goType reflect.Type
    67  	m      sync.Map // map[protoreflect.EnumNumber]proto.Enum
    68  }
    69  
    70  func (t *legacyEnumType) New(n protoreflect.EnumNumber) protoreflect.Enum {
    71  	if e, ok := t.m.Load(n); ok {
    72  		return e.(protoreflect.Enum)
    73  	}
    74  	e := &legacyEnumWrapper{num: n, pbTyp: t, goTyp: t.goType}
    75  	t.m.Store(n, e)
    76  	return e
    77  }
    78  func (t *legacyEnumType) Descriptor() protoreflect.EnumDescriptor {
    79  	return t.desc
    80  }
    81  
    82  type legacyEnumWrapper struct {
    83  	num   protoreflect.EnumNumber
    84  	pbTyp protoreflect.EnumType
    85  	goTyp reflect.Type
    86  }
    87  
    88  func (e *legacyEnumWrapper) Descriptor() protoreflect.EnumDescriptor {
    89  	return e.pbTyp.Descriptor()
    90  }
    91  func (e *legacyEnumWrapper) Type() protoreflect.EnumType {
    92  	return e.pbTyp
    93  }
    94  func (e *legacyEnumWrapper) Number() protoreflect.EnumNumber {
    95  	return e.num
    96  }
    97  func (e *legacyEnumWrapper) ProtoReflect() protoreflect.Enum {
    98  	return e
    99  }
   100  func (e *legacyEnumWrapper) protoUnwrap() interface{} {
   101  	v := reflect.New(e.goTyp).Elem()
   102  	v.SetInt(int64(e.num))
   103  	return v.Interface()
   104  }
   105  
   106  var (
   107  	_ protoreflect.Enum = (*legacyEnumWrapper)(nil)
   108  	_ unwrapper         = (*legacyEnumWrapper)(nil)
   109  )
   110  
   111  var legacyEnumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor
   112  
   113  // LegacyLoadEnumDesc returns an EnumDescriptor derived from the Go type,
   114  // which must be an int32 kind and not implement the v2 API already.
   115  //
   116  // This is exported for testing purposes.
   117  func LegacyLoadEnumDesc(t reflect.Type) protoreflect.EnumDescriptor {
   118  	// Fast-path: check if an EnumDescriptor is cached for this concrete type.
   119  	if ed, ok := legacyEnumDescCache.Load(t); ok {
   120  		return ed.(protoreflect.EnumDescriptor)
   121  	}
   122  
   123  	// Slow-path: initialize EnumDescriptor from the raw descriptor.
   124  	ev := reflect.Zero(t).Interface()
   125  	if _, ok := ev.(protoreflect.Enum); ok {
   126  		panic(fmt.Sprintf("%v already implements proto.Enum", t))
   127  	}
   128  	edV1, ok := ev.(enumV1)
   129  	if !ok {
   130  		return aberrantLoadEnumDesc(t)
   131  	}
   132  	b, idxs := edV1.EnumDescriptor()
   133  
   134  	var ed protoreflect.EnumDescriptor
   135  	if len(idxs) == 1 {
   136  		ed = legacyLoadFileDesc(b).Enums().Get(idxs[0])
   137  	} else {
   138  		md := legacyLoadFileDesc(b).Messages().Get(idxs[0])
   139  		for _, i := range idxs[1 : len(idxs)-1] {
   140  			md = md.Messages().Get(i)
   141  		}
   142  		ed = md.Enums().Get(idxs[len(idxs)-1])
   143  	}
   144  	if ed, ok := legacyEnumDescCache.LoadOrStore(t, ed); ok {
   145  		return ed.(protoreflect.EnumDescriptor)
   146  	}
   147  	return ed
   148  }
   149  
   150  var aberrantEnumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor
   151  
   152  // aberrantLoadEnumDesc returns an EnumDescriptor derived from the Go type,
   153  // which must not implement protoreflect.Enum or enumV1.
   154  //
   155  // If the type does not implement enumV1, then there is no reliable
   156  // way to derive the original protobuf type information.
   157  // We are unable to use the global enum registry since it is
   158  // unfortunately keyed by the protobuf full name, which we also do not know.
   159  // Thus, this produces some bogus enum descriptor based on the Go type name.
   160  func aberrantLoadEnumDesc(t reflect.Type) protoreflect.EnumDescriptor {
   161  	// Fast-path: check if an EnumDescriptor is cached for this concrete type.
   162  	if ed, ok := aberrantEnumDescCache.Load(t); ok {
   163  		return ed.(protoreflect.EnumDescriptor)
   164  	}
   165  
   166  	// Slow-path: construct a bogus, but unique EnumDescriptor.
   167  	ed := &filedesc.Enum{L2: new(filedesc.EnumL2)}
   168  	ed.L0.FullName = AberrantDeriveFullName(t) // e.g., github_com.user.repo.MyEnum
   169  	ed.L0.ParentFile = filedesc.SurrogateProto3
   170  	ed.L2.Values.List = append(ed.L2.Values.List, filedesc.EnumValue{})
   171  
   172  	// TODO: Use the presence of a UnmarshalJSON method to determine proto2?
   173  
   174  	vd := &ed.L2.Values.List[0]
   175  	vd.L0.FullName = ed.L0.FullName + "_UNKNOWN" // e.g., github_com.user.repo.MyEnum_UNKNOWN
   176  	vd.L0.ParentFile = ed.L0.ParentFile
   177  	vd.L0.Parent = ed
   178  
   179  	// TODO: We could use the String method to obtain some enum value names by
   180  	// starting at 0 and print the enum until it produces invalid identifiers.
   181  	// An exhaustive query is clearly impractical, but can be best-effort.
   182  
   183  	if ed, ok := aberrantEnumDescCache.LoadOrStore(t, ed); ok {
   184  		return ed.(protoreflect.EnumDescriptor)
   185  	}
   186  	return ed
   187  }
   188  
   189  // AberrantDeriveFullName derives a fully qualified protobuf name for the given Go type
   190  // The provided name is not guaranteed to be stable nor universally unique.
   191  // It should be sufficiently unique within a program.
   192  //
   193  // This is exported for testing purposes.
   194  func AberrantDeriveFullName(t reflect.Type) protoreflect.FullName {
   195  	sanitize := func(r rune) rune {
   196  		switch {
   197  		case r == '/':
   198  			return '.'
   199  		case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', '0' <= r && r <= '9':
   200  			return r
   201  		default:
   202  			return '_'
   203  		}
   204  	}
   205  	prefix := strings.Map(sanitize, t.PkgPath())
   206  	suffix := strings.Map(sanitize, t.Name())
   207  	if suffix == "" {
   208  		suffix = fmt.Sprintf("UnknownX%X", reflect.ValueOf(t).Pointer())
   209  	}
   210  
   211  	ss := append(strings.Split(prefix, "."), suffix)
   212  	for i, s := range ss {
   213  		if s == "" || ('0' <= s[0] && s[0] <= '9') {
   214  			ss[i] = "x" + s
   215  		}
   216  	}
   217  	return protoreflect.FullName(strings.Join(ss, "."))
   218  }
   219  

View as plain text