github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/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 "golang.org/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  	"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/loader"
    41  	"golang.org/x/tools/go/pointer"
    42  	"golang.org/x/tools/go/ssa"
    43  	"golang.org/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 the call graph of a Go program.
    67  
    68  Usage:
    69  
    70    callgraph [-algo=static|cha|rta|pta] [-test] [-format=...] <args>...
    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.Object.Path yields the
   118             import path of the enclosing package.  Consult the go/ssa
   119             API documentation for details.
   120  
   121  ` + loader.FromArgsUsage + `
   122  
   123  Examples:
   124  
   125    Show the call graph of the trivial web server application:
   126  
   127      callgraph -format digraph $GOROOT/src/net/http/triv.go
   128  
   129    Same, but show only the packages of each function:
   130  
   131      callgraph -format '{{.Caller.Pkg.Object.Path}} -> {{.Callee.Pkg.Object.Path}}' \
   132        $GOROOT/src/net/http/triv.go | sort | uniq
   133  
   134    Show functions that make dynamic calls into the 'fmt' test package,
   135    using the pointer analysis algorithm:
   136  
   137      callgraph -format='{{.Caller}} -{{.Dynamic}}-> {{.Callee}}' -test -algo=pta fmt |
   138        sed -ne 's/-dynamic-/--/p' |
   139        sed -ne 's/-->.*fmt_test.*$//p' | sort | uniq
   140  
   141    Show all functions directly called by the callgraph tool's main function:
   142  
   143      callgraph -format=digraph golang.org/x/tools/cmd/callgraph |
   144        digraph succs golang.org/x/tools/cmd/callgraph.main
   145  `
   146  
   147  func init() {
   148  	// If $GOMAXPROCS isn't set, use the full capacity of the machine.
   149  	// For small machines, use at least 4 threads.
   150  	if os.Getenv("GOMAXPROCS") == "" {
   151  		n := runtime.NumCPU()
   152  		if n < 4 {
   153  			n = 4
   154  		}
   155  		runtime.GOMAXPROCS(n)
   156  	}
   157  }
   158  
   159  func main() {
   160  	flag.Parse()
   161  	if err := doCallgraph(&build.Default, *algoFlag, *formatFlag, *testFlag, flag.Args()); err != nil {
   162  		fmt.Fprintf(os.Stderr, "callgraph: %s\n", err)
   163  		os.Exit(1)
   164  	}
   165  }
   166  
   167  var stdout io.Writer = os.Stdout
   168  
   169  func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []string) error {
   170  	conf := loader.Config{Build: ctxt}
   171  
   172  	if len(args) == 0 {
   173  		fmt.Fprintln(os.Stderr, Usage)
   174  		return nil
   175  	}
   176  
   177  	// Use the initial packages from the command line.
   178  	args, err := conf.FromArgs(args, tests)
   179  	if err != nil {
   180  		return err
   181  	}
   182  
   183  	// Load, parse and type-check the whole program.
   184  	iprog, err := conf.Load()
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	// Create and build SSA-form program representation.
   190  	prog := ssautil.CreateProgram(iprog, 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  		main, err := mainPackage(prog, tests)
   225  		if err != nil {
   226  			return err
   227  		}
   228  		config := &pointer.Config{
   229  			Mains:          []*ssa.Package{main},
   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  		main, err := mainPackage(prog, tests)
   241  		if err != nil {
   242  			return err
   243  		}
   244  		roots := []*ssa.Function{
   245  			main.Func("init"),
   246  			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  // mainPackage returns the main package to analyze.
   307  // The resulting package has a main() function.
   308  func mainPackage(prog *ssa.Program, tests bool) (*ssa.Package, error) {
   309  	pkgs := prog.AllPackages()
   310  
   311  	// TODO(adonovan): allow independent control over tests, mains and libraries.
   312  	// TODO(adonovan): put this logic in a library; we keep reinventing it.
   313  
   314  	if tests {
   315  		// If -test, use all packages' tests.
   316  		if len(pkgs) > 0 {
   317  			if main := prog.CreateTestMainPackage(pkgs...); main != nil {
   318  				return main, nil
   319  			}
   320  		}
   321  		return nil, fmt.Errorf("no tests")
   322  	}
   323  
   324  	// Otherwise, use the first package named main.
   325  	for _, pkg := range pkgs {
   326  		if pkg.Pkg.Name() == "main" {
   327  			if pkg.Func("main") == nil {
   328  				return nil, fmt.Errorf("no func main() in main package")
   329  			}
   330  			return pkg, nil
   331  		}
   332  	}
   333  
   334  	return nil, fmt.Errorf("no main package")
   335  }
   336  
   337  type Edge struct {
   338  	Caller *ssa.Function
   339  	Callee *ssa.Function
   340  
   341  	edge     *callgraph.Edge
   342  	fset     *token.FileSet
   343  	position token.Position // initialized lazily
   344  }
   345  
   346  func (e *Edge) pos() *token.Position {
   347  	if e.position.Offset == -1 {
   348  		e.position = e.fset.Position(e.edge.Pos()) // called lazily
   349  	}
   350  	return &e.position
   351  }
   352  
   353  func (e *Edge) Filename() string { return e.pos().Filename }
   354  func (e *Edge) Column() int      { return e.pos().Column }
   355  func (e *Edge) Line() int        { return e.pos().Line }
   356  func (e *Edge) Offset() int      { return e.pos().Offset }
   357  
   358  func (e *Edge) Dynamic() string {
   359  	if e.edge.Site != nil && e.edge.Site.Common().StaticCallee() == nil {
   360  		return "dynamic"
   361  	}
   362  	return "static"
   363  }
   364  
   365  func (e *Edge) Description() string { return e.edge.Description() }