blob: 85c599443b79a4cb33ee64f5641636f1eba8106d [file] [log] [blame]
// Copyright 2022 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 ssa_test
import (
"bytes"
"fmt"
"go/parser"
"go/token"
"reflect"
"sort"
"testing"
"golang.org/x/tools/go/expect"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa"
)
// TestGenericBodies tests that bodies of generic functions and methods containing
// different constructs can be built in BuilderMode(0).
//
// Each test specifies the contents of package containing a single go file.
// Each call print(arg0, arg1, ...) to the builtin print function
// in ssa is correlated a comment at the end of the line of the form:
//
// //@ types(a, b, c)
//
// where a, b and c are the types of the arguments to the print call
// serialized using go/types.Type.String().
// See x/tools/go/expect for details on the syntax.
func TestGenericBodies(t *testing.T) {
for _, contents := range []string{
`
package p00
func f(x int) {
var i interface{}
print(i, 0) //@ types("interface{}", int)
print() //@ types()
print(x) //@ types(int)
}
`,
`
package p01
func f[T any](x T) {
print(x) //@ types(T)
}
`,
`
package p02
func f[T ~int]() {
var x T
print(x) //@ types(T)
}
`,
`
package p03
func a[T ~[4]byte](x T) {
for k, v := range x {
print(x, k, v) //@ types(T, int, byte)
}
}
func b[T ~*[4]byte](x T) {
for k, v := range x {
print(x, k, v) //@ types(T, int, byte)
}
}
func c[T ~[]byte](x T) {
for k, v := range x {
print(x, k, v) //@ types(T, int, byte)
}
}
func d[T ~string](x T) {
for k, v := range x {
print(x, k, v) //@ types(T, int, rune)
}
}
func e[T ~map[int]string](x T) {
for k, v := range x {
print(x, k, v) //@ types(T, int, string)
}
}
func f[T ~chan string](x T) {
for v := range x {
print(x, v) //@ types(T, string)
}
}
func From() {
type A [4]byte
print(a[A]) //@ types("func(x p03.A)")
type B *[4]byte
print(b[B]) //@ types("func(x p03.B)")
type C []byte
print(c[C]) //@ types("func(x p03.C)")
type D string
print(d[D]) //@ types("func(x p03.D)")
type E map[int]string
print(e[E]) //@ types("func(x p03.E)")
type F chan string
print(f[F]) //@ types("func(x p03.F)")
}
`,
`
package p05
func f[S any, T ~chan S](x T) {
for v := range x {
print(x, v) //@ types(T, S)
}
}
func From() {
type F chan string
print(f[string, F]) //@ types("func(x p05.F)")
}
`,
`
package p06
func fibonacci[T ~chan int](c, quit T) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
print(c, quit, x, y) //@ types(T, T, int, int)
return
}
}
}
func start[T ~chan int](c, quit T) {
go func() {
for i := 0; i < 10; i++ {
print(<-c) //@ types(int)
}
quit <- 0
}()
}
func From() {
type F chan int
c := make(F)
quit := make(F)
print(start[F], c, quit) //@ types("func(c p06.F, quit p06.F)", "p06.F", "p06.F")
print(fibonacci[F], c, quit) //@ types("func(c p06.F, quit p06.F)", "p06.F", "p06.F")
}
`,
`
package p07
func f[T ~struct{ x int; y string }](i int) T {
u := []T{ T{0, "lorem"}, T{1, "ipsum"}}
return u[i]
}
func From() {
type S struct{ x int; y string }
print(f[S]) //@ types("func(i int) p07.S")
}
`,
`
package p08
func f[T ~[4]int8](x T, l, h int) []int8 {
return x[l:h]
}
func g[T ~*[4]int16](x T, l, h int) []int16 {
return x[l:h]
}
func h[T ~[]int32](x T, l, h int) T {
return x[l:h]
}
func From() {
type F [4]int8
type G *[4]int16
type H []int32
print(f[F](F{}, 0, 0)) //@ types("[]int8")
print(g[G](nil, 0, 0)) //@ types("[]int16")
print(h[H](nil, 0, 0)) //@ types("p08.H")
}
`,
`
package p09
func h[E any, T ~[]E](x T, l, h int) []E {
s := x[l:h]
print(s) //@ types("T")
return s
}
func From() {
type H []int32
print(h[int32, H](nil, 0, 0)) //@ types("[]int32")
}
`,
`
package p10
// Test "make" builtin with different forms on core types and
// when capacities are constants or variable.
func h[E any, T ~[]E](m, n int) {
print(make(T, 3)) //@ types(T)
print(make(T, 3, 5)) //@ types(T)
print(make(T, m)) //@ types(T)
print(make(T, m, n)) //@ types(T)
}
func i[K comparable, E any, T ~map[K]E](m int) {
print(make(T)) //@ types(T)
print(make(T, 5)) //@ types(T)
print(make(T, m)) //@ types(T)
}
func j[E any, T ~chan E](m int) {
print(make(T)) //@ types(T)
print(make(T, 6)) //@ types(T)
print(make(T, m)) //@ types(T)
}
func From() {
type H []int32
h[int32, H](3, 4)
type I map[int8]H
i[int8, H, I](5)
type J chan I
j[I, J](6)
}
`,
`
package p11
func h[T ~[4]int](x T) {
print(len(x), cap(x)) //@ types(int, int)
}
func i[T ~[4]byte | []int | ~chan uint8](x T) {
print(len(x), cap(x)) //@ types(int, int)
}
func j[T ~[4]int | any | map[string]int]() {
print(new(T)) //@ types("*T")
}
func k[T ~[4]int | any | map[string]int](x T) {
print(x) //@ types(T)
panic(x)
}
`,
`
package p12
func f[E any, F ~func() E](x F) {
print(x, x()) //@ types(F, E)
}
func From() {
type T func() int
f[int, T](func() int { return 0 })
f[int, func() int](func() int { return 1 })
}
`,
`
package p13
func f[E any, M ~map[string]E](m M) {
y, ok := m["lorem"]
print(m, y, ok) //@ types(M, E, bool)
}
func From() {
type O map[string][]int
f(O{"lorem": []int{0, 1, 2, 3}})
}
`,
`
package p14
func a[T interface{ []int64 | [5]int64 }](x T) int64 {
print(x, x[2], x[3]) //@ types(T, int64, int64)
x[2] = 5
return x[3]
}
func b[T interface{ []byte | string }](x T) byte {
print(x, x[3]) //@ types(T, byte)
return x[3]
}
func c[T interface{ []byte }](x T) byte {
print(x, x[2], x[3]) //@ types(T, byte, byte)
x[2] = 'b'
return x[3]
}
func d[T interface{ map[int]int64 }](x T) int64 {
print(x, x[2], x[3]) //@ types(T, int64, int64)
x[2] = 43
return x[3]
}
func e[T ~string](t T) {
print(t, t[0]) //@ types(T, uint8)
}
func f[T ~string|[]byte](t T) {
print(t, t[0]) //@ types(T, uint8)
}
func g[T []byte](t T) {
print(t, t[0]) //@ types(T, byte)
}
func h[T ~[4]int|[]int](t T) {
print(t, t[0]) //@ types(T, int)
}
func i[T ~[4]int|*[4]int|[]int](t T) {
print(t, t[0]) //@ types(T, int)
}
func j[T ~[4]int|*[4]int|[]int](t T) {
print(t, &t[0]) //@ types(T, "*int")
}
`,
`
package p15
type MyInt int
type Other int
type MyInterface interface{ foo() }
// ChangeType tests
func ct0(x int) { v := MyInt(x); print(x, v) /*@ types(int, "p15.MyInt")*/ }
func ct1[T MyInt | Other, S int ](x S) { v := T(x); print(x, v) /*@ types(S, T)*/ }
func ct2[T int, S MyInt | int ](x S) { v := T(x); print(x, v) /*@ types(S, T)*/ }
func ct3[T MyInt | Other, S MyInt | int ](x S) { v := T(x) ; print(x, v) /*@ types(S, T)*/ }
// Convert tests
func co0[T int | int8](x MyInt) { v := T(x); print(x, v) /*@ types("p15.MyInt", T)*/}
func co1[T int | int8](x T) { v := MyInt(x); print(x, v) /*@ types(T, "p15.MyInt")*/ }
func co2[S, T int | int8](x T) { v := S(x); print(x, v) /*@ types(T, S)*/ }
// MakeInterface tests
func mi0[T MyInterface](x T) { v := MyInterface(x); print(x, v) /*@ types(T, "p15.MyInterface")*/ }
// NewConst tests
func nc0[T any]() { v := (*T)(nil); print(v) /*@ types("*T")*/}
// SliceToArrayPointer
func sl0[T *[4]int | *[2]int](x []int) { v := T(x); print(x, v) /*@ types("[]int", T)*/ }
func sl1[T *[4]int | *[2]int, S []int](x S) { v := T(x); print(x, v) /*@ types(S, T)*/ }
`,
`
package p16
func c[T interface{ foo() string }](x T) {
print(x, x.foo, x.foo()) /*@ types(T, "func() string", string)*/
}
`,
`
package p17
func eq[T comparable](t T, i interface{}) bool {
return t == i
}
`,
// TODO(59983): investigate why writing g.c panics in (*FieldAddr).String.
`
package p18
type S struct{ f int }
func c[P *S]() []P { return []P{{f: 1}} }
`,
`
package p19
func sign[bytes []byte | string](s bytes) (bool, bool) {
neg := false
if len(s) > 0 && (s[0] == '-' || s[0] == '+') {
neg = s[0] == '-'
s = s[1:]
}
return !neg, len(s) > 0
}
`,
`package p20
func digits[bytes []byte | string](s bytes) bool {
for _, c := range []byte(s) {
if c < '0' || '9' < c {
return false
}
}
return true
}
`,
`
package p21
type E interface{}
func Foo[T E, PT interface{ *T }]() T {
pt := PT(new(T))
x := *pt
print(x) /*@ types(T)*/
return x
}
`,
`
package p22
func f[M any, PM *M](p PM) {
var m M
*p = m
print(m) /*@ types(M)*/
print(p) /*@ types(PM)*/
}
`,
`
package p23
type A struct{int}
func (*A) Marker() {}
type B struct{string}
func (*B) Marker() {}
type C struct{float32}
func (*C) Marker() {}
func process[T interface {
*A
*B
*C
Marker()
}](v T) {
v.Marker()
a := *(any(v).(*A)); print(a) /*@ types("p23.A")*/
b := *(any(v).(*B)); print(b) /*@ types("p23.B")*/
c := *(any(v).(*C)); print(c) /*@ types("p23.C")*/
}
`,
`
package p24
func a[T any](f func() [4]T) {
x := len(f())
print(x) /*@ types("int")*/
}
func b[T [4]any](f func() T) {
x := len(f())
print(x) /*@ types("int")*/
}
func c[T any](f func() *[4]T) {
x := len(f())
print(x) /*@ types("int")*/
}
func d[T *[4]any](f func() T) {
x := len(f())
print(x) /*@ types("int")*/
}
`,
`
package p25
func a[T any]() {
var f func() [4]T
for i, v := range f() {
print(i, v) /*@ types("int", "T")*/
}
}
func b[T [4]any](f func() T) {
for i, v := range f() {
print(i, v) /*@ types("int", "any")*/
}
}
func c[T any](f func() *[4]T) {
for i, v := range f() {
print(i, v) /*@ types("int", "T")*/
}
}
func d[T *[4]any](f func() T) {
for i, v := range f() {
print(i, v) /*@ types("int", "any")*/
}
}
`,
`
package issue64324
type bar[T any] interface {
Bar(int) T
}
type foo[T any] interface {
bar[[]T]
*T
}
func Foo[T any, F foo[T]](d int) {
m := new(T)
f := F(m)
print(f.Bar(d)) /*@ types("[]T")*/
}
`, `
package issue64324b
type bar[T any] interface {
Bar(int) T
}
type baz[T any] interface {
bar[*int]
*int
}
func Baz[I baz[string]](d int) {
m := new(int)
f := I(m)
print(f.Bar(d)) /*@ types("*int")*/
}
`,
} {
contents := contents
pkgname := packageName(t, contents)
t.Run(pkgname, func(t *testing.T) {
// Parse
conf := loader.Config{ParserMode: parser.ParseComments}
f, err := conf.ParseFile("file.go", contents)
if err != nil {
t.Fatalf("parse: %v", err)
}
conf.CreateFromFiles(pkgname, f)
// Load
lprog, err := conf.Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
// Create and build SSA
prog := ssa.NewProgram(lprog.Fset, ssa.SanityCheckFunctions)
for _, info := range lprog.AllPackages {
if info.TransitivelyErrorFree {
prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
}
}
p := prog.Package(lprog.Package(pkgname).Pkg)
p.Build()
// Collect all notes in f, i.e. comments starting with "//@ types".
notes, err := expect.ExtractGo(prog.Fset, f)
if err != nil {
t.Errorf("expect.ExtractGo: %v", err)
}
// Collect calls to the builtin print function.
probes := callsTo(p, "print")
expectations := matchNotes(prog.Fset, notes, probes)
for call := range probes {
if expectations[call] == nil {
t.Errorf("Unmatched call: %v", call)
}
}
// Check each expectation.
for call, note := range expectations {
var args []string
for _, a := range call.Args {
args = append(args, a.Type().String())
}
if got, want := fmt.Sprint(args), fmt.Sprint(note.Args); got != want {
t.Errorf("Arguments to print() were expected to be %q. got %q", want, got)
logFunction(t, probes[call])
}
}
})
}
}
// callsTo finds all calls to an SSA value named fname,
// and returns a map from each call site to its enclosing function.
func callsTo(p *ssa.Package, fname string) map[*ssa.CallCommon]*ssa.Function {
callsites := make(map[*ssa.CallCommon]*ssa.Function)
for _, mem := range p.Members {
if fn, ok := mem.(*ssa.Function); ok {
for _, bb := range fn.Blocks {
for _, i := range bb.Instrs {
if i, ok := i.(ssa.CallInstruction); ok {
call := i.Common()
if call.Value.Name() == fname {
callsites[call] = fn
}
}
}
}
}
}
return callsites
}
// matchNodes returns a mapping from call sites (found by callsTo)
// to the first "//@ note" comment on the same line.
func matchNotes(fset *token.FileSet, notes []*expect.Note, calls map[*ssa.CallCommon]*ssa.Function) map[*ssa.CallCommon]*expect.Note {
// Matches each probe with a note that has the same line.
sameLine := func(x, y token.Pos) bool {
xp := fset.Position(x)
yp := fset.Position(y)
return xp.Filename == yp.Filename && xp.Line == yp.Line
}
expectations := make(map[*ssa.CallCommon]*expect.Note)
for call := range calls {
for _, note := range notes {
if sameLine(call.Pos(), note.Pos) {
expectations[call] = note
break // first match is good enough.
}
}
}
return expectations
}
// TestInstructionString tests serializing instructions via Instruction.String().
func TestInstructionString(t *testing.T) {
// Tests (ssa.Instruction).String(). Instructions are from a single go file.
// The Instructions tested are those that match a comment of the form:
//
// //@ instrs(f, kind, strs...)
//
// where f is the name of the function, kind is the type of the instructions matched
// within the function, and tests that the String() value for all of the instructions
// matched of String() is strs (in some order).
// See x/tools/go/expect for details on the syntax.
const contents = `
package p
//@ instrs("f0", "*ssa.TypeAssert")
//@ instrs("f0", "*ssa.Call", "print(nil:interface{}, 0:int)")
func f0(x int) { // non-generic smoke test.
var i interface{}
print(i, 0)
}
//@ instrs("f1", "*ssa.Alloc", "local T (u)")
//@ instrs("f1", "*ssa.FieldAddr", "&t0.x [#0]")
func f1[T ~struct{ x string }]() T {
u := T{"lorem"}
return u
}
//@ instrs("f1b", "*ssa.Alloc", "new T (complit)")
//@ instrs("f1b", "*ssa.FieldAddr", "&t0.x [#0]")
func f1b[T ~struct{ x string }]() *T {
u := &T{"lorem"}
return u
}
//@ instrs("f2", "*ssa.TypeAssert", "typeassert t0.(interface{})")
//@ instrs("f2", "*ssa.Call", "invoke x.foo()")
func f2[T interface{ foo() string }](x T) {
_ = x.foo
_ = x.foo()
}
//@ instrs("f3", "*ssa.TypeAssert", "typeassert t0.(interface{})")
//@ instrs("f3", "*ssa.Call", "invoke x.foo()")
func f3[T interface{ foo() string; comparable }](x T) {
_ = x.foo
_ = x.foo()
}
//@ instrs("f4", "*ssa.BinOp", "t1 + 1:int", "t2 < 4:int")
//@ instrs("f4", "*ssa.Call", "f()", "print(t2, t4)")
func f4[T [4]string](f func() T) {
for i, v := range f() {
print(i, v)
}
}
//@ instrs("f5", "*ssa.Call", "nil:func()()")
func f5() {
var f func()
f()
}
type S struct{ f int }
//@ instrs("f6", "*ssa.Alloc", "new [1]P (slicelit)", "new S (complit)")
//@ instrs("f6", "*ssa.IndexAddr", "&t0[0:int]")
//@ instrs("f6", "*ssa.FieldAddr", "&t2.f [#0]")
func f6[P *S]() []P { return []P{{f: 1}} }
//@ instrs("f7", "*ssa.Alloc", "local S (complit)")
//@ instrs("f7", "*ssa.FieldAddr", "&t0.f [#0]")
func f7[T any, S struct{f T}](x T) S { return S{f: x} }
//@ instrs("f8", "*ssa.Alloc", "new [1]P (slicelit)", "new struct{f T} (complit)")
//@ instrs("f8", "*ssa.IndexAddr", "&t0[0:int]")
//@ instrs("f8", "*ssa.FieldAddr", "&t2.f [#0]")
func f8[T any, P *struct{f T}](x T) []P { return []P{{f: x}} }
//@ instrs("f9", "*ssa.Alloc", "new [1]PS (slicelit)", "new S (complit)")
//@ instrs("f9", "*ssa.IndexAddr", "&t0[0:int]")
//@ instrs("f9", "*ssa.FieldAddr", "&t2.f [#0]")
func f9[T any, S struct{f T}, PS *S](x T) {
_ = []PS{{f: x}}
}
//@ instrs("f10", "*ssa.FieldAddr", "&t0.x [#0]")
//@ instrs("f10", "*ssa.Store", "*t0 = *new(T):T", "*t1 = 4:int")
func f10[T ~struct{ x, y int }]() T {
var u T
u = T{x: 4}
return u
}
//@ instrs("f11", "*ssa.FieldAddr", "&t1.y [#1]")
//@ instrs("f11", "*ssa.Store", "*t1 = *new(T):T", "*t2 = 5:int")
func f11[T ~struct{ x, y int }, PT *T]() PT {
var u PT = new(T)
*u = T{y: 5}
return u
}
//@ instrs("f12", "*ssa.Alloc", "new struct{f T} (complit)")
//@ instrs("f12", "*ssa.MakeMap", "make map[P]bool 1:int")
func f12[T any, P *struct{f T}](x T) map[P]bool { return map[P]bool{{}: true} }
//@ instrs("f13", "*ssa.IndexAddr", "&v[0:int]")
//@ instrs("f13", "*ssa.Store", "*t0 = 7:int", "*v = *new(A):A")
func f13[A [3]int, PA *A](v PA) {
*v = A{7}
}
//@ instrs("f14", "*ssa.Call", "invoke t1.Set(0:int)")
func f14[T any, PT interface {
Set(int)
*T
}]() {
var t T
p := PT(&t)
p.Set(0)
}
//@ instrs("f15", "*ssa.MakeClosure", "make closure (interface{Set(int); *T}).Set$bound [t1]")
func f15[T any, PT interface {
Set(int)
*T
}]() func(int) {
var t T
p := PT(&t)
return p.Set
}
`
// Parse
conf := loader.Config{ParserMode: parser.ParseComments}
const fname = "p.go"
f, err := conf.ParseFile(fname, contents)
if err != nil {
t.Fatalf("parse: %v", err)
}
conf.CreateFromFiles("p", f)
// Load
lprog, err := conf.Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
// Create and build SSA
prog := ssa.NewProgram(lprog.Fset, ssa.SanityCheckFunctions)
for _, info := range lprog.AllPackages {
if info.TransitivelyErrorFree {
prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
}
}
p := prog.Package(lprog.Package("p").Pkg)
p.Build()
// Collect all notes in f, i.e. comments starting with "//@ instr".
notes, err := expect.ExtractGo(prog.Fset, f)
if err != nil {
t.Errorf("expect.ExtractGo: %v", err)
}
// Expectation is a {function, type string} -> {want, matches}
// where matches is all Instructions.String() that match the key.
// Each expecation is that some permutation of matches is wants.
type expKey struct {
function string
kind string
}
type expValue struct {
wants []string
matches []string
}
expectations := make(map[expKey]*expValue)
for _, note := range notes {
if note.Name == "instrs" {
if len(note.Args) < 2 {
t.Error("Had @instrs annotation without at least 2 arguments")
continue
}
fn, kind := fmt.Sprint(note.Args[0]), fmt.Sprint(note.Args[1])
var wants []string
for _, arg := range note.Args[2:] {
wants = append(wants, fmt.Sprint(arg))
}
expectations[expKey{fn, kind}] = &expValue{wants, nil}
}
}
// Collect all Instructions that match the expectations.
for _, mem := range p.Members {
if fn, ok := mem.(*ssa.Function); ok {
for _, bb := range fn.Blocks {
for _, i := range bb.Instrs {
kind := fmt.Sprintf("%T", i)
if e := expectations[expKey{fn.Name(), kind}]; e != nil {
e.matches = append(e.matches, i.String())
}
}
}
}
}
// Check each expectation.
for key, value := range expectations {
fn, ok := p.Members[key.function].(*ssa.Function)
if !ok {
t.Errorf("Expectation on %s does not match a member in %s", key.function, p.Pkg.Name())
}
got, want := value.matches, value.wants
sort.Strings(got)
sort.Strings(want)
if !reflect.DeepEqual(want, got) {
t.Errorf("Within %s wanted instructions of kind %s: %q. got %q", key.function, key.kind, want, got)
logFunction(t, fn)
}
}
}
// packageName is a test helper to extract the package name from a string
// containing the content of a go file.
func packageName(t testing.TB, content string) string {
f, err := parser.ParseFile(token.NewFileSet(), "", content, parser.PackageClauseOnly)
if err != nil {
t.Fatalf("parsing the file %q failed with error: %s", content, err)
}
return f.Name.Name
}
func logFunction(t testing.TB, fn *ssa.Function) {
// TODO: Consider adding a ssa.Function.GoString() so this can be logged to t via '%#v'.
var buf bytes.Buffer
ssa.WriteFunction(&buf, fn)
t.Log(buf.String())
}