// 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 impl import ( "reflect" "sync" ptag "google.golang.org/protobuf/internal/encoding/tag" "google.golang.org/protobuf/internal/filedesc" pref "google.golang.org/protobuf/reflect/protoreflect" preg "google.golang.org/protobuf/reflect/protoregistry" piface "google.golang.org/protobuf/runtime/protoiface" ) // legacyExtensionDescKey is a comparable version of protoiface.ExtensionDescV1 // suitable for use as a key in a map. type legacyExtensionDescKey struct { typeV2 pref.ExtensionType extendedType reflect.Type extensionType reflect.Type field int32 name string tag string filename string } func legacyExtensionDescKeyOf(d *piface.ExtensionDescV1) legacyExtensionDescKey { return legacyExtensionDescKey{ d.Type, reflect.TypeOf(d.ExtendedType), reflect.TypeOf(d.ExtensionType), d.Field, d.Name, d.Tag, d.Filename, } } var ( legacyExtensionTypeCache sync.Map // map[legacyExtensionDescKey]protoreflect.ExtensionType legacyExtensionDescCache sync.Map // map[protoreflect.ExtensionType]*protoiface.ExtensionDescV1 ) // legacyExtensionDescFromType converts a v2 protoreflect.ExtensionType to a // protoiface.ExtensionDescV1. The returned ExtensionDesc must not be mutated. func legacyExtensionDescFromType(xt pref.ExtensionType) *piface.ExtensionDescV1 { // Fast-path: check whether an extension desc is already nested within. if xt, ok := xt.(interface { ProtoLegacyExtensionDesc() *piface.ExtensionDescV1 }); ok { if d := xt.ProtoLegacyExtensionDesc(); d != nil { return d } } // Fast-path: check the cache for whether this ExtensionType has already // been converted to a legacy descriptor. if d, ok := legacyExtensionDescCache.Load(xt); ok { return d.(*piface.ExtensionDescV1) } // Determine the parent type if possible. xd := xt.Descriptor() var parent piface.MessageV1 messageName := xd.ContainingMessage().FullName() if mt, _ := preg.GlobalTypes.FindMessageByName(messageName); mt != nil { // Create a new parent message and unwrap it if possible. mv := mt.New().Interface() t := reflect.TypeOf(mv) if mv, ok := mv.(Unwrapper); ok { t = reflect.TypeOf(mv.ProtoUnwrap()) } // Check whether the message implements the legacy v1 Message interface. mz := reflect.Zero(t).Interface() if mz, ok := mz.(piface.MessageV1); ok { parent = mz } } // Determine the v1 extension type, which is unfortunately not the same as // the v2 ExtensionType.GoType. extType := xt.GoType() switch extType.Kind() { case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String: extType = reflect.PtrTo(extType) // T -> *T for singular scalar fields case reflect.Ptr: if extType.Elem().Kind() == reflect.Slice { extType = extType.Elem() // *[]T -> []T for repeated fields } } // Reconstruct the legacy enum full name, which is an odd mixture of the // proto package name with the Go type name. var enumName string if xd.Kind() == pref.EnumKind { // Derive Go type name. t := extType if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice { t = t.Elem() } enumName = t.Name() // Derive the proto package name. // For legacy enums, obtain the proto package from the raw descriptor. var protoPkg string if fd := xd.Enum().ParentFile(); fd != nil { protoPkg = string(fd.Package()) } if ed, ok := reflect.Zero(t).Interface().(enumV1); ok && protoPkg == "" { b, _ := ed.EnumDescriptor() protoPkg = string(legacyLoadFileDesc(b).Package()) } if protoPkg != "" { enumName = protoPkg + "." + enumName } } // Derive the proto file that the extension was declared within. var filename string if fd := xd.ParentFile(); fd != nil { filename = fd.Path() } // Construct and return a ExtensionDescV1. d := &piface.ExtensionDescV1{ Type: xt, ExtendedType: parent, ExtensionType: reflect.Zero(extType).Interface(), Field: int32(xd.Number()), Name: string(xd.FullName()), Tag: ptag.Marshal(xd, enumName), Filename: filename, } if d, ok := legacyExtensionDescCache.LoadOrStore(xt, d); ok { return d.(*piface.ExtensionDescV1) } return d } // legacyExtensionTypeFromDesc converts a protoiface.ExtensionDescV1 to a // v2 protoreflect.ExtensionType. The returned descriptor type takes ownership // of the input extension desc. The input must not be mutated so long as the // returned type is still in use. func legacyExtensionTypeFromDesc(d *piface.ExtensionDescV1) pref.ExtensionType { // Fast-path: check whether an extension type is already nested within. if d.Type != nil { return d.Type } // Fast-path: check the cache for whether this ExtensionType has already // been converted from a legacy descriptor. dk := legacyExtensionDescKeyOf(d) if t, ok := legacyExtensionTypeCache.Load(dk); ok { return t.(pref.ExtensionType) } // Resolve enum or message dependencies. var ed pref.EnumDescriptor var md pref.MessageDescriptor t := reflect.TypeOf(d.ExtensionType) isOptional := t.Kind() == reflect.Ptr && t.Elem().Kind() != reflect.Struct isRepeated := t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8 if isOptional || isRepeated { t = t.Elem() } switch v := reflect.Zero(t).Interface().(type) { case pref.Enum: ed = v.Descriptor() case enumV1: ed = LegacyLoadEnumDesc(t) case pref.ProtoMessage: md = v.ProtoReflect().Descriptor() case messageV1: md = LegacyLoadMessageDesc(t) } // Derive basic field information from the struct tag. var evs pref.EnumValueDescriptors if ed != nil { evs = ed.Values() } fd := ptag.Unmarshal(d.Tag, t, evs).(*filedesc.Field) // Construct a v2 ExtensionType. xd := &filedesc.Extension{L2: new(filedesc.ExtensionL2)} xd.L0.ParentFile = filedesc.SurrogateProto2 xd.L0.FullName = pref.FullName(d.Name) xd.L1.Number = pref.FieldNumber(d.Field) xd.L2.Cardinality = fd.L1.Cardinality xd.L1.Kind = fd.L1.Kind xd.L2.IsPacked = fd.L1.IsPacked xd.L2.Default = fd.L1.Default xd.L1.Extendee = Export{}.MessageDescriptorOf(d.ExtendedType) xd.L2.Enum = ed xd.L2.Message = md tt := reflect.TypeOf(d.ExtensionType) if isOptional { tt = tt.Elem() } else if isRepeated { tt = reflect.PtrTo(tt) } xt := LegacyExtensionTypeOf(xd, tt) // Cache the conversion for both directions. legacyExtensionDescCache.LoadOrStore(xt, d) if xt, ok := legacyExtensionTypeCache.LoadOrStore(dk, xt); ok { return xt.(pref.ExtensionType) } return xt } // LegacyExtensionTypeOf returns a protoreflect.ExtensionType where the // element type of the field is t. // // This is exported for testing purposes. func LegacyExtensionTypeOf(xd pref.ExtensionDescriptor, t reflect.Type) pref.ExtensionType { xt := &legacyExtensionType{ typ: t, conv: NewConverter(t, xd), } xt.desc = &extDesc{xd, xt} return xt } type legacyExtensionType struct { desc pref.ExtensionTypeDescriptor typ reflect.Type conv Converter } func (x *legacyExtensionType) GoType() reflect.Type { return x.typ } func (x *legacyExtensionType) New() pref.Value { return x.conv.New() } func (x *legacyExtensionType) Zero() pref.Value { return x.conv.Zero() } func (x *legacyExtensionType) ValueOf(v interface{}) pref.Value { return x.conv.PBValueOf(reflect.ValueOf(v)) } func (x *legacyExtensionType) InterfaceOf(v pref.Value) interface{} { return x.conv.GoValueOf(v).Interface() } func (x *legacyExtensionType) Descriptor() pref.ExtensionTypeDescriptor { return x.desc } type extDesc struct { pref.ExtensionDescriptor xt *legacyExtensionType } func (t *extDesc) Type() pref.ExtensionType { return t.xt } func (t *extDesc) Descriptor() pref.ExtensionDescriptor { return t.ExtensionDescriptor }