Skip to content

Commit

Permalink
go/callgraph/rta: add rta analysis test case for multiple go packages
Browse files Browse the repository at this point in the history
* use go/packages to load packages

Change-Id: I6e9f81b282cddc186b4905a23ff635cd98245ac8
GitHub-Last-Rev: a859e27
GitHub-Pull-Request: #513
Reviewed-on: https://go-review.googlesource.com/c/tools/+/609576
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Tim King <[email protected]>
Reviewed-by: Alan Donovan <[email protected]>
  • Loading branch information
xieyuschen authored and timothy-king committed Sep 4, 2024
1 parent dc4d64c commit 4fb36d1
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 47 deletions.
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

0 comments on commit 4fb36d1

Please sign in to comment.