140 lines
3.1 KiB
Go
140 lines
3.1 KiB
Go
// 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.
|
|
|
|
// +build !android
|
|
|
|
package rta_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"io/ioutil"
|
|
"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/go/types"
|
|
)
|
|
|
|
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 {
|
|
content, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
t.Errorf("couldn't read file '%s': %s", filename, err)
|
|
continue
|
|
}
|
|
|
|
conf := loader.Config{
|
|
ParserMode: parser.ParseComments,
|
|
}
|
|
f, err := conf.ParseFile(filename, content)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
|
|
want, pos := expectation(f)
|
|
if pos == token.NoPos {
|
|
t.Errorf("No WANT: comment in %s", filename)
|
|
continue
|
|
}
|
|
|
|
conf.CreateFromFiles("main", f)
|
|
iprog, err := conf.Load()
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
|
|
prog := ssautil.CreateProgram(iprog, 0)
|
|
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
|
prog.Build()
|
|
|
|
res := rta.Analyze([]*ssa.Function{
|
|
mainPkg.Func("main"),
|
|
mainPkg.Func("init"),
|
|
}, true)
|
|
|
|
if got := printResult(res, mainPkg.Pkg); got != want {
|
|
t.Errorf("%s: got:\n%s\nwant:\n%s",
|
|
prog.Fset.Position(pos), got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func printResult(res *rta.Result, from *types.Package) string {
|
|
var buf bytes.Buffer
|
|
|
|
writeSorted := func(ss []string) {
|
|
sort.Strings(ss)
|
|
for _, s := range ss {
|
|
fmt.Fprintf(&buf, " %s\n", s)
|
|
}
|
|
}
|
|
|
|
buf.WriteString("Dynamic calls\n")
|
|
var edges []string
|
|
callgraph.GraphVisitEdges(res.CallGraph, func(e *callgraph.Edge) error {
|
|
if strings.Contains(e.Description(), "dynamic") {
|
|
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())
|
|
}
|