From a37ba1418feca17a5b0cc4c01ce6c37e13c9acba Mon Sep 17 00:00:00 2001 From: Zvonimir Pavlinovic Date: Tue, 26 Apr 2022 15:46:41 -0700 Subject: [PATCH] go/callgraph/cha: adds tests for (instantiated) generics Updates golang/go#48525 Change-Id: Id568e0b188dd045356f556cb9d759a775c8c5a04 Reviewed-on: https://go-review.googlesource.com/c/tools/+/402474 Reviewed-by: Tim King --- go/callgraph/cha/cha_test.go | 88 +++++++++++++++++++-------- go/callgraph/cha/testdata/generics.go | 45 ++++++++++++++ 2 files changed, 107 insertions(+), 26 deletions(-) create mode 100644 go/callgraph/cha/testdata/generics.go diff --git a/go/callgraph/cha/cha_test.go b/go/callgraph/cha/cha_test.go index 8777ce4330..a12b3d0a34 100644 --- a/go/callgraph/cha/cha_test.go +++ b/go/callgraph/cha/cha_test.go @@ -24,7 +24,9 @@ import ( "golang.org/x/tools/go/callgraph" "golang.org/x/tools/go/callgraph/cha" "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{ @@ -49,16 +51,7 @@ func expectation(f *ast.File) (string, token.Pos) { // the WANT comment at the end of the file. func TestCHA(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.InstantiateGenerics) if err != nil { t.Error(err) continue @@ -66,34 +59,77 @@ func TestCHA(t *testing.T) { want, pos := expectation(f) if pos == token.NoPos { - t.Errorf("No WANT: comment in %s", filename) + t.Error(fmt.Errorf("No WANT: comment in %s", filename)) 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() - cg := cha.CallGraph(prog) - if got := printGraph(cg, mainPkg.Pkg); got != want { + if got := printGraph(cg, mainPkg.Pkg, "dynamic", "Dynamic calls"); got != want { t.Errorf("%s: got:\n%s\nwant:\n%s", prog.Fset.Position(pos), got, want) } } } -func printGraph(cg *callgraph.Graph, from *types.Package) string { +// TestCHAGenerics is TestCHA tailored for testing generics, +func TestCHAGenerics(t *testing.T) { + if !typeparams.Enabled { + t.Skip("TestCHAGenerics 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.Fatal(fmt.Errorf("No WANT: comment in %s", filename)) + } + + cg := cha.CallGraph(prog) + + if got := printGraph(cg, 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 := ioutil.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 +} + +// printGraph returns a string representation of cg involving only edges +// whose description contains edgeMatch. The string representation is +// prefixed with a desc line. +func printGraph(cg *callgraph.Graph, from *types.Package, edgeMatch string, desc string) string { var edges []string callgraph.GraphVisitEdges(cg, 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))) @@ -103,7 +139,7 @@ func printGraph(cg *callgraph.Graph, from *types.Package) string { sort.Strings(edges) var buf bytes.Buffer - buf.WriteString("Dynamic calls\n") + buf.WriteString(desc + "\n") for _, edge := range edges { fmt.Fprintf(&buf, " %s\n", edge) } diff --git a/go/callgraph/cha/testdata/generics.go b/go/callgraph/cha/testdata/generics.go new file mode 100644 index 0000000000..2711dc0d27 --- /dev/null +++ b/go/callgraph/cha/testdata/generics.go @@ -0,0 +1,45 @@ +//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() +} + +func Bar() {} + +func f(h func(), g func(I), k func(A), a A, b B) { + h() + + k(a) + g(b) // g:func(I) is not matched by instantiated[[B]]:func(B) + + instantiated[A](a) // static call + instantiated[B](b) // static call +} + +// WANT: +// All calls +// (*A).Foo --> (A).Foo +// (*B).Foo --> (B).Foo +// f --> Bar +// f --> instantiated[[main.A]] +// f --> instantiated[[main.A]] +// f --> instantiated[[main.B]] +// instantiated[[main.A]] --> (A).Foo +// instantiated[[main.B]] --> (B).Foo