1 // Copyright 2014 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // callgraph: a tool for reporting the call graph of a Go program.
6 // See Usage for details, or run with -help.
7 package main // import "golang.org/x/tools/cmd/callgraph"
12 // - restrict graph to a single package
14 // - functions reachable from root (use digraph tool?)
15 // - unreachable functions (use digraph tool?)
16 // - dynamic (runtime) types
17 // - indexed output (numbered nodes)
19 // - additional template fields:
20 // callee file/line/col
35 "golang.org/x/tools/go/buildutil"
36 "golang.org/x/tools/go/callgraph"
37 "golang.org/x/tools/go/callgraph/cha"
38 "golang.org/x/tools/go/callgraph/rta"
39 "golang.org/x/tools/go/callgraph/static"
40 "golang.org/x/tools/go/packages"
41 "golang.org/x/tools/go/pointer"
42 "golang.org/x/tools/go/ssa"
43 "golang.org/x/tools/go/ssa/ssautil"
48 algoFlag = flag.String("algo", "rta",
49 `Call graph construction algorithm (static, cha, rta, pta)`)
51 testFlag = flag.Bool("test", false,
52 "Loads test code (*_test.go) for imported packages")
54 formatFlag = flag.String("format",
55 "{{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}",
56 "A template expression specifying how to format an edge")
58 ptalogFlag = flag.String("ptalog", "",
59 "Location of the points-to analysis log file, or empty to disable logging.")
63 flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
66 const Usage = `callgraph: display the call graph of a Go program.
70 callgraph [-algo=static|cha|rta|pta] [-test] [-format=...] package...
74 -algo Specifies the call-graph construction algorithm, one of:
76 static static calls only (unsound)
77 cha Class Hierarchy Analysis
78 rta Rapid Type Analysis
79 pta inclusion-based Points-To Analysis
81 The algorithms are ordered by increasing precision in their
82 treatment of dynamic calls (and thus also computational cost).
83 RTA and PTA require a whole program (main or test), and
84 include only functions reachable from main.
86 -test Include the package's tests in the analysis.
88 -format Specifies the format in which each call graph edge is displayed.
91 digraph output suitable for input to
92 golang.org/x/tools/cmd/digraph.
93 graphviz output in AT&T GraphViz (.dot) format.
95 All other values are interpreted using text/template syntax.
98 {{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}
100 The structure passed to the template is (effectively):
103 Caller *ssa.Function // calling function
104 Callee *ssa.Function // called function
107 Filename string // containing file
108 Offset int // offset within file of '('
109 Line int // line number
110 Column int // column number of call
111 Dynamic string // "static" or "dynamic"
112 Description string // e.g. "static method call"
115 Caller and Callee are *ssa.Function values, which print as
116 "(*sync/atomic.Mutex).Lock", but other attributes may be
117 derived from them, e.g. Caller.Pkg.Pkg.Path yields the
118 import path of the enclosing package. Consult the go/ssa
119 API documentation for details.
123 Show the call graph of the trivial web server application:
125 callgraph -format digraph $GOROOT/src/net/http/triv.go
127 Same, but show only the packages of each function:
129 callgraph -format '{{.Caller.Pkg.Pkg.Path}} -> {{.Callee.Pkg.Pkg.Path}}' \
130 $GOROOT/src/net/http/triv.go | sort | uniq
132 Show functions that make dynamic calls into the 'fmt' test package,
133 using the pointer analysis algorithm:
135 callgraph -format='{{.Caller}} -{{.Dynamic}}-> {{.Callee}}' -test -algo=pta fmt |
136 sed -ne 's/-dynamic-/--/p' |
137 sed -ne 's/-->.*fmt_test.*$//p' | sort | uniq
139 Show all functions directly called by the callgraph tool's main function:
141 callgraph -format=digraph golang.org/x/tools/cmd/callgraph |
142 digraph succs golang.org/x/tools/cmd/callgraph.main
146 // If $GOMAXPROCS isn't set, use the full capacity of the machine.
147 // For small machines, use at least 4 threads.
148 if os.Getenv("GOMAXPROCS") == "" {
149 n := runtime.NumCPU()
153 runtime.GOMAXPROCS(n)
159 if err := doCallgraph("", "", *algoFlag, *formatFlag, *testFlag, flag.Args()); err != nil {
160 fmt.Fprintf(os.Stderr, "callgraph: %s\n", err)
165 var stdout io.Writer = os.Stdout
167 func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) error {
169 fmt.Fprintln(os.Stderr, Usage)
173 cfg := &packages.Config{
174 Mode: packages.LoadAllSyntax,
179 cfg.Env = append(os.Environ(), "GOPATH="+gopath) // to enable testing
181 initial, err := packages.Load(cfg, args...)
185 if packages.PrintErrors(initial) > 0 {
186 return fmt.Errorf("packages contain errors")
189 // Create and build SSA-form program representation.
190 prog, pkgs := ssautil.AllPackages(initial, 0)
193 // -- call graph construction ------------------------------------------
195 var cg *callgraph.Graph
199 cg = static.CallGraph(prog)
202 cg = cha.CallGraph(prog)
205 // Set up points-to analysis log file.
207 if *ptalogFlag != "" {
208 if f, err := os.Create(*ptalogFlag); err != nil {
209 log.Fatalf("Failed to create PTA log file: %s", err)
211 buf := bufio.NewWriter(f)
214 if err := buf.Flush(); err != nil {
215 log.Printf("flush: %s", err)
217 if err := f.Close(); err != nil {
218 log.Printf("close: %s", err)
224 mains, err := mainPackages(pkgs)
228 config := &pointer.Config{
230 BuildCallGraph: true,
233 ptares, err := pointer.Analyze(config)
235 return err // internal error in pointer analysis
237 cg = ptares.CallGraph
240 mains, err := mainPackages(pkgs)
244 var roots []*ssa.Function
245 for _, main := range mains {
246 roots = append(roots, main.Func("init"), main.Func("main"))
248 rtares := rta.Analyze(roots, true)
249 cg = rtares.CallGraph
251 // NB: RTA gives us Reachable and RuntimeTypes too.
254 return fmt.Errorf("unknown algorithm: %s", algo)
257 cg.DeleteSyntheticNodes()
259 // -- output------------------------------------------------------------
261 var before, after string
263 // Pre-canned formats.
266 format = `{{printf "%q %q" .Caller .Callee}}`
269 before = "digraph callgraph {\n"
271 format = ` {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}`
274 tmpl, err := template.New("-format").Parse(format)
276 return fmt.Errorf("invalid -format template: %v", err)
279 // Allocate these once, outside the traversal.
281 data := Edge{fset: prog.Fset}
283 fmt.Fprint(stdout, before)
284 if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
285 data.position.Offset = -1
287 data.Caller = edge.Caller.Func
288 data.Callee = edge.Callee.Func
291 if err := tmpl.Execute(&buf, &data); err != nil {
294 stdout.Write(buf.Bytes())
295 if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' {
302 fmt.Fprint(stdout, after)
306 // mainPackages returns the main packages to analyze.
307 // Each resulting package is named "main" and has a main function.
308 func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) {
309 var mains []*ssa.Package
310 for _, p := range pkgs {
311 if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil {
312 mains = append(mains, p)
316 return nil, fmt.Errorf("no main packages")
327 position token.Position // initialized lazily
330 func (e *Edge) pos() *token.Position {
331 if e.position.Offset == -1 {
332 e.position = e.fset.Position(e.edge.Pos()) // called lazily
337 func (e *Edge) Filename() string { return e.pos().Filename }
338 func (e *Edge) Column() int { return e.pos().Column }
339 func (e *Edge) Line() int { return e.pos().Line }
340 func (e *Edge) Offset() int { return e.pos().Offset }
342 func (e *Edge) Dynamic() string {
343 if e.edge.Site != nil && e.edge.Site.Common().StaticCallee() == nil {
349 func (e *Edge) Description() string { return e.edge.Description() }