whatcanGOwrong
This commit is contained in:
+332
@@ -0,0 +1,332 @@
|
||||
// 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.
|
||||
|
||||
// callgraph: a tool for reporting the call graph of a Go program.
|
||||
// See Usage for details, or run with -help.
|
||||
package main // import "golang.org/x/tools/cmd/callgraph"
|
||||
|
||||
// TODO(adonovan):
|
||||
//
|
||||
// Features:
|
||||
// - restrict graph to a single package
|
||||
// - output
|
||||
// - functions reachable from root (use digraph tool?)
|
||||
// - unreachable functions (use digraph tool?)
|
||||
// - dynamic (runtime) types
|
||||
// - indexed output (numbered nodes)
|
||||
// - JSON output
|
||||
// - additional template fields:
|
||||
// callee file/line/col
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/callgraph"
|
||||
"golang.org/x/tools/go/callgraph/cha"
|
||||
"golang.org/x/tools/go/callgraph/rta"
|
||||
"golang.org/x/tools/go/callgraph/static"
|
||||
"golang.org/x/tools/go/callgraph/vta"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
)
|
||||
|
||||
// flags
|
||||
var (
|
||||
algoFlag = flag.String("algo", "rta",
|
||||
`Call graph construction algorithm (static, cha, rta, vta)`)
|
||||
|
||||
testFlag = flag.Bool("test", false,
|
||||
"Loads test code (*_test.go) for imported packages")
|
||||
|
||||
formatFlag = flag.String("format",
|
||||
"{{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}",
|
||||
"A template expression specifying how to format an edge")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
|
||||
}
|
||||
|
||||
const Usage = `callgraph: display the call graph of a Go program.
|
||||
|
||||
Usage:
|
||||
|
||||
callgraph [-algo=static|cha|rta|vta] [-test] [-format=...] package...
|
||||
|
||||
Flags:
|
||||
|
||||
-algo Specifies the call-graph construction algorithm, one of:
|
||||
|
||||
static static calls only (unsound)
|
||||
cha Class Hierarchy Analysis
|
||||
rta Rapid Type Analysis
|
||||
vta Variable Type Analysis
|
||||
|
||||
The algorithms are ordered by increasing precision in their
|
||||
treatment of dynamic calls (and thus also computational cost).
|
||||
RTA requires a whole program (main or test), and
|
||||
include only functions reachable from main.
|
||||
|
||||
-test Include the package's tests in the analysis.
|
||||
|
||||
-format Specifies the format in which each call graph edge is displayed.
|
||||
One of:
|
||||
|
||||
digraph output suitable for input to
|
||||
golang.org/x/tools/cmd/digraph.
|
||||
graphviz output in AT&T GraphViz (.dot) format.
|
||||
|
||||
All other values are interpreted using text/template syntax.
|
||||
The default value is:
|
||||
|
||||
{{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}
|
||||
|
||||
The structure passed to the template is (effectively):
|
||||
|
||||
type Edge struct {
|
||||
Caller *ssa.Function // calling function
|
||||
Callee *ssa.Function // called function
|
||||
|
||||
// Call site:
|
||||
Filename string // containing file
|
||||
Offset int // offset within file of '('
|
||||
Line int // line number
|
||||
Column int // column number of call
|
||||
Dynamic string // "static" or "dynamic"
|
||||
Description string // e.g. "static method call"
|
||||
}
|
||||
|
||||
Caller and Callee are *ssa.Function values, which print as
|
||||
"(*sync/atomic.Mutex).Lock", but other attributes may be
|
||||
derived from them. For example:
|
||||
|
||||
- {{.Caller.Pkg.Pkg.Path}} yields the import path of the
|
||||
enclosing package; and
|
||||
|
||||
- {{(.Caller.Prog.Fset.Position .Caller.Pos).Filename}}
|
||||
yields the name of the file that declares the caller.
|
||||
|
||||
- The 'posn' template function returns the token.Position
|
||||
of an ssa.Function, so the previous example can be
|
||||
reduced to {{(posn .Caller).Filename}}.
|
||||
|
||||
Consult the documentation for go/token, text/template, and
|
||||
golang.org/x/tools/go/ssa for more detail.
|
||||
|
||||
Examples:
|
||||
|
||||
Show the call graph of the trivial web server application:
|
||||
|
||||
callgraph -format digraph $GOROOT/src/net/http/triv.go
|
||||
|
||||
Same, but show only the packages of each function:
|
||||
|
||||
callgraph -format '{{.Caller.Pkg.Pkg.Path}} -> {{.Callee.Pkg.Pkg.Path}}' \
|
||||
$GOROOT/src/net/http/triv.go | sort | uniq
|
||||
|
||||
Show functions that make dynamic calls into the 'fmt' test package,
|
||||
using the Rapid Type Analysis algorithm:
|
||||
|
||||
callgraph -format='{{.Caller}} -{{.Dynamic}}-> {{.Callee}}' -test -algo=rta fmt |
|
||||
sed -ne 's/-dynamic-/--/p' |
|
||||
sed -ne 's/-->.*fmt_test.*$//p' | sort | uniq
|
||||
|
||||
Show all functions directly called by the callgraph tool's main function:
|
||||
|
||||
callgraph -format=digraph golang.org/x/tools/cmd/callgraph |
|
||||
digraph succs golang.org/x/tools/cmd/callgraph.main
|
||||
`
|
||||
|
||||
func init() {
|
||||
// If $GOMAXPROCS isn't set, use the full capacity of the machine.
|
||||
// For small machines, use at least 4 threads.
|
||||
if os.Getenv("GOMAXPROCS") == "" {
|
||||
n := runtime.NumCPU()
|
||||
if n < 4 {
|
||||
n = 4
|
||||
}
|
||||
runtime.GOMAXPROCS(n)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if err := doCallgraph("", "", *algoFlag, *formatFlag, *testFlag, flag.Args()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "callgraph: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var stdout io.Writer = os.Stdout
|
||||
|
||||
func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) error {
|
||||
if len(args) == 0 {
|
||||
fmt.Fprint(os.Stderr, Usage)
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := &packages.Config{
|
||||
Mode: packages.LoadAllSyntax,
|
||||
Tests: tests,
|
||||
Dir: dir,
|
||||
}
|
||||
if gopath != "" {
|
||||
cfg.Env = append(os.Environ(), "GOPATH="+gopath) // to enable testing
|
||||
}
|
||||
initial, err := packages.Load(cfg, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if packages.PrintErrors(initial) > 0 {
|
||||
return fmt.Errorf("packages contain errors")
|
||||
}
|
||||
|
||||
// Create and build SSA-form program representation.
|
||||
mode := ssa.InstantiateGenerics // instantiate generics by default for soundness
|
||||
prog, pkgs := ssautil.AllPackages(initial, mode)
|
||||
prog.Build()
|
||||
|
||||
// -- call graph construction ------------------------------------------
|
||||
|
||||
var cg *callgraph.Graph
|
||||
|
||||
switch algo {
|
||||
case "static":
|
||||
cg = static.CallGraph(prog)
|
||||
|
||||
case "cha":
|
||||
cg = cha.CallGraph(prog)
|
||||
|
||||
case "pta":
|
||||
return fmt.Errorf("pointer analysis is no longer supported (see Go issue #59676)")
|
||||
|
||||
case "rta":
|
||||
mains, err := mainPackages(pkgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var roots []*ssa.Function
|
||||
for _, main := range mains {
|
||||
roots = append(roots, main.Func("init"), main.Func("main"))
|
||||
}
|
||||
rtares := rta.Analyze(roots, true)
|
||||
cg = rtares.CallGraph
|
||||
|
||||
// NB: RTA gives us Reachable and RuntimeTypes too.
|
||||
|
||||
case "vta":
|
||||
cg = vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown algorithm: %s", algo)
|
||||
}
|
||||
|
||||
cg.DeleteSyntheticNodes()
|
||||
|
||||
// -- output------------------------------------------------------------
|
||||
|
||||
var before, after string
|
||||
|
||||
// Pre-canned formats.
|
||||
switch format {
|
||||
case "digraph":
|
||||
format = `{{printf "%q %q" .Caller .Callee}}`
|
||||
|
||||
case "graphviz":
|
||||
before = "digraph callgraph {\n"
|
||||
after = "}\n"
|
||||
format = ` {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}`
|
||||
}
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"posn": func(f *ssa.Function) token.Position {
|
||||
return f.Prog.Fset.Position(f.Pos())
|
||||
},
|
||||
}
|
||||
tmpl, err := template.New("-format").Funcs(funcMap).Parse(format)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid -format template: %v", err)
|
||||
}
|
||||
|
||||
// Allocate these once, outside the traversal.
|
||||
var buf bytes.Buffer
|
||||
data := Edge{fset: prog.Fset}
|
||||
|
||||
fmt.Fprint(stdout, before)
|
||||
if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
|
||||
data.position.Offset = -1
|
||||
data.edge = edge
|
||||
data.Caller = edge.Caller.Func
|
||||
data.Callee = edge.Callee.Func
|
||||
|
||||
buf.Reset()
|
||||
if err := tmpl.Execute(&buf, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
stdout.Write(buf.Bytes())
|
||||
if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' {
|
||||
fmt.Fprintln(stdout)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(stdout, after)
|
||||
return nil
|
||||
}
|
||||
|
||||
// mainPackages returns the main packages to analyze.
|
||||
// Each resulting package is named "main" and has a main function.
|
||||
func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) {
|
||||
var mains []*ssa.Package
|
||||
for _, p := range pkgs {
|
||||
if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil {
|
||||
mains = append(mains, p)
|
||||
}
|
||||
}
|
||||
if len(mains) == 0 {
|
||||
return nil, fmt.Errorf("no main packages")
|
||||
}
|
||||
return mains, nil
|
||||
}
|
||||
|
||||
type Edge struct {
|
||||
Caller *ssa.Function
|
||||
Callee *ssa.Function
|
||||
|
||||
edge *callgraph.Edge
|
||||
fset *token.FileSet
|
||||
position token.Position // initialized lazily
|
||||
}
|
||||
|
||||
func (e *Edge) pos() *token.Position {
|
||||
if e.position.Offset == -1 {
|
||||
e.position = e.fset.Position(e.edge.Pos()) // called lazily
|
||||
}
|
||||
return &e.position
|
||||
}
|
||||
|
||||
func (e *Edge) Filename() string { return e.pos().Filename }
|
||||
func (e *Edge) Column() int { return e.pos().Column }
|
||||
func (e *Edge) Line() int { return e.pos().Line }
|
||||
func (e *Edge) Offset() int { return e.pos().Offset }
|
||||
|
||||
func (e *Edge) Dynamic() string {
|
||||
if e.edge.Site != nil && e.edge.Site.Common().StaticCallee() == nil {
|
||||
return "dynamic"
|
||||
}
|
||||
return "static"
|
||||
}
|
||||
|
||||
func (e *Edge) Description() string { return e.edge.Description() }
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// 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 && go1.11
|
||||
// +build !android,go1.11
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/internal/testenv"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// This test currently requires GOPATH mode.
|
||||
// Explicitly disabling module mode should suffix, but
|
||||
// we'll also turn off GOPROXY just for good measure.
|
||||
if err := os.Setenv("GO111MODULE", "off"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := os.Setenv("GOPROXY", "off"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallgraph(t *testing.T) {
|
||||
testenv.NeedsTool(t, "go")
|
||||
|
||||
gopath, err := filepath.Abs("testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
algo string
|
||||
tests bool
|
||||
want []string
|
||||
}{
|
||||
{"rta", false, []string{
|
||||
// rta imprecisely shows cross product of {main,main2} x {C,D}
|
||||
`pkg.main --> (pkg.C).f`,
|
||||
`pkg.main --> (pkg.D).f`,
|
||||
`pkg.main --> pkg.main2`,
|
||||
`pkg.main2 --> (pkg.C).f`,
|
||||
`pkg.main2 --> (pkg.D).f`,
|
||||
}},
|
||||
{"vta", false, []string{
|
||||
// vta distinguishes main->C, main2->D.
|
||||
"pkg.main --> (pkg.C).f",
|
||||
"pkg.main --> pkg.main2",
|
||||
"pkg.main2 --> (pkg.D).f",
|
||||
}},
|
||||
// tests: both the package's main and the test's main are called.
|
||||
// The callgraph includes all the guts of the "testing" package.
|
||||
{"rta", true, []string{
|
||||
`pkg.test.main --> testing.MainStart`,
|
||||
`testing.runExample --> pkg.Example`,
|
||||
`pkg.Example --> (pkg.C).f`,
|
||||
`pkg.main --> (pkg.C).f`,
|
||||
}},
|
||||
{"vta", true, []string{
|
||||
`pkg.test.main --> testing.MainStart`,
|
||||
`testing.runExample --> pkg.Example`,
|
||||
`pkg.Example --> (pkg.C).f`,
|
||||
`pkg.main --> (pkg.C).f`,
|
||||
}},
|
||||
} {
|
||||
const format = "{{.Caller}} --> {{.Callee}}"
|
||||
stdout = new(bytes.Buffer)
|
||||
if err := doCallgraph("testdata/src", gopath, test.algo, format, test.tests, []string{"pkg"}); err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
edges := make(map[string]bool)
|
||||
for _, line := range strings.Split(fmt.Sprint(stdout), "\n") {
|
||||
edges[line] = true
|
||||
}
|
||||
ok := true
|
||||
for _, edge := range test.want {
|
||||
if !edges[edge] {
|
||||
ok = false
|
||||
t.Errorf("callgraph(%q, %t): missing edge: %s",
|
||||
test.algo, test.tests, edge)
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
t.Log("got:\n", stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
type I interface {
|
||||
f()
|
||||
}
|
||||
|
||||
type C int
|
||||
|
||||
func (C) f() {}
|
||||
|
||||
type D int
|
||||
|
||||
func (D) f() {}
|
||||
|
||||
func main() {
|
||||
var i I = C(0)
|
||||
i.f() // dynamic call
|
||||
|
||||
main2()
|
||||
}
|
||||
|
||||
func main2() {
|
||||
var i I = D(0)
|
||||
i.f() // dynamic call
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
// An Example function must have an "Output:" comment for the go build
|
||||
// system to generate a call to it from the test main package.
|
||||
|
||||
func Example() {
|
||||
C(0).f()
|
||||
|
||||
// Output:
|
||||
}
|
||||
Reference in New Issue
Block a user