+++ /dev/null
-// 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 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() }