internal/impl: support aberrant enums and messages

Aberrant messages are hand-crafted messages that happen to work because
they use the same struct tags that generated code emits.
This happens to work in v1, but is unspecified behavior and entirely outside
the compatibility promise.

Support for this was added early on in the history of the v2 implementation,
but entirely untested. It was removed in CL/182360 to reduce the
technical debt of the legacy implementation. Unfortunately, sufficient number
of targets do rely on this aberrant support, so it is being added back.

The logic being added is essentially the same thing as the previous logic,
but ported to use internal/filedesc instead of the now deleted
internal/prototype package.

Change-Id: Ib5cab3e90480825b9615db358044ce05a14b05bd
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/184517
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Joe Tsai 2019-07-01 13:45:52 -07:00 committed by Joe Tsai
parent 4ae30bbb21
commit 851185dae3
6 changed files with 628 additions and 3 deletions

View File

@ -528,6 +528,9 @@ type defaultValue struct {
func (dv *defaultValue) get(fd pref.FieldDescriptor) pref.Value {
// Return the zero value as the default if unpopulated.
if !dv.has {
if fd.Cardinality() == pref.Repeated {
return pref.Value{}
}
switch fd.Kind() {
case pref.BoolKind:
return pref.ValueOf(false)

View File

@ -347,7 +347,7 @@ func testFileAccessors(t *testing.T, fd pref.FileDescriptor) {
"IsPacked": true,
"IsList": true,
"IsMap": false,
"Default": int32(0),
"Default": nil,
},
"ByNumber:6": M{
"Cardinality": pref.Required,

View File

@ -64,6 +64,21 @@ func (e PlaceholderEnum) ReservedRanges() pref.EnumRanges { return emptyEnum
func (e PlaceholderEnum) ProtoType(pref.EnumDescriptor) { return }
func (e PlaceholderEnum) ProtoInternal(pragma.DoNotImplement) { return }
// PlaceholderEnumValue is a placeholder, representing only the full name.
type PlaceholderEnumValue pref.FullName
func (e PlaceholderEnumValue) ParentFile() pref.FileDescriptor { return nil }
func (e PlaceholderEnumValue) Parent() pref.Descriptor { return nil }
func (e PlaceholderEnumValue) Index() int { return 0 }
func (e PlaceholderEnumValue) Syntax() pref.Syntax { return 0 }
func (e PlaceholderEnumValue) Name() pref.Name { return pref.FullName(e).Name() }
func (e PlaceholderEnumValue) FullName() pref.FullName { return pref.FullName(e) }
func (e PlaceholderEnumValue) IsPlaceholder() bool { return true }
func (e PlaceholderEnumValue) Options() pref.ProtoMessage { return descopts.EnumValue }
func (e PlaceholderEnumValue) Number() pref.EnumNumber { return 0 }
func (e PlaceholderEnumValue) ProtoType(pref.EnumValueDescriptor) { return }
func (e PlaceholderEnumValue) ProtoInternal(pragma.DoNotImplement) { return }
// PlaceholderMessage is a placeholder, representing only the full name.
type PlaceholderMessage pref.FullName

View File

@ -0,0 +1,292 @@
// Copyright 2019 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_test
import (
"io"
"reflect"
"testing"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/internal/impl"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/runtime/protoiface"
"google.golang.org/protobuf/types/descriptorpb"
)
type AberrantMessage struct {
OptionalBool *bool `protobuf:"varint,1,opt,name=opt_bool,def=1"`
OptionalInt32 *int32 `protobuf:"varint,2,opt,name=opt_int32,def=-12345"`
OptionalSint32 *int32 `protobuf:"zigzag32,3,opt,name=opt_sint32,def=-3200"`
OptionalUint32 *uint32 `protobuf:"varint,4,opt,name=opt_uint32,def=3200"`
OptionalInt64 *int64 `protobuf:"varint,5,opt,name=opt_int64,def=-123456789"`
OptionalSint64 *int64 `protobuf:"zigzag64,6,opt,name=opt_sint64,def=-6400"`
OptionalUint64 *uint64 `protobuf:"varint,7,opt,name=opt_uint64,def=6400"`
OptionalFixed32 *uint32 `protobuf:"fixed32,8,opt,name=opt_fixed32,def=320000"`
OptionalSfixed32 *int32 `protobuf:"fixed32,9,opt,name=opt_sfixed32,def=-320000"`
OptionalFloat *float32 `protobuf:"fixed32,10,opt,name=opt_float,def=3.14159"`
OptionalFixed64 *uint64 `protobuf:"fixed64,11,opt,name=opt_fixed64,def=640000"`
OptionalSfixed64 *int64 `protobuf:"fixed64,12,opt,name=opt_sfixed64,def=-640000"`
OptionalDouble *float64 `protobuf:"fixed64,13,opt,name=opt_double,def=3.14159265359"`
OptionalString *string `protobuf:"bytes,14,opt,name=opt_string,def=hello, \"world!\"\n"`
OptionalBytes []byte `protobuf:"bytes,15,opt,name=opt_bytes,def=dead\\336\\255\\276\\357beef"`
OptionalEnum *AberrantEnum `protobuf:"varint,16,opt,name=opt_enum,enum=google.golang.org.example.AberrantEnum,def=0"`
OptionalMessage *AberrantMessage `protobuf:"bytes,17,opt,name=opt_message"`
RepeatedBool []bool `protobuf:"varint,18,rep,packed,name=rep_bool"`
RepeatedInt32 []int32 `protobuf:"varint,19,rep,packed,name=rep_int32"`
RepeatedSint32 []int32 `protobuf:"zigzag32,20,rep,packed,name=rep_sint32"`
RepeatedUint32 []uint32 `protobuf:"varint,21,rep,packed,name=rep_uint32"`
RepeatedInt64 []int64 `protobuf:"varint,22,rep,packed,name=rep_int64"`
RepeatedSint64 []int64 `protobuf:"zigzag64,23,rep,packed,name=rep_sint64"`
RepeatedUint64 []uint64 `protobuf:"varint,24,rep,packed,name=rep_uint64"`
RepeatedFixed32 []uint32 `protobuf:"fixed32,25,rep,packed,name=rep_fixed32"`
RepeatedSfixed32 []int32 `protobuf:"fixed32,26,rep,packed,name=rep_sfixed32"`
RepeatedFloat []float32 `protobuf:"fixed32,27,rep,packed,name=rep_float"`
RepeatedFixed64 []uint64 `protobuf:"fixed64,28,rep,packed,name=rep_fixed64"`
RepeatedSfixed64 []int64 `protobuf:"fixed64,29,rep,packed,name=rep_sfixed64"`
RepeatedDouble []float64 `protobuf:"fixed64,30,rep,packed,name=rep_double"`
RepeatedString []string `protobuf:"bytes,31,rep,name=rep_string"`
RepeatedBytes [][]byte `protobuf:"bytes,32,rep,name=rep_bytes"`
RepeatedEnum []AberrantEnum `protobuf:"varint,33,rep,name=rep_enum,enum=google.golang.org.example.AberrantEnum"`
RepeatedMessage []*AberrantMessage `protobuf:"bytes,34,rep,name=rep_message"`
MapStringBool map[string]bool `protobuf:"bytes,35,rep,name=map_string_bool" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
MapStringInt32 map[string]int32 `protobuf:"bytes,36,rep,name=map_string_int32" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
MapStringSint32 map[string]int32 `protobuf:"bytes,37,rep,name=map_string_sint32" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"zigzag32,2,opt,name=value"`
MapStringUint32 map[string]uint32 `protobuf:"bytes,38,rep,name=map_string_uint32" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
MapStringInt64 map[string]int64 `protobuf:"bytes,39,rep,name=map_string_int64" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
MapStringSint64 map[string]int64 `protobuf:"bytes,40,rep,name=map_string_sint64" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"zigzag64,2,opt,name=value"`
MapStringUint64 map[string]uint64 `protobuf:"bytes,41,rep,name=map_string_uint64" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
MapStringFixed32 map[string]uint32 `protobuf:"bytes,42,rep,name=map_string_fixed32" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"`
MapStringSfixed32 map[string]int32 `protobuf:"bytes,43,rep,name=map_string_sfixed32" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"`
MapStringFloat map[string]float32 `protobuf:"bytes,44,rep,name=map_string_float" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"`
MapStringFixed64 map[string]uint64 `protobuf:"bytes,45,rep,name=map_string_fixed64" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed64,2,opt,name=value"`
MapStringSfixed64 map[string]int64 `protobuf:"bytes,46,rep,name=map_string_sfixed64" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed64,2,opt,name=value"`
MapStringDouble map[string]float64 `protobuf:"bytes,47,rep,name=map_string_double" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"fixed64,2,opt,name=value"`
MapStringString map[string]string `protobuf:"bytes,48,rep,name=map_string_string" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
MapStringBytes map[string][]byte `protobuf:"bytes,49,rep,name=map_string_bytes" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
MapStringEnum map[string]AberrantEnum `protobuf:"bytes,50,rep,name=map_string_enum" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value,enum=google.golang.org.example.AberrantEnum"`
MapStringMessage map[string]*AberrantMessage `protobuf:"bytes,51,rep,name=map_string_message" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
OneofUnion isOneofUnion `protobuf_oneof:"oneof_union"`
Ignored io.Reader
}
func (m *AberrantMessage) XXX_MessageName() string {
return "google.golang.org.example.AberrantMessage"
}
func (m *AberrantMessage) ExtensionRangeArray() []protoiface.ExtensionRangeV1 {
return []protoiface.ExtensionRangeV1{{Start: 10, End: 100}}
}
func (m *AberrantMessage) XXX_OneofWrappers() []interface{} {
return []interface{}{
(*OneofBool)(nil),
(*OneofInt32)(nil),
(*OneofSint32)(nil),
(*OneofUint32)(nil),
(*OneofInt64)(nil),
(*OneofSint64)(nil),
(*OneofUint64)(nil),
(*OneofFixed32)(nil),
(*OneofSfixed32)(nil),
(*OneofFloat)(nil),
(*OneofFixed64)(nil),
(*OneofSfixed64)(nil),
(*OneofDouble)(nil),
(*OneofString)(nil),
(*OneofBytes)(nil),
(*OneofEnum)(nil),
(*OneofMessage)(nil),
}
}
type isOneofUnion interface{ isOneofUnion() }
type OneofBool struct {
OneofBool bool `protobuf:"varint,52,opt,name=oneof_bool,oneof,def=1"`
}
type OneofInt32 struct {
OneofInt32 int32 `protobuf:"varint,53,opt,name=oneof_int32,oneof,def=-12345"`
}
type OneofSint32 struct {
OneofSint32 int32 `protobuf:"zigzag32,54,opt,name=oneof_sint32,oneof,def=-3200"`
}
type OneofUint32 struct {
OneofUint32 uint32 `protobuf:"varint,55,opt,name=oneof_uint32,oneof,def=3200"`
}
type OneofInt64 struct {
OneofInt64 int64 `protobuf:"varint,56,opt,name=oneof_int64,oneof,def=-123456789"`
}
type OneofSint64 struct {
OneofSint64 int64 `protobuf:"zigzag64,57,opt,name=oneof_sint64,oneof,def=-6400"`
}
type OneofUint64 struct {
OneofUint64 uint64 `protobuf:"varint,58,opt,name=oneof_uint64,oneof,def=6400"`
}
type OneofFixed32 struct {
OneofFixed32 uint32 `protobuf:"fixed32,59,opt,name=oneof_fixed32,oneof,def=320000"`
}
type OneofSfixed32 struct {
OneofSfixed32 int32 `protobuf:"fixed32,60,opt,name=oneof_sfixed32,oneof,def=-320000"`
}
type OneofFloat struct {
OneofFloat float32 `protobuf:"fixed32,61,opt,name=oneof_float,oneof,def=3.14159"`
}
type OneofFixed64 struct {
OneofFixed64 uint64 `protobuf:"fixed64,62,opt,name=oneof_fixed64,oneof,def=640000"`
}
type OneofSfixed64 struct {
OneofSfixed64 int64 `protobuf:"fixed64,63,opt,name=oneof_sfixed64,oneof,def=-640000"`
}
type OneofDouble struct {
OneofDouble float64 `protobuf:"fixed64,64,opt,name=oneof_double,oneof,def=3.14159265359"`
}
type OneofString struct {
OneofString string `protobuf:"bytes,65,opt,name=oneof_string,oneof,def=hello, \"world!\"\n"`
}
type OneofBytes struct {
OneofBytes []byte `protobuf:"bytes,66,opt,name=oneof_bytes,oneof,def=dead\\336\\255\\276\\357beef"`
}
type OneofEnum struct {
OneofEnum AberrantEnum `protobuf:"varint,67,opt,name=oneof_enum,enum=google.golang.org.example.AberrantEnum,oneof,def=0"`
}
type OneofMessage struct {
OneofMessage *AberrantMessage `protobuf:"bytes,68,opt,name=oneof_message,oneof"`
}
func (OneofBool) isOneofUnion() {}
func (OneofInt32) isOneofUnion() {}
func (OneofSint32) isOneofUnion() {}
func (OneofUint32) isOneofUnion() {}
func (OneofInt64) isOneofUnion() {}
func (OneofSint64) isOneofUnion() {}
func (OneofUint64) isOneofUnion() {}
func (OneofFixed32) isOneofUnion() {}
func (OneofSfixed32) isOneofUnion() {}
func (OneofFloat) isOneofUnion() {}
func (OneofFixed64) isOneofUnion() {}
func (OneofSfixed64) isOneofUnion() {}
func (OneofDouble) isOneofUnion() {}
func (OneofString) isOneofUnion() {}
func (OneofBytes) isOneofUnion() {}
func (OneofEnum) isOneofUnion() {}
func (OneofMessage) isOneofUnion() {}
type AberrantEnum int32
func TestAberrant(t *testing.T) {
want := new(descriptorpb.DescriptorProto)
if err := prototext.Unmarshal([]byte(`
name: "AberrantMessage"
field: [
{name:"opt_bool" number:1 label:LABEL_OPTIONAL type:TYPE_BOOL default_value:"true"},
{name:"opt_int32" number:2 label:LABEL_OPTIONAL type:TYPE_INT32 default_value:"-12345"},
{name:"opt_sint32" number:3 label:LABEL_OPTIONAL type:TYPE_SINT32 default_value:"-3200"},
{name:"opt_uint32" number:4 label:LABEL_OPTIONAL type:TYPE_UINT32 default_value:"3200"},
{name:"opt_int64" number:5 label:LABEL_OPTIONAL type:TYPE_INT64 default_value:"-123456789"},
{name:"opt_sint64" number:6 label:LABEL_OPTIONAL type:TYPE_SINT64 default_value:"-6400"},
{name:"opt_uint64" number:7 label:LABEL_OPTIONAL type:TYPE_UINT64 default_value:"6400"},
{name:"opt_fixed32" number:8 label:LABEL_OPTIONAL type:TYPE_FIXED32 default_value:"320000"},
{name:"opt_sfixed32" number:9 label:LABEL_OPTIONAL type:TYPE_SFIXED32 default_value:"-320000"},
{name:"opt_float" number:10 label:LABEL_OPTIONAL type:TYPE_FLOAT default_value:"3.14159"},
{name:"opt_fixed64" number:11 label:LABEL_OPTIONAL type:TYPE_FIXED64 default_value:"640000"},
{name:"opt_sfixed64" number:12 label:LABEL_OPTIONAL type:TYPE_SFIXED64 default_value:"-640000"},
{name:"opt_double" number:13 label:LABEL_OPTIONAL type:TYPE_DOUBLE default_value:"3.14159265359"},
{name:"opt_string" number:14 label:LABEL_OPTIONAL type:TYPE_STRING default_value:"hello, \"world!\"\n"},
{name:"opt_bytes" number:15 label:LABEL_OPTIONAL type:TYPE_BYTES default_value:"dead\\336\\255\\276\\357beef"},
{name:"opt_enum" number:16 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".google_golang_org.protobuf.internal.impl_test.AberrantEnum" default_value:"UNKNOWN_0"},
{name:"opt_message" number:17 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage"},
{name:"rep_bool" number:18 label:LABEL_REPEATED type:TYPE_BOOL options:{packed:true}},
{name:"rep_int32" number:19 label:LABEL_REPEATED type:TYPE_INT32 options:{packed:true}},
{name:"rep_sint32" number:20 label:LABEL_REPEATED type:TYPE_SINT32 options:{packed:true}},
{name:"rep_uint32" number:21 label:LABEL_REPEATED type:TYPE_UINT32 options:{packed:true}},
{name:"rep_int64" number:22 label:LABEL_REPEATED type:TYPE_INT64 options:{packed:true}},
{name:"rep_sint64" number:23 label:LABEL_REPEATED type:TYPE_SINT64 options:{packed:true}},
{name:"rep_uint64" number:24 label:LABEL_REPEATED type:TYPE_UINT64 options:{packed:true}},
{name:"rep_fixed32" number:25 label:LABEL_REPEATED type:TYPE_FIXED32 options:{packed:true}},
{name:"rep_sfixed32" number:26 label:LABEL_REPEATED type:TYPE_SFIXED32 options:{packed:true}},
{name:"rep_float" number:27 label:LABEL_REPEATED type:TYPE_FLOAT options:{packed:true}},
{name:"rep_fixed64" number:28 label:LABEL_REPEATED type:TYPE_FIXED64 options:{packed:true}},
{name:"rep_sfixed64" number:29 label:LABEL_REPEATED type:TYPE_SFIXED64 options:{packed:true}},
{name:"rep_double" number:30 label:LABEL_REPEATED type:TYPE_DOUBLE options:{packed:true}},
{name:"rep_string" number:31 label:LABEL_REPEATED type:TYPE_STRING},
{name:"rep_bytes" number:32 label:LABEL_REPEATED type:TYPE_BYTES},
{name:"rep_enum" number:33 label:LABEL_REPEATED type:TYPE_ENUM type_name:".google_golang_org.protobuf.internal.impl_test.AberrantEnum"},
{name:"rep_message" number:34 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage"},
{name:"map_string_bool" number:35 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringBoolEntry"},
{name:"map_string_int32" number:36 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringInt32Entry"},
{name:"map_string_sint32" number:37 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringSint32Entry"},
{name:"map_string_uint32" number:38 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringUint32Entry"},
{name:"map_string_int64" number:39 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringInt64Entry"},
{name:"map_string_sint64" number:40 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringSint64Entry"},
{name:"map_string_uint64" number:41 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringUint64Entry"},
{name:"map_string_fixed32" number:42 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringFixed32Entry"},
{name:"map_string_sfixed32" number:43 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringSfixed32Entry"},
{name:"map_string_float" number:44 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringFloatEntry"},
{name:"map_string_fixed64" number:45 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringFixed64Entry"},
{name:"map_string_sfixed64" number:46 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringSfixed64Entry"},
{name:"map_string_double" number:47 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringDoubleEntry"},
{name:"map_string_string" number:48 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringStringEntry"},
{name:"map_string_bytes" number:49 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringBytesEntry"},
{name:"map_string_enum" number:50 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringEnumEntry"},
{name:"map_string_message" number:51 label:LABEL_REPEATED type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage.MapStringMessageEntry"},
{name:"oneof_bool" number:52 label:LABEL_OPTIONAL type:TYPE_BOOL oneof_index:0 default_value:"true"},
{name:"oneof_int32" number:53 label:LABEL_OPTIONAL type:TYPE_INT32 oneof_index:0 default_value:"-12345"},
{name:"oneof_sint32" number:54 label:LABEL_OPTIONAL type:TYPE_SINT32 oneof_index:0 default_value:"-3200"},
{name:"oneof_uint32" number:55 label:LABEL_OPTIONAL type:TYPE_UINT32 oneof_index:0 default_value:"3200"},
{name:"oneof_int64" number:56 label:LABEL_OPTIONAL type:TYPE_INT64 oneof_index:0 default_value:"-123456789"},
{name:"oneof_sint64" number:57 label:LABEL_OPTIONAL type:TYPE_SINT64 oneof_index:0 default_value:"-6400"},
{name:"oneof_uint64" number:58 label:LABEL_OPTIONAL type:TYPE_UINT64 oneof_index:0 default_value:"6400"},
{name:"oneof_fixed32" number:59 label:LABEL_OPTIONAL type:TYPE_FIXED32 oneof_index:0 default_value:"320000"},
{name:"oneof_sfixed32" number:60 label:LABEL_OPTIONAL type:TYPE_SFIXED32 oneof_index:0 default_value:"-320000"},
{name:"oneof_float" number:61 label:LABEL_OPTIONAL type:TYPE_FLOAT oneof_index:0 default_value:"3.14159"},
{name:"oneof_fixed64" number:62 label:LABEL_OPTIONAL type:TYPE_FIXED64 oneof_index:0 default_value:"640000"},
{name:"oneof_sfixed64" number:63 label:LABEL_OPTIONAL type:TYPE_SFIXED64 oneof_index:0 default_value:"-640000"},
{name:"oneof_double" number:64 label:LABEL_OPTIONAL type:TYPE_DOUBLE oneof_index:0 default_value:"3.14159265359"},
{name:"oneof_string" number:65 label:LABEL_OPTIONAL type:TYPE_STRING oneof_index:0 default_value:"hello, \"world!\"\n"},
{name:"oneof_bytes" number:66 label:LABEL_OPTIONAL type:TYPE_BYTES oneof_index:0 default_value:"dead\\336\\255\\276\\357beef"},
{name:"oneof_enum" number:67 label:LABEL_OPTIONAL type:TYPE_ENUM oneof_index:0 type_name:".google_golang_org.protobuf.internal.impl_test.AberrantEnum" default_value:"UNKNOWN_0"},
{name:"oneof_message" number:68 label:LABEL_OPTIONAL type:TYPE_MESSAGE oneof_index:0 type_name:".google.golang.org.example.AberrantMessage"}
]
oneof_decl: [{name:"oneof_union"}]
extension_range: [{start:10 end:101}]
nested_type: [
{name:"MapStringBoolEntry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_BOOL}] options:{map_entry:true}},
{name:"MapStringInt32Entry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_INT32}] options:{map_entry:true}},
{name:"MapStringSint32Entry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_SINT32}] options:{map_entry:true}},
{name:"MapStringUint32Entry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_UINT32}] options:{map_entry:true}},
{name:"MapStringInt64Entry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_INT64}] options:{map_entry:true}},
{name:"MapStringSint64Entry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_SINT64}] options:{map_entry:true}},
{name:"MapStringUint64Entry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_UINT64}] options:{map_entry:true}},
{name:"MapStringFixed32Entry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_FIXED32}] options:{map_entry:true}},
{name:"MapStringSfixed32Entry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_SFIXED32}] options:{map_entry:true}},
{name:"MapStringFloatEntry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_FLOAT}] options:{map_entry:true}},
{name:"MapStringFixed64Entry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_FIXED64}] options:{map_entry:true}},
{name:"MapStringSfixed64Entry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_SFIXED64}] options:{map_entry:true}},
{name:"MapStringDoubleEntry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_DOUBLE}] options:{map_entry:true}},
{name:"MapStringStringEntry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_STRING}] options:{map_entry:true}},
{name:"MapStringBytesEntry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_BYTES}] options:{map_entry:true}},
{name:"MapStringEnumEntry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_ENUM type_name:".google_golang_org.protobuf.internal.impl_test.AberrantEnum"}] options:{map_entry:true}},
{name:"MapStringMessageEntry" field:[{name:"key" number:1 label:LABEL_OPTIONAL type:TYPE_STRING}, {name:"value" number:2 label:LABEL_OPTIONAL type:TYPE_MESSAGE type_name:".google.golang.org.example.AberrantMessage"}] options:{map_entry:true}}
]
`), want); err != nil {
t.Fatalf("prototext.Unmarshal() error: %v", err)
}
md := impl.LegacyLoadMessageDesc(reflect.TypeOf(&AberrantMessage{}))
got := protodesc.ToDescriptorProto(md)
if !proto.Equal(got, want) {
t.Errorf("mismatching descriptor:\ngot %v\nwant %v", got, want)
}
}

View File

@ -7,8 +7,10 @@ package impl
import (
"fmt"
"reflect"
"strings"
"sync"
"google.golang.org/protobuf/internal/filedesc"
pvalue "google.golang.org/protobuf/internal/value"
"google.golang.org/protobuf/reflect/protoreflect"
pref "google.golang.org/protobuf/reflect/protoreflect"
@ -100,7 +102,7 @@ func LegacyLoadEnumDesc(t reflect.Type) pref.EnumDescriptor {
}
edV1, ok := ev.(enumV1)
if !ok {
panic(fmt.Sprintf("enum %v is no longer supported; please regenerate", t))
return aberrantLoadEnumDesc(t)
}
b, idxs := edV1.EnumDescriptor()
@ -119,3 +121,71 @@ func LegacyLoadEnumDesc(t reflect.Type) pref.EnumDescriptor {
}
return ed
}
var aberrantEnumDescCache sync.Map // map[reflect.Type]protoreflect.EnumDescriptor
// aberrantLoadEnumDesc returns an EnumDescriptor derived from the Go type,
// which must not implement protoreflect.Enum or enumV1.
//
// If the type does not implement enumV1, then there is no reliable
// way to derive the original protobuf type information.
// We are unable to use the global enum registry since it is
// unfortunately keyed by the protobuf full name, which we also do not know.
// Thus, this produces some bogus enum descriptor based on the Go type name.
func aberrantLoadEnumDesc(t reflect.Type) pref.EnumDescriptor {
// Fast-path: check if an EnumDescriptor is cached for this concrete type.
if ed, ok := aberrantEnumDescCache.Load(t); ok {
return ed.(pref.EnumDescriptor)
}
// Slow-path: construct a bogus, but unique EnumDescriptor.
ed := &filedesc.Enum{L2: new(filedesc.EnumL2)}
ed.L0.FullName = aberrantDeriveFullName(t) // e.g., github_com.user.repo.MyEnum
ed.L0.ParentFile = filedesc.SurrogateProto3
ed.L2.Values.List = append(ed.L2.Values.List, filedesc.EnumValue{})
// TODO: Use the presence of a UnmarshalJSON method to determine proto2?
vd := &ed.L2.Values.List[0]
vd.L0.FullName = ed.L0.FullName + "_UNKNOWN" // e.g., github_com.user.repo.MyEnum_UNKNOWN
vd.L0.ParentFile = ed.L0.ParentFile
vd.L0.Parent = ed
// TODO: We could use the String method to obtain some enum value names by
// starting at 0 and print the enum until it produces invalid identifiers.
// An exhaustive query is clearly impractical, but can be best-effort.
if ed, ok := aberrantEnumDescCache.LoadOrStore(t, ed); ok {
return ed.(pref.EnumDescriptor)
}
return ed
}
// aberrantDeriveFullName derives a fully qualified protobuf name for the given Go type
// The provided name is not guaranteed to be stable nor universally unique.
// It should be sufficiently unique within a program.
func aberrantDeriveFullName(t reflect.Type) pref.FullName {
sanitize := func(r rune) rune {
switch {
case r == '/':
return '.'
case 'a' <= r && r <= 'z', 'A' <= r && r <= 'Z', '0' <= r && r <= '9':
return r
default:
return '_'
}
}
prefix := strings.Map(sanitize, t.PkgPath())
suffix := strings.Map(sanitize, t.Name())
if suffix == "" {
suffix = fmt.Sprintf("UnknownX%X", reflect.ValueOf(t).Pointer())
}
ss := append(strings.Split(prefix, "."), suffix)
for i, s := range ss {
if s == "" || ('0' <= s[0] && s[0] <= '9') {
ss[i] = "x" + s
}
}
return pref.FullName(strings.Join(ss, "."))
}

View File

@ -7,8 +7,13 @@ package impl
import (
"fmt"
"reflect"
"strings"
"sync"
"unicode"
"google.golang.org/protobuf/internal/descopts"
ptag "google.golang.org/protobuf/internal/encoding/tag"
"google.golang.org/protobuf/internal/filedesc"
"google.golang.org/protobuf/reflect/protoreflect"
pref "google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/prototype"
@ -54,6 +59,9 @@ var legacyMessageDescCache sync.Map // map[reflect.Type]protoreflect.MessageDesc
//
// This is exported for testing purposes.
func LegacyLoadMessageDesc(t reflect.Type) pref.MessageDescriptor {
return legacyLoadMessageDesc(t, true)
}
func legacyLoadMessageDesc(t reflect.Type, finalized bool) pref.MessageDescriptor {
// Fast-path: check if a MessageDescriptor is cached for this concrete type.
if mi, ok := legacyMessageDescCache.Load(t); ok {
return mi.(pref.MessageDescriptor)
@ -66,7 +74,7 @@ func LegacyLoadMessageDesc(t reflect.Type) pref.MessageDescriptor {
}
mdV1, ok := mv.(messageV1)
if !ok {
panic(fmt.Sprintf("message %v is no longer supported; please regenerate", t))
return aberrantLoadMessageDesc(t, finalized)
}
b, idxs := mdV1.Descriptor()
@ -79,3 +87,240 @@ func LegacyLoadMessageDesc(t reflect.Type) pref.MessageDescriptor {
}
return md
}
var aberrantMessageDescCache sync.Map // map[reflect.Type]aberrantMessageDesc
// aberrantMessageDesc is a tuple containing a MessageDescriptor and a channel
// to signal whether the descriptor is initialized. For external lookups,
// we must ensure that the descriptor is fully initialized. For internal lookups
// to resolve cycles, we only need to obtain the descriptor reference.
type aberrantMessageDesc struct {
desc protoreflect.MessageDescriptor
done chan struct{} // closed when desc is fully initialized
}
// aberrantLoadEnumDesc returns an EnumDescriptor derived from the Go type,
// which must not implement protoreflect.ProtoMessage or messageV1.
//
// This is a best-effort derivation of the message descriptor using the protobuf
// tags on the struct fields.
//
// The finalized flag determines whether the returned message descriptor must
// be fully initialized.
func aberrantLoadMessageDesc(t reflect.Type, finalized bool) pref.MessageDescriptor {
// Fast-path: check if an MessageDescriptor is cached for this concrete type.
if mdi, ok := aberrantMessageDescCache.Load(t); ok {
if finalized {
<-mdi.(aberrantMessageDesc).done
}
return mdi.(aberrantMessageDesc).desc
}
// Medium-path: create an initial descriptor and cache it immediately,
// so that cyclic references can be resolved. Each descriptor is paired
// with a channel to signal when the descriptor is fully initialized.
md := &filedesc.Message{L2: new(filedesc.MessageL2)}
mdi := aberrantMessageDesc{desc: md, done: make(chan struct{})}
if mdi, ok := aberrantMessageDescCache.LoadOrStore(t, mdi); ok {
if finalized {
<-mdi.(aberrantMessageDesc).done
}
return mdi.(aberrantMessageDesc).desc
}
defer func() { close(mdi.done) }()
// Slow-path: construct a descriptor from the Go struct type (best-effort).
md.L0.FullName = aberrantDeriveFullName(t.Elem())
md.L0.ParentFile = filedesc.SurrogateProto2
// If possible, use the custom protobuf name specified on the type.
fn, ok := t.MethodByName("XXX_MessageName")
if !ok {
fn, ok = t.Elem().MethodByName("XXX_MessageName")
}
if ok {
v := fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[0]
if s := pref.FullName(v.String()); s.IsValid() {
md.L0.FullName = s
}
}
// Try to determine if the message is using proto3 by checking scalars.
for i := 0; i < t.Elem().NumField(); i++ {
f := t.Elem().Field(i)
if tag := f.Tag.Get("protobuf"); tag != "" {
switch f.Type.Kind() {
case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
md.L0.ParentFile = filedesc.SurrogateProto3
}
for _, s := range strings.Split(tag, ",") {
if s == "proto3" {
md.L0.ParentFile = filedesc.SurrogateProto3
}
}
}
}
// Obtain a list of oneof wrapper types.
var oneofWrappers []reflect.Type
if fn, ok := t.MethodByName("XXX_OneofFuncs"); ok {
vs := fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[3]
for _, v := range vs.Interface().([]interface{}) {
oneofWrappers = append(oneofWrappers, reflect.TypeOf(v))
}
}
if fn, ok := t.MethodByName("XXX_OneofWrappers"); ok {
vs := fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[0]
for _, v := range vs.Interface().([]interface{}) {
oneofWrappers = append(oneofWrappers, reflect.TypeOf(v))
}
}
// Obtain a list of the extension ranges.
if fn, ok := t.MethodByName("ExtensionRangeArray"); ok {
vs := fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[0]
for i := 0; i < vs.Len(); i++ {
v := vs.Index(i)
md.L2.ExtensionRanges.List = append(md.L2.ExtensionRanges.List, [2]pref.FieldNumber{
pref.FieldNumber(v.FieldByName("Start").Int()),
pref.FieldNumber(v.FieldByName("End").Int() + 1),
})
md.L2.ExtensionRangeOptions = append(md.L2.ExtensionRangeOptions, nil)
}
}
// Derive the message fields by inspecting the struct fields.
for i := 0; i < t.Elem().NumField(); i++ {
f := t.Elem().Field(i)
if tag := f.Tag.Get("protobuf"); tag != "" {
tagKey := f.Tag.Get("protobuf_key")
tagVal := f.Tag.Get("protobuf_val")
aberrantAppendField(md, f.Type, tag, tagKey, tagVal)
}
if tag := f.Tag.Get("protobuf_oneof"); tag != "" {
n := len(md.L2.Oneofs.List)
md.L2.Oneofs.List = append(md.L2.Oneofs.List, filedesc.Oneof{})
od := &md.L2.Oneofs.List[n]
od.L0.FullName = md.FullName().Append(pref.Name(tag))
od.L0.ParentFile = md.L0.ParentFile
od.L0.Parent = md
od.L0.Index = n
for _, t := range oneofWrappers {
if t.Implements(f.Type) {
f := t.Elem().Field(0)
if tag := f.Tag.Get("protobuf"); tag != "" {
aberrantAppendField(md, f.Type, tag, "", "")
fd := &md.L2.Fields.List[len(md.L2.Fields.List)-1]
fd.L1.ContainingOneof = od
od.L1.Fields.List = append(od.L1.Fields.List, fd)
}
}
}
}
}
// TODO: Use custom Marshal/Unmarshal methods for the fast-path?
return md
}
func aberrantAppendField(md *filedesc.Message, goType reflect.Type, tag, tagKey, tagVal string) {
t := goType
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()
}
fd := ptag.Unmarshal(tag, t, placeholderEnumValues{}).(*filedesc.Field)
// Append field descriptor to the message.
n := len(md.L2.Fields.List)
md.L2.Fields.List = append(md.L2.Fields.List, *fd)
fd = &md.L2.Fields.List[n]
fd.L0.FullName = md.FullName().Append(fd.Name())
fd.L0.ParentFile = md.L0.ParentFile
fd.L0.Parent = md
fd.L0.Index = n
if fd.L1.IsWeak || fd.L1.HasPacked {
fd.L1.Options = func() pref.ProtoMessage {
opts := descopts.Field.ProtoReflect().New()
if fd.L1.IsWeak {
opts.Set(opts.Descriptor().Fields().ByName("weak"), protoreflect.ValueOf(true))
}
if fd.L1.HasPacked {
opts.Set(opts.Descriptor().Fields().ByName("packed"), protoreflect.ValueOf(fd.L1.IsPacked))
}
return opts.Interface()
}
}
// Populate Enum and Message.
if fd.Enum() == nil && fd.Kind() == pref.EnumKind {
switch v := reflect.Zero(t).Interface().(type) {
case pref.Enum:
fd.L1.Enum = v.Descriptor()
default:
fd.L1.Enum = LegacyLoadEnumDesc(t)
}
}
if fd.Message() == nil && (fd.Kind() == pref.MessageKind || fd.Kind() == pref.GroupKind) {
switch v := reflect.Zero(t).Interface().(type) {
case pref.ProtoMessage:
fd.L1.Message = v.ProtoReflect().Descriptor()
default:
if t.Kind() == reflect.Map {
n := len(md.L1.Messages.List)
md.L1.Messages.List = append(md.L1.Messages.List, filedesc.Message{L2: new(filedesc.MessageL2)})
md2 := &md.L1.Messages.List[n]
md2.L0.FullName = md.FullName().Append(aberrantMapEntryName(fd.Name()))
md2.L0.ParentFile = md.L0.ParentFile
md2.L0.Parent = md
md2.L0.Index = n
md2.L2.IsMapEntry = true
md2.L2.Options = func() pref.ProtoMessage {
opts := descopts.Message.ProtoReflect().New()
opts.Set(opts.Descriptor().Fields().ByName("map_entry"), protoreflect.ValueOf(true))
return opts.Interface()
}
aberrantAppendField(md2, t.Key(), tagKey, "", "")
aberrantAppendField(md2, t.Elem(), tagVal, "", "")
fd.L1.Message = md2
break
}
fd.L1.Message = aberrantLoadMessageDesc(t, false)
}
}
}
type placeholderEnumValues struct {
protoreflect.EnumValueDescriptors
}
func (placeholderEnumValues) ByNumber(n pref.EnumNumber) pref.EnumValueDescriptor {
return filedesc.PlaceholderEnumValue(pref.FullName(fmt.Sprintf("UNKNOWN_%d", n)))
}
// aberrantMapEntryName derives the name for a map entry message.
// See protoc v3.8.0: src/google/protobuf/descriptor.cc:254-276,6057
func aberrantMapEntryName(s pref.Name) pref.Name {
var b []byte
upperNext := true
for _, c := range s {
switch {
case c == '_':
upperNext = true
case upperNext:
b = append(b, byte(unicode.ToUpper(c)))
upperNext = false
default:
b = append(b, byte(c))
}
}
b = append(b, "Entry"...)
return pref.Name(b)
}