golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/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 "bytes" 24 "flag" 25 "fmt" 26 "go/build" 27 "go/token" 28 "io" 29 "os" 30 "runtime" 31 "text/template" 32 33 "golang.org/x/tools/go/buildutil" 34 "golang.org/x/tools/go/callgraph" 35 "golang.org/x/tools/go/callgraph/cha" 36 "golang.org/x/tools/go/callgraph/rta" 37 "golang.org/x/tools/go/callgraph/static" 38 "golang.org/x/tools/go/callgraph/vta" 39 "golang.org/x/tools/go/packages" 40 "golang.org/x/tools/go/ssa" 41 "golang.org/x/tools/go/ssa/ssautil" 42 ) 43 44 // flags 45 var ( 46 algoFlag = flag.String("algo", "rta", 47 `Call graph construction algorithm (static, cha, rta, vta)`) 48 49 testFlag = flag.Bool("test", false, 50 "Loads test code (*_test.go) for imported packages") 51 52 formatFlag = flag.String("format", 53 "{{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}", 54 "A template expression specifying how to format an edge") 55 ) 56 57 func init() { 58 flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc) 59 } 60 61 const Usage = `callgraph: display the call graph of a Go program. 62 63 Usage: 64 65 callgraph [-algo=static|cha|rta|vta] [-test] [-format=...] package... 66 67 Flags: 68 69 -algo Specifies the call-graph construction algorithm, one of: 70 71 static static calls only (unsound) 72 cha Class Hierarchy Analysis 73 rta Rapid Type Analysis 74 vta Variable Type Analysis 75 76 The algorithms are ordered by increasing precision in their 77 treatment of dynamic calls (and thus also computational cost). 78 RTA requires a whole program (main or test), and 79 include only functions reachable from main. 80 81 -test Include the package's tests in the analysis. 82 83 -format Specifies the format in which each call graph edge is displayed. 84 One of: 85 86 digraph output suitable for input to 87 golang.org/x/tools/cmd/digraph. 88 graphviz output in AT&T GraphViz (.dot) format. 89 90 All other values are interpreted using text/template syntax. 91 The default value is: 92 93 {{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}} 94 95 The structure passed to the template is (effectively): 96 97 type Edge struct { 98 Caller *ssa.Function // calling function 99 Callee *ssa.Function // called function 100 101 // Call site: 102 Filename string // containing file 103 Offset int // offset within file of '(' 104 Line int // line number 105 Column int // column number of call 106 Dynamic string // "static" or "dynamic" 107 Description string // e.g. "static method call" 108 } 109 110 Caller and Callee are *ssa.Function values, which print as 111 "(*sync/atomic.Mutex).Lock", but other attributes may be 112 derived from them. For example: 113 114 - {{.Caller.Pkg.Pkg.Path}} yields the import path of the 115 enclosing package; and 116 117 - {{(.Caller.Prog.Fset.Position .Caller.Pos).Filename}} 118 yields the name of the file that declares the caller. 119 120 - The 'posn' template function returns the token.Position 121 of an ssa.Function, so the previous example can be 122 reduced to {{(posn .Caller).Filename}}. 123 124 Consult the documentation for go/token, text/template, and 125 golang.org/x/tools/go/ssa for more detail. 126 127 Examples: 128 129 Show the call graph of the trivial web server application: 130 131 callgraph -format digraph $GOROOT/src/net/http/triv.go 132 133 Same, but show only the packages of each function: 134 135 callgraph -format '{{.Caller.Pkg.Pkg.Path}} -> {{.Callee.Pkg.Pkg.Path}}' \ 136 $GOROOT/src/net/http/triv.go | sort | uniq 137 138 Show functions that make dynamic calls into the 'fmt' test package, 139 using the Rapid Type Analysis algorithm: 140 141 callgraph -format='{{.Caller}} -{{.Dynamic}}-> {{.Callee}}' -test -algo=rta fmt | 142 sed -ne 's/-dynamic-/--/p' | 143 sed -ne 's/-->.*fmt_test.*$//p' | sort | uniq 144 145 Show all functions directly called by the callgraph tool's main function: 146 147 callgraph -format=digraph golang.org/x/tools/cmd/callgraph | 148 digraph succs golang.org/x/tools/cmd/callgraph.main 149 ` 150 151 func init() { 152 // If $GOMAXPROCS isn't set, use the full capacity of the machine. 153 // For small machines, use at least 4 threads. 154 if os.Getenv("GOMAXPROCS") == "" { 155 n := runtime.NumCPU() 156 if n < 4 { 157 n = 4 158 } 159 runtime.GOMAXPROCS(n) 160 } 161 } 162 163 func main() { 164 flag.Parse() 165 if err := doCallgraph("", "", *algoFlag, *formatFlag, *testFlag, flag.Args()); err != nil { 166 fmt.Fprintf(os.Stderr, "callgraph: %s\n", err) 167 os.Exit(1) 168 } 169 } 170 171 var stdout io.Writer = os.Stdout 172 173 func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) error { 174 if len(args) == 0 { 175 fmt.Fprint(os.Stderr, Usage) 176 return nil 177 } 178 179 cfg := &packages.Config{ 180 Mode: packages.LoadAllSyntax, 181 Tests: tests, 182 Dir: dir, 183 } 184 if gopath != "" { 185 cfg.Env = append(os.Environ(), "GOPATH="+gopath) // to enable testing 186 } 187 initial, err := packages.Load(cfg, args...) 188 if err != nil { 189 return err 190 } 191 if packages.PrintErrors(initial) > 0 { 192 return fmt.Errorf("packages contain errors") 193 } 194 195 // Create and build SSA-form program representation. 196 mode := ssa.InstantiateGenerics // instantiate generics by default for soundness 197 prog, pkgs := ssautil.AllPackages(initial, mode) 198 prog.Build() 199 200 // -- call graph construction ------------------------------------------ 201 202 var cg *callgraph.Graph 203 204 switch algo { 205 case "static": 206 cg = static.CallGraph(prog) 207 208 case "cha": 209 cg = cha.CallGraph(prog) 210 211 case "pta": 212 return fmt.Errorf("pointer analysis is no longer supported (see Go issue #59676)") 213 214 case "rta": 215 mains, err := mainPackages(pkgs) 216 if err != nil { 217 return err 218 } 219 var roots []*ssa.Function 220 for _, main := range mains { 221 roots = append(roots, main.Func("init"), main.Func("main")) 222 } 223 rtares := rta.Analyze(roots, true) 224 cg = rtares.CallGraph 225 226 // NB: RTA gives us Reachable and RuntimeTypes too. 227 228 case "vta": 229 cg = vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog)) 230 231 default: 232 return fmt.Errorf("unknown algorithm: %s", algo) 233 } 234 235 cg.DeleteSyntheticNodes() 236 237 // -- output------------------------------------------------------------ 238 239 var before, after string 240 241 // Pre-canned formats. 242 switch format { 243 case "digraph": 244 format = `{{printf "%q %q" .Caller .Callee}}` 245 246 case "graphviz": 247 before = "digraph callgraph {\n" 248 after = "}\n" 249 format = ` {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}` 250 } 251 252 funcMap := template.FuncMap{ 253 "posn": func(f *ssa.Function) token.Position { 254 return f.Prog.Fset.Position(f.Pos()) 255 }, 256 } 257 tmpl, err := template.New("-format").Funcs(funcMap).Parse(format) 258 if err != nil { 259 return fmt.Errorf("invalid -format template: %v", err) 260 } 261 262 // Allocate these once, outside the traversal. 263 var buf bytes.Buffer 264 data := Edge{fset: prog.Fset} 265 266 fmt.Fprint(stdout, before) 267 if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error { 268 data.position.Offset = -1 269 data.edge = edge 270 data.Caller = edge.Caller.Func 271 data.Callee = edge.Callee.Func 272 273 buf.Reset() 274 if err := tmpl.Execute(&buf, &data); err != nil { 275 return err 276 } 277 stdout.Write(buf.Bytes()) 278 if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' { 279 fmt.Fprintln(stdout) 280 } 281 return nil 282 }); err != nil { 283 return err 284 } 285 fmt.Fprint(stdout, after) 286 return nil 287 } 288 289 // mainPackages returns the main packages to analyze. 290 // Each resulting package is named "main" and has a main function. 291 func mainPackages(pkgs []*ssa.Package) ([]*ssa.Package, error) { 292 var mains []*ssa.Package 293 for _, p := range pkgs { 294 if p != nil && p.Pkg.Name() == "main" && p.Func("main") != nil { 295 mains = append(mains, p) 296 } 297 } 298 if len(mains) == 0 { 299 return nil, fmt.Errorf("no main packages") 300 } 301 return mains, nil 302 } 303 304 type Edge struct { 305 Caller *ssa.Function 306 Callee *ssa.Function 307 308 edge *callgraph.Edge 309 fset *token.FileSet 310 position token.Position // initialized lazily 311 } 312 313 func (e *Edge) pos() *token.Position { 314 if e.position.Offset == -1 { 315 e.position = e.fset.Position(e.edge.Pos()) // called lazily 316 } 317 return &e.position 318 } 319 320 func (e *Edge) Filename() string { return e.pos().Filename } 321 func (e *Edge) Column() int { return e.pos().Column } 322 func (e *Edge) Line() int { return e.pos().Line } 323 func (e *Edge) Offset() int { return e.pos().Offset } 324 325 func (e *Edge) Dynamic() string { 326 if e.edge.Site != nil && e.edge.Site.Common().StaticCallee() == nil { 327 return "dynamic" 328 } 329 return "static" 330 } 331 332 func (e *Edge) Description() string { return e.edge.Description() }