protobuf-go/cmd/protoc-gen-go/internal_gengo/reflect.go
Joe Tsai afb455eaf8 cmd/protoc-gen-go: correlate v1 ExtensionDesc with v2 ExtensionType
Unfortunately a good amount of code uses pointer comparisons on the
v1 ExtensionDesc to determine exactly which extension field is set,
rather than checking whether the extension descriptor semantically
describes the field that they are interested in.

To preserve this behavior in v1, we need a 1:1 mapping between
a v2 ExtensionType and a specific v1 ExtensionDesc.

Change-Id: I852b3cefb4585bd656e48e5adad6cc28795d02df
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/167759
Reviewed-by: Damien Neil <dneil@google.com>
2019-03-15 01:24:42 +00:00

248 lines
8.3 KiB
Go

// 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 internal_gengo
import (
"fmt"
"math"
"strings"
"github.com/golang/protobuf/v2/protogen"
"github.com/golang/protobuf/v2/reflect/protoreflect"
)
// TODO: Remove special-casing for descriptor proto.
func isDescriptor(f *protogen.File) bool {
return f.Desc.Path() == "google/protobuf/descriptor.proto" && f.Desc.Package() == "google.protobuf"
}
// minimumVersion is minimum version of the v2 proto package that is required.
// This is incremented every time the generated code relies on some property
// in the proto package that was introduced in a later version.
const minimumVersion = 0
const (
reflectPackage = protogen.GoImportPath("reflect")
protoimplPackage = protogen.GoImportPath("github.com/golang/protobuf/v2/runtime/protoimpl")
protoreflectPackage = protogen.GoImportPath("github.com/golang/protobuf/v2/reflect/protoreflect")
prototypePackage = protogen.GoImportPath("github.com/golang/protobuf/v2/internal/prototype")
)
// TODO: Add support for proto options.
func genReflectFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo) {
// Emit a static check that enforces a minimum version of the proto package.
// TODO: This should appear higher up in the Go source file.
g.P("const _ = ", protoimplPackage.Ident("EnforceVersion"), "(", protoimplPackage.Ident("Version"), " - ", minimumVersion, ")")
g.P("var ", f.GoDescriptorIdent, " ", protoreflectPackage.Ident("FileDescriptor"))
g.P()
if len(f.allEnums) > 0 {
g.P("var ", enumTypesVarName(f), " = make([]", protoreflectPackage.Ident("EnumType"), ",", len(f.allEnums), ")")
}
if len(f.allMessages) > 0 {
g.P("var ", messageTypesVarName(f), " = make([]", protoimplPackage.Ident("MessageType"), ",", len(f.allMessages), ")")
}
// Generate a unique list of Go types for all declarations and dependencies,
// and the associated index into the type list for all dependencies.
var goTypes []string
var depIdxs []string
seen := map[protoreflect.FullName]int{}
genDep := func(name protoreflect.FullName, depSource string) {
if depSource != "" {
line := fmt.Sprintf("%d, // %s -> %s", seen[name], depSource, name)
depIdxs = append(depIdxs, line)
}
}
genEnum := func(e *protogen.Enum, depSource string) {
if e != nil {
name := e.Desc.FullName()
if _, ok := seen[name]; !ok {
line := fmt.Sprintf("(%s)(0), // %d: %s", g.QualifiedGoIdent(e.GoIdent), len(goTypes), name)
goTypes = append(goTypes, line)
seen[name] = len(seen)
}
if depSource != "" {
genDep(name, depSource)
}
}
}
genMessage := func(m *protogen.Message, depSource string) {
if m != nil {
name := m.Desc.FullName()
if _, ok := seen[name]; !ok {
line := fmt.Sprintf("(*%s)(nil), // %d: %s", g.QualifiedGoIdent(m.GoIdent), len(goTypes), name)
if m.Desc.IsMapEntry() {
// Map entry messages have no associated Go type.
line = fmt.Sprintf("nil, // %d: %s", len(goTypes), name)
}
goTypes = append(goTypes, line)
seen[name] = len(seen)
}
if depSource != "" {
genDep(name, depSource)
}
}
}
// This ordering is significant. See protoimpl.FileBuilder.GoTypes.
for _, enum := range f.allEnums {
genEnum(enum, "")
}
for _, message := range f.allMessages {
genMessage(message, "")
}
for _, extension := range f.allExtensions {
source := string(extension.Desc.FullName())
genMessage(extension.ExtendedType, source+":extendee")
}
for _, message := range f.allMessages {
for _, field := range message.Fields {
if field.Desc.IsWeak() {
continue
}
source := string(field.Desc.FullName())
genEnum(field.EnumType, source+":type_name")
genMessage(field.MessageType, source+":type_name")
}
}
for _, extension := range f.allExtensions {
source := string(extension.Desc.FullName())
genEnum(extension.EnumType, source+":type_name")
genMessage(extension.MessageType, source+":type_name")
}
for _, service := range f.Services {
for _, method := range service.Methods {
source := string(method.Desc.FullName())
genMessage(method.InputType, source+":input_type")
genMessage(method.OutputType, source+":output_type")
}
}
if len(depIdxs) > math.MaxInt32 {
panic("too many dependencies") // sanity check
}
g.P("var ", goTypesVarName(f), " = []interface{}{")
for _, s := range goTypes {
g.P(s)
}
g.P("}")
g.P("var ", depIdxsVarName(f), " = []int32{")
for _, s := range depIdxs {
g.P(s)
}
g.P("}")
g.P("func init() { ", initFuncName(f.File), "() }")
g.P("func ", initFuncName(f.File), "() {")
g.P("if ", f.GoDescriptorIdent, " != nil {")
g.P("return")
g.P("}")
// Ensure that initialization functions for different files in the same Go
// package run in the correct order: Call the init funcs for every .proto file
// imported by this one that is in the same Go package.
for i, imps := 0, f.Desc.Imports(); i < imps.Len(); i++ {
impFile, _ := gen.FileByName(imps.Get(i).Path())
if impFile.GoImportPath != f.GoImportPath {
continue
}
g.P(initFuncName(impFile), "()")
}
if len(f.allMessages) > 0 {
g.P("messageTypes := make([]", protoreflectPackage.Ident("MessageType"), ",", len(f.allMessages), ")")
}
if len(f.allExtensions) > 0 {
g.P("extensionTypes := make([]", protoreflectPackage.Ident("ExtensionType"), ",", len(f.allExtensions), ")")
}
g.P(f.GoDescriptorIdent, " = ", protoimplPackage.Ident("FileBuilder"), "{")
g.P("RawDescriptor: ", f.descriptorRawVar, ",")
g.P("GoTypes: ", goTypesVarName(f), ",")
g.P("DependencyIndexes: ", depIdxsVarName(f), ",")
if len(f.allExtensions) > 0 {
g.P("LegacyExtensions: ", extDecsVarName(f), ",")
}
if len(f.allEnums) > 0 {
g.P("EnumOutputTypes: ", enumTypesVarName(f), ",")
}
if len(f.allMessages) > 0 {
g.P("MessageOutputTypes: messageTypes,")
}
if len(f.allExtensions) > 0 {
g.P("ExtensionOutputTypes: extensionTypes,")
}
g.P("}.Init()")
// Copy the local list of message types into the global array.
if len(f.allMessages) > 0 {
g.P("messageGoTypes := ", goTypesVarName(f), "[", len(f.allEnums), ":][:", len(f.allMessages), "]")
g.P("for i, mt := range messageTypes {")
g.P(messageTypesVarName(f), "[i].GoType = ", reflectPackage.Ident("TypeOf"), "(messageGoTypes[i])")
g.P(messageTypesVarName(f), "[i].PBType = mt")
g.P("}")
}
// TODO: Add v2 registration and stop v1 registration in genInitFunction.
// The descriptor proto needs to register the option types with the
// prototype so that the package can properly handle those option types.
if isDescriptor(f.File) {
for _, m := range f.allMessages {
name := m.GoIdent.GoName
if strings.HasSuffix(name, "Options") {
g.P(prototypePackage.Ident("X"), ".Register", name, "((*", name, ")(nil))")
}
}
}
g.P(goTypesVarName(f), " = nil") // allow GC to reclaim resource
g.P(depIdxsVarName(f), " = nil") // allow GC to reclaim resource
g.P("}")
}
func genReflectEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, enum *protogen.Enum) {
idx := f.allEnumsByPtr[enum]
typesVar := enumTypesVarName(f)
g.P("func (e ", enum.GoIdent, ") Type() ", protoreflectPackage.Ident("EnumType"), " {")
g.P("return ", typesVar, "[", idx, "]")
g.P("}")
g.P("func (e ", enum.GoIdent, ") Number() ", protoreflectPackage.Ident("EnumNumber"), " {")
g.P("return ", protoreflectPackage.Ident("EnumNumber"), "(e)")
g.P("}")
}
func genReflectMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *fileInfo, message *protogen.Message) {
idx := f.allMessagesByPtr[message]
typesVar := messageTypesVarName(f)
g.P("func (m *", message.GoIdent, ") ProtoReflect() ", protoreflectPackage.Ident("Message"), " {")
g.P("return ", typesVar, "[", idx, "].MessageOf(m)")
g.P("}")
}
func goTypesVarName(f *fileInfo) string {
return "xxx_" + f.GoDescriptorIdent.GoName + "_goTypes"
}
func depIdxsVarName(f *fileInfo) string {
return "xxx_" + f.GoDescriptorIdent.GoName + "_depIdxs"
}
func enumTypesVarName(f *fileInfo) string {
return "xxx_" + f.GoDescriptorIdent.GoName + "_enumTypes"
}
func messageTypesVarName(f *fileInfo) string {
return "xxx_" + f.GoDescriptorIdent.GoName + "_messageTypes"
}
func extDecsVarName(f *fileInfo) string {
return "xxx_" + f.GoDescriptorIdent.GoName + "_extDescs"
}
func initFuncName(f *protogen.File) string {
return "xxx_" + f.GoDescriptorIdent.GoName + "_init"
}