1
0
mirror of https://github.com/golang/go synced 2024-11-18 11:14:39 -07:00
go/cmd/callgraph/main.go
Alan Donovan aa46a01996 go/ssa/ssautil: add AllPackages method
In go1.10, go/packages falls back to loading all packages
from source but not typechecking function bodies for imports.
The ssautil.Packages function would nonetheless provide
the partially-typed ASTs to the SSA builder, which would crash.
Now Packages only passes syntax trees to the SSA builder for
the initial packages, which are the only ones guaranteed to be
fully typed.

It is impossible to discern whether the caller of Packages intends to
build SSA code for dependencies, as in some clients such as
cmd/callgraph, so we add a new function, AllPackages, that expresses
this intent.

Fixes golang/go#28106

Change-Id: I6a88b7c7545e9de90b61f5bee0e6de3d2e21b548
Reviewed-on: https://go-review.googlesource.com/c/141686
Reviewed-by: Michael Matloob <matloob@golang.org>
Run-TryBot: Michael Matloob <matloob@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-10-15 18:41:29 +00:00

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.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() }