From 4fb36d15ccac8c59e805a07f9f23394b29a874ab Mon Sep 17 00:00:00 2001 From: xieyuschen Date: Wed, 4 Sep 2024 03:37:56 +0000 Subject: [PATCH] go/callgraph/rta: add rta analysis test case for multiple go packages * use go/packages to load packages Change-Id: I6e9f81b282cddc186b4905a23ff635cd98245ac8 GitHub-Last-Rev: a859e27d81476736c82a9d9c7c40ec218c2a193b GitHub-Pull-Request: golang/tools#513 Reviewed-on: https://go-review.googlesource.com/c/tools/+/609576 LUCI-TryBot-Result: Go LUCI Reviewed-by: Tim King Reviewed-by: Alan Donovan --- go/callgraph/rta/rta_test.go | 101 +++++++++++++---- .../rta/testdata/{func.go => func.txtar} | 8 +- .../testdata/{generics.go => generics.txtar} | 30 ++--- .../rta/testdata/{iface.go => iface.txtar} | 6 +- go/callgraph/rta/testdata/multipkgs.txtar | 106 ++++++++++++++++++ .../{reflectcall.go => reflectcall.txtar} | 6 +- .../rta/testdata/{rtype.go => rtype.txtar} | 6 +- 7 files changed, 216 insertions(+), 47 deletions(-) rename go/callgraph/rta/testdata/{func.go => func.txtar} (88%) rename go/callgraph/rta/testdata/{generics.go => generics.txtar} (56%) rename go/callgraph/rta/testdata/{iface.go => iface.txtar} (96%) create mode 100644 go/callgraph/rta/testdata/multipkgs.txtar rename go/callgraph/rta/testdata/{reflectcall.go => reflectcall.txtar} (95%) rename go/callgraph/rta/testdata/{rtype.go => rtype.txtar} (92%) diff --git a/go/callgraph/rta/rta_test.go b/go/callgraph/rta/rta_test.go index 8552dc7b13c..1fc32c64252 100644 --- a/go/callgraph/rta/rta_test.go +++ b/go/callgraph/rta/rta_test.go @@ -12,7 +12,6 @@ package rta_test import ( "fmt" "go/ast" - "go/parser" "go/types" "sort" "strings" @@ -20,39 +19,59 @@ import ( "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"), @@ -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:". // diff --git a/go/callgraph/rta/testdata/func.go b/go/callgraph/rta/testdata/func.txtar similarity index 88% rename from go/callgraph/rta/testdata/func.go rename to go/callgraph/rta/testdata/func.txtar index bcdcb6ebf90..57930a40cb3 100644 --- a/go/callgraph/rta/testdata/func.go +++ b/go/callgraph/rta/testdata/func.txtar @@ -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. @@ -36,4 +38,4 @@ func main() { // reachable init$1 // reachable init$2 // !reachable B -// reachable main +// reachable main \ No newline at end of file diff --git a/go/callgraph/rta/testdata/generics.go b/go/callgraph/rta/testdata/generics.txtar similarity index 56% rename from go/callgraph/rta/testdata/generics.go rename to go/callgraph/rta/testdata/generics.txtar index 17ed6b58e0c..b8039742110 100644 --- a/go/callgraph/rta/testdata/generics.go +++ b/go/callgraph/rta/testdata/generics.txtar @@ -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. @@ -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 diff --git a/go/callgraph/rta/testdata/iface.go b/go/callgraph/rta/testdata/iface.txtar similarity index 96% rename from go/callgraph/rta/testdata/iface.go rename to go/callgraph/rta/testdata/iface.txtar index c559204581e..ceb0140a238 100644 --- a/go/callgraph/rta/testdata/iface.go +++ b/go/callgraph/rta/testdata/iface.txtar @@ -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. diff --git a/go/callgraph/rta/testdata/multipkgs.txtar b/go/callgraph/rta/testdata/multipkgs.txtar new file mode 100644 index 00000000000..908fea00563 --- /dev/null +++ b/go/callgraph/rta/testdata/multipkgs.txtar @@ -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 \ No newline at end of file diff --git a/go/callgraph/rta/testdata/reflectcall.go b/go/callgraph/rta/testdata/reflectcall.txtar similarity index 95% rename from go/callgraph/rta/testdata/reflectcall.go rename to go/callgraph/rta/testdata/reflectcall.txtar index 8f71fb58303..67cd290d479 100644 --- a/go/callgraph/rta/testdata/reflectcall.go +++ b/go/callgraph/rta/testdata/reflectcall.txtar @@ -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. diff --git a/go/callgraph/rta/testdata/rtype.go b/go/callgraph/rta/testdata/rtype.txtar similarity index 92% rename from go/callgraph/rta/testdata/rtype.go rename to go/callgraph/rta/testdata/rtype.txtar index 6d84e0342bf..377bc1f7c8c 100644 --- a/go/callgraph/rta/testdata/rtype.go +++ b/go/callgraph/rta/testdata/rtype.txtar @@ -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).