blob: 90dbd97a1389b59cf857d5d30e9a01214d9f9af1 [file] [log] [blame]
// Copyright 2024 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 fix
import (
"context"
"fmt"
"testing"
"github.com/dave/dst"
"github.com/google/go-cmp/cmp"
spb "google.golang.org/open2opaque/internal/dashboard"
"google.golang.org/open2opaque/internal/o2o/statsutil"
"google.golang.org/protobuf/testing/protocmp"
)
func TestStats(t *testing.T) {
t.Setenv("GODEBUG", "gotypesalias=1")
const extra = `
type NotAProto struct {
S *string
Field struct{}
}
var notAProto *NotAProto
func g() string { return "" }
`
loc := func(startLn, startCol, endLn, endCol int64) *spb.Location {
return &spb.Location{
Package: "google.golang.org/open2opaque/internal/fix/testdata/fake",
File: "google.golang.org/open2opaque/internal/fix/testdata/fake/pkg_test.go",
Start: &spb.Position{
Line: startLn,
Column: startCol,
},
End: &spb.Position{
Line: endLn,
Column: endCol,
},
}
}
const protoMsg = "google.golang.org/protobuf/proto.Message"
m2Val := statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2")
m2 := statsutil.ShortAndLongNameFrom("*google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2")
m3Val := statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")
m3 := statsutil.ShortAndLongNameFrom("*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")
expr := func(node, parent any) *spb.Expression {
return &spb.Expression{
Type: fmt.Sprintf("%T", node),
ParentType: fmt.Sprintf("%T", parent),
}
}
entry := func(loc *spb.Location, typ *spb.Type, expr *spb.Expression, use any) *spb.Entry {
e := &spb.Entry{
Location: loc,
Level: toRewriteLevel(None),
Type: typ,
Expr: expr,
}
switch use := use.(type) {
case *spb.Use:
e.Use = use
case *spb.Constructor:
e.Use = &spb.Use{
Type: spb.Use_CONSTRUCTOR,
Constructor: use,
}
case *spb.Conversion:
e.Use = &spb.Use{
Type: spb.Use_CONVERSION,
Conversion: use,
}
case *spb.TypeAssertion:
e.Use = &spb.Use{
Type: spb.Use_TYPE_ASSERTION,
TypeAssertion: use,
}
case *spb.TypeDefinition:
e.Use = &spb.Use{
Type: spb.Use_TYPE_DEFINITION,
TypeDefinition: use,
}
case *spb.Embedding:
e.Use = &spb.Use{
Type: spb.Use_EMBEDDING,
Embedding: use,
}
case *spb.ShallowCopy:
e.Use = &spb.Use{
Type: spb.Use_SHALLOW_COPY,
ShallowCopy: use,
}
case *spb.MethodCall:
e.Use = spb.Use_builder{
Type: spb.Use_METHOD_CALL,
MethodCall: use,
}.Build()
default:
panic(fmt.Sprintf("Bad 'use' type: %T", use))
}
return e
}
directFieldAccess := func(fieldName, shortType, longType string) *spb.Use {
return &spb.Use{
Type: spb.Use_DIRECT_FIELD_ACCESS,
DirectFieldAccess: &spb.FieldAccess{
FieldName: fieldName,
FieldType: &spb.Type{
ShortName: shortType,
LongName: longType,
},
},
}
}
typeMissing := func(loc *spb.Location, expr *spb.Expression) *spb.Entry {
return spb.Entry_builder{
Status: spb.Status_builder{
Type: spb.Status_FAIL,
Error: "type information missing; are dependencies in a silo?",
}.Build(),
Location: loc,
Level: toRewriteLevel(None),
Expr: expr,
}.Build()
}
callStmt := expr(&dst.CallExpr{}, &dst.ExprStmt{})
callCall := expr(&dst.CallExpr{}, &dst.CallExpr{})
callConv := func(dstType, fname, fpkg, fsig string, c spb.Conversion_Context) *spb.Conversion {
return &spb.Conversion{
DestTypeName: dstType,
FuncArg: &spb.FuncArg{
FunctionName: fname,
PackagePath: fpkg,
Signature: fsig,
},
Context: c,
}
}
retStmt := expr(&dst.ReturnStmt{}, &dst.BlockStmt{})
assignStmt := expr(&dst.AssignStmt{}, &dst.BlockStmt{})
assignConv := func(dstType string) *spb.Conversion {
return &spb.Conversion{
DestTypeName: dstType,
Context: spb.Conversion_ASSIGNMENT,
}
}
kvCLit := expr(&dst.KeyValueExpr{}, &dst.CompositeLit{})
starCLit := expr(&dst.StarExpr{}, &dst.CompositeLit{})
identCLit := expr(&dst.Ident{}, &dst.CompositeLit{})
elemConv := func(dstType string) *spb.Conversion {
return &spb.Conversion{
DestTypeName: dstType,
Context: spb.Conversion_COMPOSITE_LITERAL_ELEMENT,
}
}
selAssign := expr(&dst.SelectorExpr{}, &dst.AssignStmt{})
cLitUnary := expr(&dst.CompositeLit{}, &dst.UnaryExpr{})
cLitCLit := expr(&dst.CompositeLit{}, &dst.CompositeLit{})
emptyLiteral := &spb.Constructor{Type: spb.Constructor_EMPTY_LITERAL}
nonEmptyLiteral := &spb.Constructor{Type: spb.Constructor_NONEMPTY_LITERAL}
builderLiteral := &spb.Constructor{Type: spb.Constructor_BUILDER}
tests := []test{{
desc: "direct field accesses",
extra: extra,
in: `
// simple proto2/proto3 access
_ = m2.S
m2.S = nil
_ = m3.S
m3.S = ""
// repeated fields
m3.Ms[0] = nil
m3.Ms = append(m3.Ms, m3)
// multiple selector expressions
m3.M.M.S = ""
// accessing a method is not a direct field access
_ = m3.GetS
// direct field access in non-protos don't count
var s NotAProto
_ = s.S
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
// simple proto2/proto3 access
entry(loc(3, 5, 3, 9), m2, selAssign, directFieldAccess("S", "*string", "*string")),
entry(loc(4, 1, 4, 5), m2, selAssign, directFieldAccess("S", "*string", "*string")),
entry(loc(5, 5, 5, 9), m3, selAssign, directFieldAccess("S", "string", "string")),
entry(loc(6, 1, 6, 5), m3, selAssign, directFieldAccess("S", "string", "string")),
// repeated fields
entry(loc(9, 1, 9, 6), m3, expr(&dst.SelectorExpr{}, &dst.IndexExpr{}), directFieldAccess("Ms", "[]*M3", "[]*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")),
entry(loc(10, 1, 10, 6), m3, selAssign, directFieldAccess("Ms", "[]*M3", "[]*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")),
entry(loc(10, 16, 10, 21), m3, expr(&dst.SelectorExpr{}, &dst.CallExpr{}), directFieldAccess("Ms", "[]*M3", "[]*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")),
// multiple selector expressions
entry(loc(13, 1, 13, 9), m3, selAssign, directFieldAccess("S", "string", "string")),
entry(loc(13, 1, 13, 7), m3, expr(&dst.SelectorExpr{}, &dst.SelectorExpr{}), directFieldAccess("M", "*M3", "*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")),
entry(loc(13, 1, 13, 5), m3, expr(&dst.SelectorExpr{}, &dst.SelectorExpr{}), directFieldAccess("M", "*M3", "*google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3")),
},
},
}, {
desc: "conversion in call expression",
extra: extra + `
func retProto() *pb2.M2 { return nil }
func protoIn(*pb2.M2) { }
func protoIn2(*pb2.M2, *pb2.M2) { }
func efaceIn(interface{}) { }
func efaceIn2(a,b interface{}) { }
func efaceVararg(format string, args ...interface{}) { }
func msgVararg(format string, args ...proto.Message) { }
type T struct{}
func (T) Method(interface{}) {}
`,
in: `
protoIn(m2) // ignored: no conversion
protoIn(&pb2.M2{}) // ignored: no conversion
efaceIn(notAProto) // ignored: not a proto
efaceIn(m2)
efaceIn2(m2, &pb2.M2{})
proto.Clone(m2)
proto.Marshal(m2)
proto.Clone(proto.Message(m2))
proto.Marshal(proto.Message(m2))
efaceVararg("", m2, m2)
msgVararg("", m2)
T{}.Method(m2)
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(3, 10, 3, 18), m2Val, cLitUnary, emptyLiteral),
entry(loc(7, 1, 7, 12), m2, callStmt, callConv("interface{}", "efaceIn", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(8, 1, 8, 24), m2, callStmt, callConv("interface{}", "efaceIn2", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(a interface{}, b interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(8, 1, 8, 24), m2, callStmt, callConv("interface{}", "efaceIn2", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(a interface{}, b interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(8, 15, 8, 23), m2Val, cLitUnary, emptyLiteral),
entry(loc(10, 1, 10, 16), m2, callStmt, callConv(protoMsg, "Clone", "google.golang.org/protobuf/proto", "func(m "+protoMsg+") "+protoMsg, spb.Conversion_CALL_ARGUMENT)),
entry(loc(11, 1, 11, 18), m2, callStmt, callConv(protoMsg, "Marshal", "google.golang.org/protobuf/proto", "func(m "+protoMsg+") ([]byte, error)", spb.Conversion_CALL_ARGUMENT)),
entry(loc(13, 13, 13, 30), m2, callCall, callConv(protoMsg, "Message", "google.golang.org/protobuf/proto", protoMsg, spb.Conversion_EXPLICIT)),
entry(loc(14, 15, 14, 32), m2, callCall, callConv(protoMsg, "Message", "google.golang.org/protobuf/proto", protoMsg, spb.Conversion_EXPLICIT)),
entry(loc(16, 1, 16, 24), m2, callStmt, callConv("interface{}", "efaceVararg", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(format string, args ...interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(16, 1, 16, 24), m2, callStmt, callConv("interface{}", "efaceVararg", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(format string, args ...interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(17, 1, 17, 18), m2, callStmt, callConv(protoMsg, "msgVararg", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(format string, args ..."+protoMsg+")", spb.Conversion_CALL_ARGUMENT)),
entry(loc(19, 1, 19, 15), m2, callStmt, callConv("interface{}", "Method", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
},
},
}, {
desc: "conversion in assignment",
extra: `
func multival() (interface{}, *pb2.M2, int, *pb2.M2) {
return nil, nil, 0, nil
}
`,
in: `
x := m2 // ignored: no conversion
var mm *pb2.M2 = m2 // ignored: no conversion
var in interface{}
in = m2
var min proto.Message
min = m3
var n int
n, in = 1, m2
in, n2 := m2, 0
in, in = m2, m3
var m map[string]*pb2.M2
in, ok := m[""]
in, _ = m[""]
in, in, n, in = multival() // eface->eface, *pb.M->eface, int->int, *pb.M->eface
_, _, _, _, _, _, _ = mm, n, n2, ok, x, in, min
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(6, 1, 6, 8), m2, assignStmt, assignConv("interface{}")), // in = m2
entry(loc(9, 1, 9, 9), m3, assignStmt, assignConv(protoMsg)), // min = m3
entry(loc(12, 1, 12, 14), m2, assignStmt, assignConv("interface{}")), // n, in = 1, m2
entry(loc(13, 1, 13, 16), m2, assignStmt, assignConv("interface{}")), // in, n2 := m2, 0
entry(loc(14, 1, 14, 16), m2, assignStmt, assignConv("interface{}")), // in, in = m2, m3
entry(loc(14, 1, 14, 16), m3, assignStmt, assignConv("interface{}")), // in, in = m2, m3
entry(loc(17, 1, 17, 16), m2, assignStmt, assignConv("interface{}")), // in, ok := m[""]
entry(loc(18, 1, 18, 14), m2, assignStmt, assignConv("interface{}")), // in, _ = m[""]
entry(loc(20, 1, 20, 27), m2, assignStmt, assignConv("interface{}")), // in, in, n, in = multival() : second arg
entry(loc(20, 1, 20, 27), m2, assignStmt, assignConv("interface{}")), // in, in, n, in = multival() : last arg
},
},
}, {
desc: "conversion in construction",
in: `
type t struct {
eface interface{}
msg proto.Message
m *pb2.M2
}
_ = t{
eface: m2,
msg: m3,
m: m2, // ignore: no conversion
}
_ = &t{
eface: m2,
msg: m3,
m: m2, // ignore: no conversion
}
_ = &t{m: m2} // ignore: no conversions
_ = &t{eface: m2}
_ = []struct{m interface{}}{{m: m2}}
_ = []struct{m *pb2.M2}{{m: m2}} // ignore: no conversion
_ = map[int]interface{}{0: m2}
_ = map[interface{}]int{m2: 0}
_ = map[interface{}]interface{}{m2: m2}
_ = [...]interface{}{0:m2}
_ = []interface{}{0:m2}
_ = &t{m2,m2,m2} // 2 findings + 1 ignored (no conversion)
_ = []interface{}{m2}
_ = []struct{m interface{}}{{m2}}
_ = []*t{{m2, m2, m2}}
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(9, 3, 9, 12), m2, kvCLit, elemConv("interface{}")),
entry(loc(10, 3, 10, 10), m3, kvCLit, elemConv(protoMsg)),
entry(loc(15, 3, 15, 12), m2, kvCLit, elemConv("interface{}")),
entry(loc(16, 3, 16, 10), m3, kvCLit, elemConv(protoMsg)),
entry(loc(21, 8, 21, 17), m2, kvCLit, elemConv("interface{}")),
entry(loc(23, 30, 23, 35), m2, kvCLit, elemConv("interface{}")),
entry(loc(26, 25, 26, 30), m2, kvCLit, elemConv("interface{}")),
entry(loc(27, 25, 27, 30), m2, kvCLit, elemConv("interface{}")),
entry(loc(28, 33, 28, 39), m2, kvCLit, elemConv("interface{}")),
entry(loc(28, 33, 28, 39), m2, kvCLit, elemConv("interface{}")),
entry(loc(30, 22, 30, 26), m2, kvCLit, elemConv("interface{}")),
entry(loc(31, 19, 31, 23), m2, kvCLit, elemConv("interface{}")),
entry(loc(33, 8, 33, 10), m2, identCLit, elemConv("interface{}")),
entry(loc(33, 11, 33, 13), m2, identCLit, elemConv(protoMsg)),
entry(loc(34, 19, 34, 21), m2, identCLit, elemConv("interface{}")),
entry(loc(35, 30, 35, 32), m2, identCLit, elemConv("interface{}")),
entry(loc(37, 11, 37, 13), m2, identCLit, elemConv("interface{}")),
entry(loc(37, 15, 37, 17), m2, identCLit, elemConv(protoMsg)),
},
},
}, {
desc: "conversions to unsafe.Pointer",
extra: `
func f(unsafe.Pointer) {}
func g(*pb2.M2) unsafe.Pointer{ return nil }
`,
in: `
_ = unsafe.Pointer(m2)
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(2, 5, 2, 23), m2, expr(&dst.CallExpr{}, &dst.AssignStmt{}), &spb.Conversion{
DestTypeName: "unsafe.Pointer",
Context: spb.Conversion_EXPLICIT,
}),
},
},
}, {
desc: "composite types: contexts",
extra: `
type s struct {m *pb2.M2}
func f(interface{}) {}
func g() (int, uintptr, unsafe.Pointer) {
for {
return 0, 0, unsafe.Pointer(&pb2.M2{})
}
return 0, 0, nil
}
`,
in: `
// Various contexts:
f(&s{m: m2}) // conversion in call arg
var in interface{}
in = &s{m2} // conversion in assignment
in = struct{s *s}{s: &s{m2}}
_ = struct{s interface{}}{s: m2}
_ = struct{s interface{}}{m2}
f(unsafe.Pointer(&m2))
in = <-make(chan *pb2.M2) // assignment from *pb.M to interface{} in assignment
make(chan interface{}) <- m2
_ = func() interface{} {
if true {
return m2
}
return nil
}()
type namedChan chan interface{}
make(namedChan) <- m2
_ = in
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(3, 1, 3, 13), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(5, 1, 5, 12), m2, assignStmt, assignConv("interface{}")),
entry(loc(6, 1, 6, 29), m2, assignStmt, assignConv("interface{}")),
entry(loc(7, 27, 7, 32), m2, kvCLit, elemConv("interface{}")),
entry(loc(8, 27, 8, 29), m2, expr(&dst.Ident{}, &dst.CompositeLit{}), elemConv("interface{}")),
entry(loc(9, 3, 9, 22), m2, expr(&dst.CallExpr{}, &dst.CallExpr{}), &spb.Conversion{
DestTypeName: "unsafe.Pointer",
Context: spb.Conversion_EXPLICIT,
}),
entry(loc(10, 1, 10, 26), m2, assignStmt, assignConv("interface{}")),
entry(loc(11, 1, 11, 29), m2, expr(&dst.SendStmt{}, &dst.BlockStmt{}), &spb.Conversion{
DestTypeName: "interface{}",
Context: spb.Conversion_CHAN_SEND,
}),
entry(loc(14, 3, 14, 12), m2, retStmt, &spb.Conversion{
DestTypeName: "interface{}",
Context: spb.Conversion_FUNC_RET,
}),
entry(loc(20, 1, 20, 22), m2, expr(&dst.SendStmt{}, &dst.BlockStmt{}), &spb.Conversion{
DestTypeName: "interface{}",
Context: spb.Conversion_CHAN_SEND,
}),
// This is for function "g" which is defined in "extra" (outside "in") and hence the line number is out of range.
entry(loc(31, 16, 31, 41), m2, expr(&dst.CallExpr{}, &dst.ReturnStmt{}), &spb.Conversion{
DestTypeName: "unsafe.Pointer",
Context: spb.Conversion_EXPLICIT,
}),
entry(loc(31, 32, 31, 40), m2Val, cLitUnary, emptyLiteral),
},
},
}, {
desc: "composite types: types",
extra: `
func f(interface{}) {}
func g(_, _ interface{}) {}
`,
in: `
f(&m2)
f([]*pb2.M2{})
f([1]*pb2.M2{{}})
f(map[int]*pb2.M2{})
f(map[*pb2.M2]int{})
g(func() (_,_ *pb2.M2) { return }()) // generates two entries
f(make(chan *pb2.M2)) // ignored: no proto value provided to reflection, only type
f(func(*pb2.M2){}) // ignored: no proto value provided to reflection, only type
f(func(a,b,c int, m *pb2.M2){}) // ignored: no proto value provided to reflection, only type
type msg pb2.M2
f(&msg{})
f(struct{m *pb2.M2}{m: m2})
_ = make(map[int]bool, len([]int{})) // make sure that builtins don't mess things up with variadic functions
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(2, 1, 2, 7), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(3, 1, 3, 15), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(4, 1, 4, 18), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(4, 14, 4, 16), m2, cLitCLit, emptyLiteral),
entry(loc(5, 1, 5, 21), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(6, 1, 6, 21), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(7, 1, 7, 37), m2, callStmt, callConv("interface{}", "g", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(_ interface{}, _ interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(7, 1, 7, 37), m2, callStmt, callConv("interface{}", "g", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(_ interface{}, _ interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(12, 6, 12, 16), m2Val, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg")}),
entry(loc(13, 1, 13, 10), statsutil.ShortAndLongNameFrom("*google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
entry(loc(13, 4, 13, 9), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), cLitUnary, emptyLiteral),
entry(loc(15, 1, 15, 28), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
},
},
}, {
desc: "composite types: constructors",
in: `
m2 = &pb2.M2{S: proto.String("s")}
_ = pb2.M2{
S: proto.String("s"),
B: proto.Bool(true),
}
_ = pb2.M2_builder{
S: proto.String("builder"),
}.Build()
m3s := []*pb3.M3{
{S: "pointer"},
{},
pb3.M3_builder{}.Build(),
}
m3s = append(m3s, &pb3.M3{})
_ = []pb3.M3{
{S: "shallow"},
{},
}
type NotMsg struct{ M *pb2.M2 }
_ = &NotMsg{ M: &pb2.M2{} }
_ = NotMsg{}
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(2, 7, 2, 35), m2Val, cLitUnary, nonEmptyLiteral),
entry(loc(3, 1, 6, 2), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
entry(loc(3, 5, 6, 2), m2Val, expr(&dst.CompositeLit{}, &dst.AssignStmt{}), nonEmptyLiteral),
entry(loc(7, 5, 9, 2), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/proto2test_go_proto.M2_builder"), expr(&dst.CompositeLit{}, &dst.SelectorExpr{}), builderLiteral),
entry(loc(11, 2, 11, 16), m3, cLitCLit, nonEmptyLiteral),
entry(loc(12, 2, 12, 4), m3, cLitCLit, emptyLiteral),
entry(loc(13, 2, 13, 18), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/proto3test_go_proto.M3_builder"), expr(&dst.CompositeLit{}, &dst.SelectorExpr{}), builderLiteral),
entry(loc(15, 20, 15, 28), m3Val, cLitUnary, emptyLiteral),
entry(loc(18, 2, 18, 16), m3Val, cLitCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
entry(loc(19, 2, 19, 4), m3Val, cLitCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
entry(loc(18, 2, 18, 16), m3Val, cLitCLit, nonEmptyLiteral),
entry(loc(19, 2, 19, 4), m3Val, cLitCLit, emptyLiteral),
entry(loc(23, 18, 23, 26), m2Val, cLitUnary, emptyLiteral),
},
},
}, {
desc: "type assertions",
extra: `type NotAProto struct {
S *string
Field struct{}
}`,
in: `
var in interface{}
_ = in.(*pb3.M3)
_ = in.(*NotAProto)
switch in.(type) {}
switch n := in.(type) { case int: _ = n }
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(3, 5, 3, 17), m3, expr(&dst.TypeAssertExpr{}, &dst.AssignStmt{}), &spb.TypeAssertion{
SrcType: &spb.Type{
ShortName: "interface{}",
LongName: "interface{}",
},
}),
},
},
}, {
desc: "type defs",
extra: `type NotAProto struct {
S *string
Field struct{}
}`,
in: `
type alias = *pb3.M3 // ignored
type notProto NotAProto // ignored
type myProto pb2.M2
type myProtoPtr *pb3.M3
type myProtoPtr2 myProtoPtr
type myProtoPtr3 *myProtoPtr // ignored: pointer to pointer
// Composite types are not interesting to us.
type ignored1 struct { _ *pb2.M2 }
type ignored2 func(*pb3.M3)
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(4, 6, 4, 20), m2Val, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.myProto")}),
entry(loc(5, 6, 5, 24), m3, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.myProtoPtr")}),
entry(loc(6, 6, 6, 28), m3, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.myProtoPtr2")}),
},
},
}, {
desc: "type embedding",
extra: `type NotAProto struct {
S *string
Field struct{}
}`,
in: `
type ignored struct {
NotAProto
Named *pb3.M3
_ *pb3.M3
}
type T struct {
n int
*pb3.M3
}
type U struct {
_, _ int
_ int
pb3.M3
}
type V struct {
_, _, _ int
T // ignored
}
type W struct {
_, _, _ int
_ func(*pb3.M3) // ignored
}
type named pb3.M3
type X struct {
named
}
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(10, 2, 10, 9), m3, expr(&dst.StructType{}, &dst.TypeSpec{}), &spb.Embedding{FieldIndex: 1}),
entry(loc(16, 2, 16, 8), m3Val, expr(&dst.StructType{}, &dst.TypeSpec{}), &spb.Embedding{FieldIndex: 3}),
entry(loc(29, 6, 29, 18), m3Val, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.named")}),
entry(loc(31, 2, 31, 7), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.named"), expr(&dst.StructType{}, &dst.TypeSpec{}), &spb.Embedding{FieldIndex: 0}),
},
},
}, {
desc: "recursive type",
extra: "func f(interface{}){}",
in: `
type S struct {
S *S
M *pb2.M2
}
f(&S{})
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(6, 1, 6, 8), m2, callStmt, callConv("interface{}", "f", "google.golang.org/open2opaque/internal/fix/testdata/fake", "func(interface{})", spb.Conversion_CALL_ARGUMENT)),
},
},
}, {
desc: "mismatched number of return values",
extra: `func f() (*pb2.M2, *pb2.M2) { return nil, nil }`,
in: `
_ = func() (m interface{}) {
m = &pb2.M2{}
return
}
_ = func() (a,b interface{}) {
return f()
}
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(3, 2, 3, 15), m2, assignStmt, assignConv("interface{}")),
entry(loc(3, 7, 3, 15), m2Val, cLitUnary, emptyLiteral),
entry(loc(7, 2, 7, 12), m2, retStmt, &spb.Conversion{
DestTypeName: "interface{}",
Context: spb.Conversion_FUNC_RET,
}),
entry(loc(7, 2, 7, 12), m2, retStmt, &spb.Conversion{
DestTypeName: "interface{}",
Context: spb.Conversion_FUNC_RET,
}),
},
},
}, {
// Copy a proto message by value. This test doesn't check for copying a
// composite type that results in a proto shallow copy).
desc: "direct shallow copies",
extra: `
func args(int, pb2.M2) {}
func ret() (_ int, _ pb2.M2) { return } // naked return so that we don't trigger analysis
`,
in: `
copy := *m2 // 0: assign-shallow-copy
copy = *m2 // 1: assign-shallow-copy
var n int
n, copy = 0, *m2 // 2: assign-shallow-copy
_,_ = copy,n // 3: assign-shallow-copy
args(0, *m2) // 4: call-argument-shallow-copy
args(ret()) // 5: call-argument-shallow-copy
func() (int, pb2.M2) {
return 0, *m2 // 6: call-argument-return-shallow-copy
}()
func() (int, pb2.M2) {
return ret() // 7: call-argument-return-shallow-copy
}()
(*m2).GetS() // ignored: non-pointer receiver is fine
m := map[string]pb2.M2{
"": *m2, // 8: composite-literal-shallow-copy
}
copy, _ = m[""] // 9: assign-shallow-copy
s := &struct {
m pb2.M2
} {
m: *m2, // 11: composite-literal-shallow-copy
}
_ = s
ch := make(chan pb2.M2)
ch <- *m2 // 12: chan-send-shallow-copy
var in interface{} = *m2
_ = in
_ = []*struct{m pb2.M2}{
{*m2}, // 14: composite-literal-shallow-copy
{m: *m2}, // 16: composite-literal-shallow-copy
}
_ = []struct{m pb2.M2}{
{*m2}, // 17,18: composite-literal-shallow-copy of the entire struct '{*m2}' and of the message itself '*m2'
{m: *m2}, // 20,22: composite-literal-shallow-copy of the entire struct '{m: *m2}' and of the message itself '*m2'
}
_ = []pb2.M2{*m2} // 23: composite-literal-shallow-copy of the element '*m2'
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(2, 1, 2, 12), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
entry(loc(3, 1, 3, 11), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
entry(loc(5, 1, 5, 17), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
entry(loc(6, 1, 6, 13), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
entry(loc(8, 1, 8, 13), m2Val, callStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_CALL_ARGUMENT}),
entry(loc(10, 1, 10, 12), m2Val, callStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_CALL_ARGUMENT}),
entry(loc(13, 2, 13, 15), m2Val, retStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_FUNC_RET}),
entry(loc(17, 2, 17, 14), m2Val, retStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_FUNC_RET}),
entry(loc(23, 2, 23, 9), m2Val, kvCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
entry(loc(25, 1, 25, 16), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
entry(loc(30, 2, 30, 8), m2Val, kvCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
entry(loc(35, 1, 35, 10), m2Val, expr(&dst.SendStmt{}, &dst.BlockStmt{}), &spb.ShallowCopy{Type: spb.ShallowCopy_CHAN_SEND}),
entry(loc(41, 3, 41, 6), m2Val, starCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
entry(loc(42, 3, 42, 9), m2Val, kvCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
entry(loc(45, 2, 45, 7), m2Val, cLitCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
entry(loc(46, 2, 46, 10), m2Val, cLitCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
entry(loc(45, 3, 45, 6), m2Val, starCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
entry(loc(46, 3, 46, 9), m2Val, kvCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
entry(loc(48, 14, 48, 17), m2Val, starCLit, &spb.ShallowCopy{Type: spb.ShallowCopy_COMPOSITE_LITERAL_ELEMENT}),
},
},
}, {
// Copy a container with a proto struct.
desc: "indirect shallow copies",
extra: `
type msg pb2.M2
type S struct{m msg}
func args(_, _ S){}
func argsp(_, _ *S){}`,
in: `
s := S{} // 0: shallow copy in definition
_ = s // 2: shallow copy in assignment
args(func() (_, _ S) { return }()) // 3,4: shallow via a tuple (twice because there are two values in the tuple)
_ = [1]pb2.M2{} // 5: copy an array
// Those are OK because of the indirection
sp := &S{}
_ = sp
argsp(func() (_, _ *S) { return }())
_ = [1]*pb2.M2{}
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(2, 1, 2, 9), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
entry(loc(3, 1, 3, 6), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
entry(loc(4, 1, 4, 35), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), callStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_CALL_ARGUMENT}),
entry(loc(4, 1, 4, 35), statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg"), callStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_CALL_ARGUMENT}),
entry(loc(5, 1, 5, 16), m2Val, assignStmt, &spb.ShallowCopy{Type: spb.ShallowCopy_ASSIGN}),
// "type S struct{m msg}" definition:
entry(loc(17, 6, 17, 16), m2Val, expr(&dst.TypeSpec{}, &dst.GenDecl{}), &spb.TypeDefinition{NewType: statsutil.ShortAndLongNameFrom("google.golang.org/open2opaque/internal/fix/testdata/fake.msg")}),
},
},
}, {
desc: "type information missing",
in: `
_ = siloedpb.Message{}
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
typeMissing(loc(2, 1, 2, 23), assignStmt),
typeMissing(loc(2, 1, 2, 23), assignStmt),
typeMissing(loc(2, 5, 2, 23), expr(&dst.CompositeLit{}, &dst.AssignStmt{})),
typeMissing(loc(2, 5, 2, 21), expr(&dst.SelectorExpr{}, &dst.CompositeLit{})),
typeMissing(loc(4, 2, 4, 27), assignStmt),
typeMissing(loc(4, 2, 4, 27), assignStmt),
},
},
}, {
// The method GetFoo for a oneof field foo only exists in the OPEN API.
desc: "GetOneof",
in: `
switch x := m2.GetOneofField().(type) {
case *pb2.M2_StringOneof:
_ = x
default:
}
_ = m3.GetOneofField()
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(2, 13, 2, 31), m2, expr(&dst.CallExpr{}, &dst.TypeAssertExpr{}), &spb.MethodCall{Method: "GetOneofField", Type: spb.MethodCall_GET_ONEOF}),
entry(loc(7, 5, 7, 23), m3, expr(&dst.CallExpr{}, &dst.AssignStmt{}), &spb.MethodCall{Method: "GetOneofField", Type: spb.MethodCall_GET_ONEOF}),
},
},
}, {
desc: "GetBuild",
in: `
_ = m2.GetBuild()
`,
wantStats: map[Level][]*spb.Entry{
None: []*spb.Entry{
entry(loc(2, 5, 2, 18), m2, expr(&dst.CallExpr{}, &dst.AssignStmt{}), &spb.MethodCall{Method: "GetBuild", Type: spb.MethodCall_GET_BUILD}),
},
},
}}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if tt.skip != "" {
t.Skip(tt.skip)
}
in := NewSrc(tt.in, tt.extra)
statsOnly := []Level{}
_, got, err := fixSource(context.Background(), in, "pkg_test.go", ConfiguredPackage{}, statsOnly)
if err != nil {
t.Fatalf("fixSources(%q) failed: %v; Full input:\n%s", tt.in, err, in)
}
for _, lvl := range []Level{None} {
want, ok := tt.wantStats[lvl]
if !ok {
continue
}
if len(want) != len(got[lvl]) {
t.Errorf("len(want)=%d != len(got[lvl])=%d", len(want), len(got[lvl]))
}
for idx := range want {
if diff := cmp.Diff(want[idx], got[lvl][idx], protocmp.Transform()); diff != "" {
// We don't print got/want because it's a lot of output and the diff is almost always enough.
t.Errorf("[%s] fixSources(level=%s, message %d) = diff:\n%s", tt.desc, lvl, idx, diff)
}
}
}
})
}
}
func TestSliceLiteral(t *testing.T) {
const src = `
for _, _ = range map[*[]*pb2.M2]string{
{}: "UNKNOWN_NOTIFICATION_TYPE",
{
{
S: nil,
},
}: "UNKNOWN_NOTIFICATION_TYPE",
} {
panic("irrelevant")
}
`
in := NewSrc(src, "")
_, got, err := fixSource(context.Background(), in, "pkg_test.go", ConfiguredPackage{}, []Level{Green, Yellow, Red})
if err != nil {
t.Fatalf("fixSources(%q) failed: %v; Full input:\n%s", src, err, in)
}
t.Logf("got: %v", got)
}