mirror of
https://github.com/golang/go
synced 2024-11-18 22:55:23 -07:00
1bd72987c2
Because go/packages presents each synthesized test main package as a first-class package, the tests now analyze all of the dependencies of "testing", which they previously avoided. This makes the tests slower and the resulting call graph much larger, so they now look for a subset match, not an exact match, on the set of graph edges. Change-Id: I9d7acf420e41cbffc03ca8423f5afb3ef671d775 Reviewed-on: https://go-review.googlesource.com/128695 Reviewed-by: Ian Cottrell <iancottrell@google.com> Reviewed-by: Michael Matloob <matloob@golang.org>
347 lines
9.3 KiB
Go
347 lines
9.3 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.
|
|
|
|
// 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 (
|
|
"bufio"
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"go/build"
|
|
"go/token"
|
|
"io"
|
|
"log"
|
|
"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/packages"
|
|
"golang.org/x/tools/go/pointer"
|
|
"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, pta)`)
|
|
|
|
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")
|
|
|
|
ptalogFlag = flag.String("ptalog", "",
|
|
"Location of the points-to analysis log file, or empty to disable logging.")
|
|
)
|
|
|
|
func init() {
|
|
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
|
|
}
|
|
|
|
const Usage = `callgraph: display the the call graph of a Go program.
|
|
|
|
Usage:
|
|
|
|
callgraph [-algo=static|cha|rta|pta] [-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
|
|
pta inclusion-based Points-To Analysis
|
|
|
|
The algorithms are ordered by increasing precision in their
|
|
treatment of dynamic calls (and thus also computational cost).
|
|
RTA and PTA require 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, e.g. Caller.Pkg.Pkg.Path yields the
|
|
import path of the enclosing package. Consult the go/ssa
|
|
API documentation for details.
|
|
|
|
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 pointer analysis algorithm:
|
|
|
|
callgraph -format='{{.Caller}} -{{.Dynamic}}-> {{.Callee}}' -test -algo=pta 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.Fprintln(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
|
|
}
|
|
|
|
// Create and build SSA-form program representation.
|
|
prog, pkgs := ssautil.Packages(initial, 0)
|
|
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":
|
|
// Set up points-to analysis log file.
|
|
var ptalog io.Writer
|
|
if *ptalogFlag != "" {
|
|
if f, err := os.Create(*ptalogFlag); err != nil {
|
|
log.Fatalf("Failed to create PTA log file: %s", err)
|
|
} else {
|
|
buf := bufio.NewWriter(f)
|
|
ptalog = buf
|
|
defer func() {
|
|
if err := buf.Flush(); err != nil {
|
|
log.Printf("flush: %s", err)
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
log.Printf("close: %s", err)
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
mains, err := mainPackages(pkgs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
config := &pointer.Config{
|
|
Mains: mains,
|
|
BuildCallGraph: true,
|
|
Log: ptalog,
|
|
}
|
|
ptares, err := pointer.Analyze(config)
|
|
if err != nil {
|
|
return err // internal error in pointer analysis
|
|
}
|
|
cg = ptares.CallGraph
|
|
|
|
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.
|
|
|
|
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}}`
|
|
}
|
|
|
|
tmpl, err := template.New("-format").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() }
|