Skip to content

go/callgraph/rta: add rta analysis test case for multiple go packages #513

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 77 additions & 24 deletions go/callgraph/rta/rta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,66 @@ package rta_test
import (
"fmt"
"go/ast"
"go/parser"
"go/types"
"sort"
"strings"
"testing"

"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/callgraph/rta"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/testfiles"
"golang.org/x/tools/txtar"
)

// TestRTA runs RTA on each testdata/*.go file and compares the
// results with the expectations expressed in the WANT comment.
// TestRTA runs RTA on each testdata/*.txtar file containing a single
// go file in a single package or multiple files in different packages,
// and compares the results with the expectations expressed in the WANT
// comment.
func TestRTA(t *testing.T) {
filenames := []string{
"testdata/func.go",
"testdata/generics.go",
"testdata/iface.go",
"testdata/reflectcall.go",
"testdata/rtype.go",
archivePaths := []string{
"testdata/func.txtar",
"testdata/generics.txtar",
"testdata/iface.txtar",
"testdata/reflectcall.txtar",
"testdata/rtype.txtar",
"testdata/multipkgs.txtar",
}
for _, filename := range filenames {
t.Run(filename, func(t *testing.T) {
// Load main program and build SSA.
// TODO(adonovan): use go/packages instead.
conf := loader.Config{ParserMode: parser.ParseComments}
f, err := conf.ParseFile(filename, nil)
if err != nil {
t.Fatal(err)
for _, archive := range archivePaths {
t.Run(archive, func(t *testing.T) {
pkgs := loadPackages(t, archive)

// find the file which contains the expected result
var f *ast.File
for _, p := range pkgs {
// We assume the packages have a single file or
// the wanted result is in the first file of the main package.
if p.Name == "main" {
f = p.Syntax[0]
}
}
conf.CreateFromFiles("main", f)
lprog, err := conf.Load()
if err != nil {
t.Fatal(err)
if f == nil {
t.Fatalf("failed to find the file with expected result within main package %s", archive)
}
prog := ssautil.CreateProgram(lprog, ssa.InstantiateGenerics)

prog, spkgs := ssautil.Packages(pkgs, ssa.SanityCheckFunctions|ssa.InstantiateGenerics)

// find the main package to get functions for rta analysis
var mainPkg *ssa.Package
for _, sp := range spkgs {
if sp.Pkg.Name() == "main" {
mainPkg = sp
break
}
}
if mainPkg == nil {
t.Fatalf("failed to find main ssa package %s", archive)
}

prog.Build()
mainPkg := prog.Package(lprog.Created[0].Pkg)

res := rta.Analyze([]*ssa.Function{
mainPkg.Func("main"),
Expand All @@ -64,6 +83,40 @@ func TestRTA(t *testing.T) {
}
}

// loadPackages unpacks the archive to a temporary directory and loads all packages within it.
func loadPackages(t *testing.T, archive string) []*packages.Package {
ar, err := txtar.ParseFile(archive)
if err != nil {
t.Fatal(err)
}

fs, err := txtar.FS(ar)
if err != nil {
t.Fatal(err)
}
dir := testfiles.CopyToTmp(t, fs)

var baseConfig = &packages.Config{
Mode: packages.NeedSyntax |
packages.NeedTypesInfo |
packages.NeedDeps |
packages.NeedName |
packages.NeedFiles |
packages.NeedImports |
packages.NeedCompiledGoFiles |
packages.NeedTypes,
Dir: dir,
}
pkgs, err := packages.Load(baseConfig, "./...")
if err != nil {
t.Fatal(err)
}
if num := packages.PrintErrors(pkgs); num > 0 {
t.Fatalf("packages contained %d errors", num)
}
return pkgs
}

// check tests the RTA analysis results against the test expectations
// defined by a comment starting with a line "WANT:".
//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//go:build ignore
// +build ignore
-- go.mod --
module example.com
go 1.18

-- func.go --
package main

// Test of dynamic function calls.
Expand Down Expand Up @@ -36,4 +38,4 @@ func main() {
// reachable init$1
// reachable init$2
// !reachable B
// reachable main
// reachable main
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//go:build ignore
// +build ignore
-- go.mod --
module example.com
go 1.18

-- generics.go --
package main

// Test of generic function calls.
Expand Down Expand Up @@ -53,27 +55,27 @@ func lambda[X I]() func() func() {
//
// edge (*C).Foo --static method call--> (C).Foo
// edge (A).Foo$bound --static method call--> (A).Foo
// edge instantiated[main.A] --static method call--> (A).Foo
// edge instantiated[main.B] --static method call--> (B).Foo
// edge instantiated[example.com.A] --static method call--> (A).Foo
// edge instantiated[example.com.B] --static method call--> (B).Foo
// edge main --dynamic method call--> (*C).Foo
// edge main --dynamic function call--> (A).Foo$bound
// edge main --dynamic method call--> (C).Foo
// edge main --static function call--> instantiated[main.A]
// edge main --static function call--> instantiated[main.B]
// edge main --static function call--> lambda[main.A]
// edge main --dynamic function call--> lambda[main.A]$1
// edge main --static function call--> local[main.C]
// edge main --static function call--> instantiated[example.com.A]
// edge main --static function call--> instantiated[example.com.B]
// edge main --static function call--> lambda[example.com.A]
// edge main --dynamic function call--> lambda[example.com.A]$1
// edge main --static function call--> local[example.com.C]
//
// reachable (*C).Foo
// reachable (A).Foo
// reachable (A).Foo$bound
// reachable (B).Foo
// reachable (C).Foo
// reachable instantiated[main.A]
// reachable instantiated[main.B]
// reachable lambda[main.A]
// reachable lambda[main.A]$1
// reachable local[main.C]
// reachable instantiated[example.com.A]
// reachable instantiated[example.com.B]
// reachable lambda[example.com.A]
// reachable lambda[example.com.A]$1
// reachable local[example.com.C]
//
// rtype *C
// rtype C
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//go:build ignore
// +build ignore
-- go.mod --
module example.com
go 1.18

-- iface.go --
package main

// Test of interface calls.
Expand Down
106 changes: 106 additions & 0 deletions go/callgraph/rta/testdata/multipkgs.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
-- go.mod --
module example.com
go 1.18

-- iface.go --
package main

import (
"example.com/subpkg"
)

func use(interface{})

// Test of interface calls.

func main() {
use(subpkg.A(0))
use(new(subpkg.B))
use(subpkg.B2(0))

var i interface {
F()
}

// assign an interface type with a function return interface value
i = subpkg.NewInterfaceF()

i.F()
}

func dead() {
use(subpkg.D(0))
}

// WANT:
//
// edge (*example.com/subpkg.A).F --static method call--> (example.com/subpkg.A).F
// edge (*example.com/subpkg.B2).F --static method call--> (example.com/subpkg.B2).F
// edge (*example.com/subpkg.C).F --static method call--> (example.com/subpkg.C).F
// edge init --static function call--> example.com/subpkg.init
// edge main --dynamic method call--> (*example.com/subpkg.A).F
// edge main --dynamic method call--> (*example.com/subpkg.B).F
// edge main --dynamic method call--> (*example.com/subpkg.B2).F
// edge main --dynamic method call--> (*example.com/subpkg.C).F
// edge main --dynamic method call--> (example.com/subpkg.A).F
// edge main --dynamic method call--> (example.com/subpkg.B2).F
// edge main --dynamic method call--> (example.com/subpkg.C).F
// edge main --static function call--> example.com/subpkg.NewInterfaceF
// edge main --static function call--> use
//
// reachable (*example.com/subpkg.A).F
// reachable (*example.com/subpkg.B).F
// reachable (*example.com/subpkg.B2).F
// reachable (*example.com/subpkg.C).F
// reachable (example.com/subpkg.A).F
// !reachable (example.com/subpkg.B).F
// reachable (example.com/subpkg.B2).F
// reachable (example.com/subpkg.C).F
// reachable example.com/subpkg.NewInterfaceF
// reachable example.com/subpkg.init
// !reachable (*example.com/subpkg.D).F
// !reachable (example.com/subpkg.D).F
// reachable init
// reachable main
// reachable use
//
// rtype *example.com/subpkg.A
// rtype *example.com/subpkg.B
// rtype *example.com/subpkg.B2
// rtype *example.com/subpkg.C
// rtype example.com/subpkg.B
// rtype example.com/subpkg.A
// rtype example.com/subpkg.B2
// rtype example.com/subpkg.C
// !rtype example.com/subpkg.D

-- subpkg/impl.go --
package subpkg

type InterfaceF interface {
F()
}

type A byte // instantiated but not a reflect type

func (A) F() {} // reachable: exported method of reflect type

type B int // a reflect type

func (*B) F() {} // reachable: exported method of reflect type

type B2 int // a reflect type, and *B2 also

func (B2) F() {} // reachable: exported method of reflect type

type C string

func (C) F() {} // reachable: exported by NewInterfaceF

func NewInterfaceF() InterfaceF {
return C("")
}

type D uint // instantiated only in dead code

func (*D) F() {} // unreachable
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//go:build ignore
// +build ignore
-- go.mod --
module example.com
go 1.18

-- reflectcall.go --
// Test of a reflective call to an address-taken function.
//
// Dynamically, this program executes both print statements.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//go:build ignore
// +build ignore
-- go.mod --
module example.com
go 1.18

-- rtype.go --
package main

// Test of runtime types (types for which descriptors are needed).
Expand Down