blob: 67d05d61233070e46c33e233f374efd95187115f [file] [log] [blame]
// Copyright 2014 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.
// No testdata on Android.
//go:build !android
// +build !android
package rta_test
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"os"
"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/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/typeparams"
)
var inputs = []string{
"testdata/func.go",
"testdata/rtype.go",
"testdata/iface.go",
}
func expectation(f *ast.File) (string, token.Pos) {
for _, c := range f.Comments {
text := strings.TrimSpace(c.Text())
if t := strings.TrimPrefix(text, "WANT:\n"); t != text {
return t, c.Pos()
}
}
return "", token.NoPos
}
// TestRTA runs RTA on each file in inputs, prints the results, and
// compares it with the golden results embedded in the WANT comment at
// the end of the file.
//
// The results string consists of two parts: the set of dynamic call
// edges, "f --> g", one per line, and the set of reachable functions,
// one per line. Each set is sorted.
func TestRTA(t *testing.T) {
for _, filename := range inputs {
prog, f, mainPkg, err := loadProgInfo(filename, ssa.BuilderMode(0))
if err != nil {
t.Error(err)
continue
}
want, pos := expectation(f)
if pos == token.NoPos {
t.Errorf("No WANT: comment in %s", filename)
continue
}
res := rta.Analyze([]*ssa.Function{
mainPkg.Func("main"),
mainPkg.Func("init"),
}, true)
if got := printResult(res, mainPkg.Pkg, "dynamic", "Dynamic calls"); got != want {
t.Errorf("%s: got:\n%s\nwant:\n%s",
prog.Fset.Position(pos), got, want)
}
}
}
// TestRTAGenerics is TestRTA specialized for testing generics.
func TestRTAGenerics(t *testing.T) {
if !typeparams.Enabled {
t.Skip("TestRTAGenerics requires type parameters")
}
filename := "testdata/generics.go"
prog, f, mainPkg, err := loadProgInfo(filename, ssa.InstantiateGenerics)
if err != nil {
t.Fatal(err)
}
want, pos := expectation(f)
if pos == token.NoPos {
t.Fatalf("No WANT: comment in %s", filename)
}
res := rta.Analyze([]*ssa.Function{
mainPkg.Func("main"),
mainPkg.Func("init"),
}, true)
if got := printResult(res, mainPkg.Pkg, "", "All calls"); got != want {
t.Errorf("%s: got:\n%s\nwant:\n%s",
prog.Fset.Position(pos), got, want)
}
}
func loadProgInfo(filename string, mode ssa.BuilderMode) (*ssa.Program, *ast.File, *ssa.Package, error) {
content, err := os.ReadFile(filename)
if err != nil {
return nil, nil, nil, fmt.Errorf("couldn't read file '%s': %s", filename, err)
}
conf := loader.Config{
ParserMode: parser.ParseComments,
}
f, err := conf.ParseFile(filename, content)
if err != nil {
return nil, nil, nil, err
}
conf.CreateFromFiles("main", f)
iprog, err := conf.Load()
if err != nil {
return nil, nil, nil, err
}
prog := ssautil.CreateProgram(iprog, mode)
prog.Build()
return prog, f, prog.Package(iprog.Created[0].Pkg), nil
}
// printResult returns a string representation of res, i.e., call graph,
// reachable functions, and reflect types. For call graph, only edges
// whose description contains edgeMatch are returned and their string
// representation is prefixed with a desc line.
func printResult(res *rta.Result, from *types.Package, edgeMatch, desc string) string {
var buf bytes.Buffer
writeSorted := func(ss []string) {
sort.Strings(ss)
for _, s := range ss {
fmt.Fprintf(&buf, " %s\n", s)
}
}
buf.WriteString(desc + "\n")
var edges []string
callgraph.GraphVisitEdges(res.CallGraph, func(e *callgraph.Edge) error {
if strings.Contains(e.Description(), edgeMatch) {
edges = append(edges, fmt.Sprintf("%s --> %s",
e.Caller.Func.RelString(from),
e.Callee.Func.RelString(from)))
}
return nil
})
writeSorted(edges)
buf.WriteString("Reachable functions\n")
var reachable []string
for f := range res.Reachable {
reachable = append(reachable, f.RelString(from))
}
writeSorted(reachable)
buf.WriteString("Reflect types\n")
var rtypes []string
res.RuntimeTypes.Iterate(func(key types.Type, value interface{}) {
if value == false { // accessible to reflection
rtypes = append(rtypes, types.TypeString(key, types.RelativeTo(from)))
}
})
writeSorted(rtypes)
return strings.TrimSpace(buf.String())
}