blob: e5a9b41e0eac41b4c945bf4f5b72a8d5e52af605 [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/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/callgraph_static.go",
"testdata/callgraph_ho.go",
"testdata/callgraph_interfaces.go",
"testdata/callgraph_pointers.go",
"testdata/callgraph_collections.go",
"testdata/callgraph_fields.go",
"testdata/callgraph_field_funcs.go",
} {
t.Run(file, func(t *testing.T) {
prog, want, err := testProg(file)
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))
if got := callGraphStr(g); !subGraph(want, got) {
t.Errorf("computed callgraph %v should contain %v", got, want)
}
})
}
}
// 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/callgraph_nested_ptr.go")
if err != nil {
t.Fatalf("couldn't load test `testdata/callgraph_nested_ptr.go`: %s", err)
}
if len(want) == 0 {
t.Fatal("couldn't find want in `testdata/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.
if got := callGraphStr(g); !subGraph(want, got) {
t.Errorf("computed callgraph %v should contain %v", got, want)
}
// 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))
if got := callGraphStr(g); !subGraph(want, got) {
t.Errorf("pruned callgraph %v should contain %v", got, want)
}
}