350 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			350 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
| // 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.Packages(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() }
 |