blob: bc68efe12c7d41cc81ca13d1bc0decaa322c9655 [file] [log] [blame]
// Protocol Buffers for Go with Gadgets
//
// Copyright (c) 2013, The GoGo Authors. All rights reserved.
// http://github.com/gogo/protobuf
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/*
The embedcheck plugin is used to check whether embed is not used incorrectly.
For instance:
An embedded message has a generated string method, but the is a member of a message which does not.
This causes a warning.
An error is caused by a namespace conflict.
It is enabled by the following extensions:
- embed
- embed_all
For incorrect usage of embed with tests see:
github.com/gogo/protobuf/test/embedconflict
*/
package embedcheck
import (
"fmt"
"os"
"github.com/gogo/protobuf/gogoproto"
"github.com/gogo/protobuf/protoc-gen-gogo/generator"
)
type plugin struct {
*generator.Generator
}
func NewPlugin() *plugin {
return &plugin{}
}
func (p *plugin) Name() string {
return "embedcheck"
}
func (p *plugin) Init(g *generator.Generator) {
p.Generator = g
}
var overwriters []map[string]gogoproto.EnableFunc = []map[string]gogoproto.EnableFunc{
{
"stringer": gogoproto.IsStringer,
},
{
"gostring": gogoproto.HasGoString,
},
{
"equal": gogoproto.HasEqual,
},
{
"verboseequal": gogoproto.HasVerboseEqual,
},
{
"size": gogoproto.IsSizer,
"protosizer": gogoproto.IsProtoSizer,
},
{
"unmarshaler": gogoproto.IsUnmarshaler,
"unsafe_unmarshaler": gogoproto.IsUnsafeUnmarshaler,
},
{
"marshaler": gogoproto.IsMarshaler,
"unsafe_marshaler": gogoproto.IsUnsafeMarshaler,
},
}
func (p *plugin) Generate(file *generator.FileDescriptor) {
for _, msg := range file.Messages() {
for _, os := range overwriters {
possible := true
for _, overwriter := range os {
if overwriter(file.FileDescriptorProto, msg.DescriptorProto) {
possible = false
}
}
if possible {
p.checkOverwrite(msg, os)
}
}
p.checkNameSpace(msg)
for _, field := range msg.GetField() {
if gogoproto.IsEmbed(field) && gogoproto.IsCustomName(field) {
fmt.Fprintf(os.Stderr, "ERROR: field %v with custom name %v cannot be embedded", *field.Name, gogoproto.GetCustomName(field))
os.Exit(1)
}
}
p.checkRepeated(msg)
}
for _, e := range file.GetExtension() {
if gogoproto.IsEmbed(e) {
fmt.Fprintf(os.Stderr, "ERROR: extended field %v cannot be embedded", generator.CamelCase(*e.Name))
os.Exit(1)
}
}
}
func (p *plugin) checkNameSpace(message *generator.Descriptor) map[string]bool {
ccTypeName := generator.CamelCaseSlice(message.TypeName())
names := make(map[string]bool)
for _, field := range message.Field {
fieldname := generator.CamelCase(*field.Name)
if field.IsMessage() && gogoproto.IsEmbed(field) {
desc := p.ObjectNamed(field.GetTypeName())
moreNames := p.checkNameSpace(desc.(*generator.Descriptor))
for another := range moreNames {
if names[another] {
fmt.Fprintf(os.Stderr, "ERROR: duplicate embedded fieldname %v in type %v\n", fieldname, ccTypeName)
os.Exit(1)
}
names[another] = true
}
} else {
if names[fieldname] {
fmt.Fprintf(os.Stderr, "ERROR: duplicate embedded fieldname %v in type %v\n", fieldname, ccTypeName)
os.Exit(1)
}
names[fieldname] = true
}
}
return names
}
func (p *plugin) checkOverwrite(message *generator.Descriptor, enablers map[string]gogoproto.EnableFunc) {
ccTypeName := generator.CamelCaseSlice(message.TypeName())
names := []string{}
for name := range enablers {
names = append(names, name)
}
for _, field := range message.Field {
if field.IsMessage() && gogoproto.IsEmbed(field) {
fieldname := generator.CamelCase(*field.Name)
desc := p.ObjectNamed(field.GetTypeName())
msg := desc.(*generator.Descriptor)
for errStr, enabled := range enablers {
if enabled(msg.File().FileDescriptorProto, msg.DescriptorProto) {
fmt.Fprintf(os.Stderr, "WARNING: found non-%v %v with embedded %v %v\n", names, ccTypeName, errStr, fieldname)
}
}
p.checkOverwrite(msg, enablers)
}
}
}
func (p *plugin) checkRepeated(message *generator.Descriptor) {
ccTypeName := generator.CamelCaseSlice(message.TypeName())
for _, field := range message.Field {
if !gogoproto.IsEmbed(field) {
continue
}
if field.IsBytes() {
fieldname := generator.CamelCase(*field.Name)
fmt.Fprintf(os.Stderr, "ERROR: found embedded bytes field %s in message %s\n", fieldname, ccTypeName)
os.Exit(1)
}
if !field.IsRepeated() {
continue
}
fieldname := generator.CamelCase(*field.Name)
fmt.Fprintf(os.Stderr, "ERROR: found repeated embedded field %s in message %s\n", fieldname, ccTypeName)
os.Exit(1)
}
}
func (p *plugin) GenerateImports(*generator.FileDescriptor) {}
func init() {
generator.RegisterPlugin(NewPlugin())
}