github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/cmd/callgraph/main.go (about)

     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.
     4  
     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 "github.com/jhump/golang-x-tools/cmd/callgraph"
     8  
     9  // TODO(adonovan):
    10  //
    11  // Features:
    12  // - restrict graph to a single package
    13  // - output
    14  //   - functions reachable from root (use digraph tool?)
    15  //   - unreachable functions (use digraph tool?)
    16  //   - dynamic (runtime) types
    17  //   - indexed output (numbered nodes)
    18  //   - JSON output
    19  //   - additional template fields:
    20  //     callee file/line/col
    21  
    22  import (
    23  	"bufio"
    24  	"bytes"
    25  	"flag"
    26  	"fmt"
    27  	"go/build"
    28  	"go/token"
    29  	"io"
    30  	"log"
    31  	"os"
    32  	"runtime"
    33  	"text/template"
    34  
    35  	"github.com/jhump/golang-x-tools/go/buildutil"
    36  	"github.com/jhump/golang-x-tools/go/callgraph"
    37  	"github.com/jhump/golang-x-tools/go/callgraph/cha"
    38  	"github.com/jhump/golang-x-tools/go/callgraph/rta"
    39  	"github.com/jhump/golang-x-tools/go/callgraph/static"
    40  	"github.com/jhump/golang-x-tools/go/packages"
    41  	"github.com/jhump/golang-x-tools/go/pointer"
    42  	"github.com/jhump/golang-x-tools/go/ssa"
    43  	"github.com/jhump/golang-x-tools/go/ssa/ssautil"
    44  )
    45  
    46  // flags
    47  var (
    48  	algoFlag = flag.String("algo", "rta",
    49  		`Call graph construction algorithm (static, cha, rta, pta)`)
    50  
    51  	testFlag = flag.Bool("test", false,
    52  		"Loads test code (*_test.go) for imported packages")
    53  
    54  	formatFlag = flag.String("format",
    55  		"{{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}",
    56  		"A template expression specifying how to format an edge")
    57  
    58  	ptalogFlag = flag.String("ptalog", "",
    59  		"Location of the points-to analysis log file, or empty to disable logging.")
    60  )
    61  
    62  func init() {
    63  	flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
    64  }
    65  
    66  const Usage = `callgraph: display the call graph of a Go program.
    67  
    68  Usage:
    69  
    70    callgraph [-algo=static|cha|rta|pta] [-test] [-format=...] package...
    71  
    72  Flags:
    73  
    74  -algo      Specifies the call-graph construction algorithm, one of:
    75  
    76              static      static calls only (unsound)
    77              cha         Class Hierarchy Analysis
    78              rta         Rapid Type Analysis
    79              pta         inclusion-based Points-To Analysis
    80  
    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.
    85  
    86  -test      Include the package's tests in the analysis.
    87  
    88  -format    Specifies the format in which each call graph edge is displayed.
    89             One of:
    90  
    91              digraph     output suitable for input to
    92                          golang.org/x/tools/cmd/digraph.
    93              graphviz    output in AT&T GraphViz (.dot) format.
    94  
    95             All other values are interpreted using text/template syntax.
    96             The default value is:
    97  
    98              {{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}
    99  
   100             The structure passed to the template is (effectively):
   101  
   102                     type Edge struct {
   103                             Caller      *ssa.Function // calling function
   104                             Callee      *ssa.Function // called function
   105  
   106                             // Call site:
   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"
   113                     }
   114  
   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.
   120  
   121  Examples:
   122  
   123    Show the call graph of the trivial web server application:
   124  
   125      callgraph -format digraph $GOROOT/src/net/http/triv.go
   126  
   127    Same, but show only the packages of each function:
   128  
   129      callgraph -format '{{.Caller.Pkg.Pkg.Path}} -> {{.Callee.Pkg.Pkg.Path}}' \
   130        $GOROOT/src/net/http/triv.go | sort | uniq
   131  
   132    Show functions that make dynamic calls into the 'fmt' test package,
   133    using the pointer analysis algorithm:
   134  
   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
   138  
   139    Show all functions directly called by the callgraph tool's main function:
   140  
   141      callgraph -format=digraph golang.org/x/tools/cmd/callgraph |
   142        digraph succs golang.org/x/tools/cmd/callgraph.main
   143  `
   144  
   145  func init() {
   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()
   150  		if n < 4 {
   151  			n = 4
   152  		}
   153  		runtime.GOMAXPROCS(n)
   154  	}
   155  }
   156  
   157  func main() {
   158  	flag.Parse()
   159  	if err := doCallgraph("", "", *algoFlag, *formatFlag, *testFlag, flag.Args()); err != nil {
   160  		fmt.Fprintf(os.Stderr, "callgraph: %s\n", err)
   161  		os.Exit(1)
   162  	}
   163  }
   164  
   165  var stdout io.Writer = os.Stdout
   166  
   167  func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) error {
   168  	if len(args) == 0 {
   169  		fmt.Fprint(os.Stderr, Usage)
   170  		return nil
   171  	}
   172  
   173  	cfg := &packages.Config{
   174  		Mode:  packages.LoadAllSyntax,
   175  		Tests: tests,
   176  		Dir:   dir,
   177  	}
   178  	if gopath != "" {
   179  		cfg.Env = append(os.Environ(), "GOPATH="+gopath) // to enable testing
   180  	}
   181  	initial, err := packages.Load(cfg, args...)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	if packages.PrintErrors(initial) > 0 {
   186  		return fmt.Errorf("packages contain errors")
   187  	}
   188  
   189  	// Create and build SSA-form program representation.
   190  	prog, pkgs := ssautil.AllPackages(initial, 0)
   191  	prog.Build()
   192  
   193  	// -- call graph construction ------------------------------------------
   194  
   195  	var cg *callgraph.Graph
   196  
   197  	switch algo {
   198  	case "static":
   199  		cg = static.CallGraph(prog)
   200  
   201  	case "cha":
   202  		cg = cha.CallGraph(prog)
   203  
   204  	case "pta":
   205  		// Set up points-to analysis log file.
   206  		var ptalog io.Writer
   207  		if *ptalogFlag != "" {
   208  			if f, err := os.Create(*ptalogFlag); err != nil {
   209  				log.Fatalf("Failed to create PTA log file: %s", err)
   210  			} else {
   211  				buf := bufio.NewWriter(f)
   212  				ptalog = buf
   213  				defer func() {
   214  					if err := buf.Flush(); err != nil {
   215  						log.Printf("flush: %s", err)
   216  					}
   217  					if err := f.Close(); err != nil {
   218  						log.Printf("close: %s", err)
   219  					}
   220  				}()
   221  			}
   222  		}
   223  
   224  		mains, err := mainPackages(pkgs)
   225  		if err != nil {
   226  			return err
   227  		}
   228  		config := &pointer.Config{
   229  			Mains:          mains,
   230  			BuildCallGraph: true,
   231  			Log:            ptalog,
   232  		}
   233  		ptares, err := pointer.Analyze(config)
   234  		if err != nil {
   235  			return err // internal error in pointer analysis
   236  		}
   237  		cg = ptares.CallGraph
   238  
   239  	case "rta":
   240  		mains, err := mainPackages(pkgs)
   241  		if err != nil {
   242  			return err
   243  		}
   244  		var roots []*ssa.Function
   245  		for _, main := range mains {
   246  			roots = append(roots, main.Func("init"), main.Func("main"))
   247  		}
   248  		rtares := rta.Analyze(roots, true)
   249  		cg = rtares.CallGraph
   250  
   251  		// NB: RTA gives us Reachable and RuntimeTypes too.
   252  
   253  	default:
   254  		return fmt.Errorf("unknown algorithm: %s", algo)
   255  	}
   256  
   257  	cg.DeleteSyntheticNodes()
   258  
   259  	// -- output------------------------------------------------------------
   260  
   261  	var before, after string
   262  
   263  	// Pre-canned formats.
   264  	switch format {
   265  	case "digraph":
   266  		format = `{{printf "%q %q" .Caller .Callee}}`
   267  
   268  	case "graphviz":
   269  		before = "digraph callgraph {\n"
   270  		after = "}\n"
   271  		format = `  {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}`
   272  	}
   273  
   274  	tmpl, err := template.New("-format").Parse(format)
   275  	if err != nil {
   276  		return fmt.Errorf("invalid -format template: %v", err)
   277  	}
   278  
   279  	// Allocate these once, outside the traversal.
   280  	var buf bytes.Buffer
   281  	data := Edge{fset: prog.Fset}
   282  
   283  	fmt.Fprint(stdout, before)
   284  	if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
   285  		data.position.Offset = -1
   286  		data.edge = edge
   287  		data.Caller = edge.Caller.Func
   288  		data.Callee = edge.Callee.Func
   289  
   290  		buf.Reset()
   291  		if err := tmpl.Execute(&buf, &data); err != nil {
   292  			return err
   293  		}
   294  		stdout.Write(buf.Bytes())
   295  		if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' {
   296  			fmt.Fprintln(stdout)
   297  		}
   298  		return nil
   299  	}); err != nil {
   300  		return err
   301  	}
   302  	fmt.Fprint(stdout, after)
   303  	return nil
   304  }
   305  
   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)
   313  		}
   314  	}
   315  	if len(mains) == 0 {
   316  		return nil, fmt.Errorf("no main packages")
   317  	}
   318  	return mains, nil
   319  }
   320  
   321  type Edge struct {
   322  	Caller *ssa.Function
   323  	Callee *ssa.Function
   324  
   325  	edge     *callgraph.Edge
   326  	fset     *token.FileSet
   327  	position token.Position // initialized lazily
   328  }
   329  
   330  func (e *Edge) pos() *token.Position {
   331  	if e.position.Offset == -1 {
   332  		e.position = e.fset.Position(e.edge.Pos()) // called lazily
   333  	}
   334  	return &e.position
   335  }
   336  
   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 }
   341  
   342  func (e *Edge) Dynamic() string {
   343  	if e.edge.Site != nil && e.edge.Site.Common().StaticCallee() == nil {
   344  		return "dynamic"
   345  	}
   346  	return "static"
   347  }
   348  
   349  func (e *Edge) Description() string { return e.edge.Description() }