Skip to content

Commit

Permalink
go/callgraph/rta: adds tests for (instantiated) generics
Browse files Browse the repository at this point in the history
Updates golang/go#48525

Change-Id: I7e25ab136dd69ebd50b12894bc893986fc59999b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/402994
Run-TryBot: Zvonimir Pavlinovic <[email protected]>
gopls-CI: kokoro <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
Reviewed-by: Tim King <[email protected]>
Reviewed-by: Alan Donovan <[email protected]>
  • Loading branch information
zpavlinovic committed May 16, 2022
1 parent ed968f6 commit 29d48d6
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 26 deletions.
4 changes: 4 additions & 0 deletions go/callgraph/rta/rta.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ package rta // import "golang.org/x/tools/go/callgraph/rta"
// replacing all "unreachable" functions by a special intrinsic, and
// ensure that that intrinsic is never called.

// TODO(zpavlinovic): decide if the clients must use ssa.InstantiateGenerics
// mode when building programs with generics. It might be possible to
// extend rta to accurately support generics with just ssa.BuilderMode(0).

import (
"fmt"
"go/types"
Expand Down
91 changes: 65 additions & 26 deletions go/callgraph/rta/rta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"os"
"sort"
"strings"
"testing"
Expand All @@ -26,6 +26,7 @@ import (
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/typeparams"
)

var inputs = []string{
Expand Down Expand Up @@ -53,16 +54,7 @@ func expectation(f *ast.File) (string, token.Pos) {
// one per line. Each set is sorted.
func TestRTA(t *testing.T) {
for _, filename := range inputs {
content, err := ioutil.ReadFile(filename)
if err != nil {
t.Errorf("couldn't read file '%s': %s", filename, err)
continue
}

conf := loader.Config{
ParserMode: parser.ParseComments,
}
f, err := conf.ParseFile(filename, content)
prog, f, mainPkg, err := loadProgInfo(filename, ssa.BuilderMode(0))
if err != nil {
t.Error(err)
continue
Expand All @@ -74,30 +66,77 @@ func TestRTA(t *testing.T) {
continue
}

conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
t.Error(err)
continue
}

prog := ssautil.CreateProgram(iprog, 0)
mainPkg := prog.Package(iprog.Created[0].Pkg)
prog.Build()

res := rta.Analyze([]*ssa.Function{
mainPkg.Func("main"),
mainPkg.Func("init"),
}, true)

if got := printResult(res, mainPkg.Pkg); got != want {
if got := printResult(res, mainPkg.Pkg, "dynamic", "Dynamic calls"); got != want {
t.Errorf("%s: got:\n%s\nwant:\n%s",
prog.Fset.Position(pos), got, want)
}
}
}

func printResult(res *rta.Result, from *types.Package) string {
// TestRTAGenerics is TestRTA specialized for testing generics.
func TestRTAGenerics(t *testing.T) {
if !typeparams.Enabled {
t.Skip("TestRTAGenerics requires type parameters")
}

filename := "testdata/generics.go"
prog, f, mainPkg, err := loadProgInfo(filename, ssa.InstantiateGenerics)
if err != nil {
t.Fatal(err)
}

want, pos := expectation(f)
if pos == token.NoPos {
t.Fatalf("No WANT: comment in %s", filename)
}

res := rta.Analyze([]*ssa.Function{
mainPkg.Func("main"),
mainPkg.Func("init"),
}, true)

if got := printResult(res, mainPkg.Pkg, "", "All calls"); got != want {
t.Errorf("%s: got:\n%s\nwant:\n%s",
prog.Fset.Position(pos), got, want)
}
}

func loadProgInfo(filename string, mode ssa.BuilderMode) (*ssa.Program, *ast.File, *ssa.Package, error) {
content, err := os.ReadFile(filename)
if err != nil {
return nil, nil, nil, fmt.Errorf("couldn't read file '%s': %s", filename, err)
}

conf := loader.Config{
ParserMode: parser.ParseComments,
}
f, err := conf.ParseFile(filename, content)
if err != nil {
return nil, nil, nil, err
}

conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
return nil, nil, nil, err
}

prog := ssautil.CreateProgram(iprog, mode)
prog.Build()

return prog, f, prog.Package(iprog.Created[0].Pkg), nil
}

// printResult returns a string representation of res, i.e., call graph,
// reachable functions, and reflect types. For call graph, only edges
// whose description contains edgeMatch are returned and their string
// representation is prefixed with a desc line.
func printResult(res *rta.Result, from *types.Package, edgeMatch, desc string) string {
var buf bytes.Buffer

writeSorted := func(ss []string) {
Expand All @@ -107,10 +146,10 @@ func printResult(res *rta.Result, from *types.Package) string {
}
}

buf.WriteString("Dynamic calls\n")
buf.WriteString(desc + "\n")
var edges []string
callgraph.GraphVisitEdges(res.CallGraph, func(e *callgraph.Edge) error {
if strings.Contains(e.Description(), "dynamic") {
if strings.Contains(e.Description(), edgeMatch) {
edges = append(edges, fmt.Sprintf("%s --> %s",
e.Caller.Func.RelString(from),
e.Callee.Func.RelString(from)))
Expand Down
79 changes: 79 additions & 0 deletions go/callgraph/rta/testdata/generics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//go:build ignore
// +build ignore

package main

// Test of generic function calls.

type I interface {
Foo()
}

type A struct{}

func (a A) Foo() {}

type B struct{}

func (b B) Foo() {}

func instantiated[X I](x X) {
x.Foo()
}

var a A
var b B

func main() {
instantiated[A](a) // static call
instantiated[B](b) // static call

local[C]().Foo()

lambda[A]()()()
}

func local[X I]() I {
var x X
return x
}

type C struct{}

func (c C) Foo() {}

func lambda[X I]() func() func() {
return func() func() {
var x X
return x.Foo
}
}

// WANT:
// All calls
// (*C).Foo --> (C).Foo
// (A).Foo$bound --> (A).Foo
// instantiated[[main.A]] --> (A).Foo
// instantiated[[main.B]] --> (B).Foo
// main --> (*C).Foo
// main --> (A).Foo$bound
// main --> (C).Foo
// main --> instantiated[[main.A]]
// main --> instantiated[[main.B]]
// main --> lambda[[main.A]]
// main --> lambda[[main.A]]$1
// main --> local[[main.C]]
// Reachable functions
// (*C).Foo
// (A).Foo
// (A).Foo$bound
// (B).Foo
// (C).Foo
// instantiated[[main.A]]
// instantiated[[main.B]]
// lambda[[main.A]]
// lambda[[main.A]]$1
// local[[main.C]]
// Reflect types
// *C
// C

0 comments on commit 29d48d6

Please sign in to comment.