github.com/april1989/origin-go-tools@v0.0.32/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/april1989/origin-go-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/april1989/origin-go-tools/go/buildutil" 36 "github.com/april1989/origin-go-tools/go/callgraph" 37 "github.com/april1989/origin-go-tools/go/callgraph/cha" 38 "github.com/april1989/origin-go-tools/go/callgraph/rta" 39 "github.com/april1989/origin-go-tools/go/callgraph/static" 40 "github.com/april1989/origin-go-tools/go/packages" 41 "github.com/april1989/origin-go-tools/go/pointer" 42 "github.com/april1989/origin-go-tools/go/ssa" 43 "github.com/april1989/origin-go-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=...] 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 github.com/april1989/origin-go-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 github.com/april1989/origin-go-tools/cmd/callgraph | 142 digraph succs github.com/april1989/origin-go-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.Fprintln(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 golibexec_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() }