| // 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 |
| } |
| if packages.PrintErrors(initial) > 0 { |
| return fmt.Errorf("packages contain errors") |
| } |
| |
| // Create and build SSA-form program representation. |
| prog, pkgs := ssautil.AllPackages(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() } |