blob: b190149edcce7005da05d1d03630dd43f18df79f [file] [log] [blame]
// Copyright 2021 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 vta
import (
"testing"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/buildssa"
"golang.org/x/tools/go/callgraph/cha"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
func TestVTACallGraph(t *testing.T) {
for _, file := range []string{
"testdata/src/callgraph_static.go",
"testdata/src/callgraph_ho.go",
"testdata/src/callgraph_interfaces.go",
"testdata/src/callgraph_pointers.go",
"testdata/src/callgraph_collections.go",
"testdata/src/callgraph_fields.go",
"testdata/src/callgraph_field_funcs.go",
"testdata/src/callgraph_recursive_types.go",
"testdata/src/callgraph_issue_57756.go",
"testdata/src/callgraph_comma_maps.go",
"testdata/src/callgraph_type_aliases.go",
} {
t.Run(file, func(t *testing.T) {
prog, want, err := testProg(file, ssa.BuilderMode(0))
if err != nil {
t.Fatalf("couldn't load test file '%s': %s", file, err)
}
if len(want) == 0 {
t.Fatalf("couldn't find want in `%s`", file)
}
g := CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
got := callGraphStr(g)
if diff := setdiff(want, got); len(diff) > 0 {
t.Errorf("computed callgraph %v\nshould contain\n%v\n(diff: %v)", got, want, diff)
}
})
}
}
// TestVTAProgVsFuncSet exemplifies and tests different possibilities
// enabled by having an arbitrary function set as input to CallGraph
// instead of the whole program (i.e., ssautil.AllFunctions(prog)).
func TestVTAProgVsFuncSet(t *testing.T) {
prog, want, err := testProg("testdata/src/callgraph_nested_ptr.go", ssa.BuilderMode(0))
if err != nil {
t.Fatalf("couldn't load test `testdata/src/callgraph_nested_ptr.go`: %s", err)
}
if len(want) == 0 {
t.Fatal("couldn't find want in `testdata/src/callgraph_nested_ptr.go`")
}
allFuncs := ssautil.AllFunctions(prog)
g := CallGraph(allFuncs, cha.CallGraph(prog))
// VTA over the whole program will produce a call graph that
// includes Baz:(**i).Foo -> A.Foo, B.Foo.
got := callGraphStr(g)
if diff := setdiff(want, got); len(diff) > 0 {
t.Errorf("computed callgraph %v should contain %v (diff: %v)", got, want, diff)
}
// Prune the set of program functions to exclude Bar(). This should
// yield a call graph that includes different set of callees for Baz
// Baz:(**i).Foo -> A.Foo
//
// Note that the exclusion of Bar can happen, for instance, if Baz is
// considered an entry point of some data flow analysis and Bar is
// provably (e.g., using CHA forward reachability) unreachable from Baz.
noBarFuncs := make(map[*ssa.Function]bool)
for f, in := range allFuncs {
noBarFuncs[f] = in && (funcName(f) != "Bar")
}
want = []string{"Baz: Do(i) -> Do; invoke t2.Foo() -> A.Foo"}
g = CallGraph(noBarFuncs, cha.CallGraph(prog))
got = callGraphStr(g)
if diff := setdiff(want, got); len(diff) > 0 {
t.Errorf("pruned callgraph %v should contain %v (diff: %v)", got, want, diff)
}
}
// TestVTAPanicMissingDefinitions tests if VTA gracefully handles the case
// where VTA panics when a definition of a function or method is not
// available, which can happen when using analysis package. A successful
// test simply does not panic.
func TestVTAPanicMissingDefinitions(t *testing.T) {
run := func(pass *analysis.Pass) (interface{}, error) {
s := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
CallGraph(ssautil.AllFunctions(s.Pkg.Prog), cha.CallGraph(s.Pkg.Prog))
return nil, nil
}
analyzer := &analysis.Analyzer{
Name: "test",
Doc: "test",
Run: run,
Requires: []*analysis.Analyzer{
buildssa.Analyzer,
},
}
testdata := analysistest.TestData()
res := analysistest.Run(t, testdata, analyzer, "t", "d")
if len(res) != 2 {
t.Errorf("want analysis results for 2 packages; got %v", len(res))
}
for _, r := range res {
if r.Err != nil {
t.Errorf("want no error for package %v; got %v", r.Pass.Pkg.Path(), r.Err)
}
}
}
func TestVTACallGraphGenerics(t *testing.T) {
// TODO(zpavlinovic): add more tests
files := []string{
"testdata/src/arrays_generics.go",
"testdata/src/callgraph_generics.go",
"testdata/src/issue63146.go",
}
for _, file := range files {
t.Run(file, func(t *testing.T) {
prog, want, err := testProg(file, ssa.InstantiateGenerics)
if err != nil {
t.Fatalf("couldn't load test file '%s': %s", file, err)
}
if len(want) == 0 {
t.Fatalf("couldn't find want in `%s`", file)
}
g := CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
got := callGraphStr(g)
if diff := setdiff(want, got); len(diff) != 0 {
t.Errorf("computed callgraph %v should contain %v (diff: %v)", got, want, diff)
logFns(t, prog)
}
})
}
}
func TestVTACallGraphGo117(t *testing.T) {
file := "testdata/src/go117.go"
prog, want, err := testProg(file, ssa.BuilderMode(0))
if err != nil {
t.Fatalf("couldn't load test file '%s': %s", file, err)
}
if len(want) == 0 {
t.Fatalf("couldn't find want in `%s`", file)
}
g, _ := typePropGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
got := vtaGraphStr(g)
if diff := setdiff(want, got); len(diff) != 0 {
t.Errorf("`%s`: want superset of %v;\n got %v", file, want, got)
}
}