You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

361 lines
9.7 KiB

  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. // callgraph: a tool for reporting the call graph of a Go program.
  5. // See Usage for details, or run with -help.
  6. package main // import "golang.org/x/tools/cmd/callgraph"
  7. // TODO(adonovan):
  8. //
  9. // Features:
  10. // - restrict graph to a single package
  11. // - output
  12. // - functions reachable from root (use digraph tool?)
  13. // - unreachable functions (use digraph tool?)
  14. // - dynamic (runtime) types
  15. // - indexed output (numbered nodes)
  16. // - JSON output
  17. // - additional template fields:
  18. // callee file/line/col
  19. import (
  20. "bufio"
  21. "bytes"
  22. "flag"
  23. "fmt"
  24. "go/build"
  25. "go/token"
  26. "io"
  27. "log"
  28. "os"
  29. "runtime"
  30. "text/template"
  31. "golang.org/x/tools/go/buildutil"
  32. "golang.org/x/tools/go/callgraph"
  33. "golang.org/x/tools/go/callgraph/cha"
  34. "golang.org/x/tools/go/callgraph/rta"
  35. "golang.org/x/tools/go/callgraph/static"
  36. "golang.org/x/tools/go/loader"
  37. "golang.org/x/tools/go/pointer"
  38. "golang.org/x/tools/go/ssa"
  39. "golang.org/x/tools/go/ssa/ssautil"
  40. )
  41. // flags
  42. var (
  43. algoFlag = flag.String("algo", "rta",
  44. `Call graph construction algorithm (static, cha, rta, pta)`)
  45. testFlag = flag.Bool("test", false,
  46. "Loads test code (*_test.go) for imported packages")
  47. formatFlag = flag.String("format",
  48. "{{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}",
  49. "A template expression specifying how to format an edge")
  50. ptalogFlag = flag.String("ptalog", "",
  51. "Location of the points-to analysis log file, or empty to disable logging.")
  52. )
  53. func init() {
  54. flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
  55. }
  56. const Usage = `callgraph: display the the call graph of a Go program.
  57. Usage:
  58. callgraph [-algo=static|cha|rta|pta] [-test] [-format=...] <args>...
  59. Flags:
  60. -algo Specifies the call-graph construction algorithm, one of:
  61. static static calls only (unsound)
  62. cha Class Hierarchy Analysis
  63. rta Rapid Type Analysis
  64. pta inclusion-based Points-To Analysis
  65. The algorithms are ordered by increasing precision in their
  66. treatment of dynamic calls (and thus also computational cost).
  67. RTA and PTA require a whole program (main or test), and
  68. include only functions reachable from main.
  69. -test Include the package's tests in the analysis.
  70. -format Specifies the format in which each call graph edge is displayed.
  71. One of:
  72. digraph output suitable for input to
  73. golang.org/x/tools/cmd/digraph.
  74. graphviz output in AT&T GraphViz (.dot) format.
  75. All other values are interpreted using text/template syntax.
  76. The default value is:
  77. {{.Caller}}\t--{{.Dynamic}}-{{.Line}}:{{.Column}}-->\t{{.Callee}}
  78. The structure passed to the template is (effectively):
  79. type Edge struct {
  80. Caller *ssa.Function // calling function
  81. Callee *ssa.Function // called function
  82. // Call site:
  83. Filename string // containing file
  84. Offset int // offset within file of '('
  85. Line int // line number
  86. Column int // column number of call
  87. Dynamic string // "static" or "dynamic"
  88. Description string // e.g. "static method call"
  89. }
  90. Caller and Callee are *ssa.Function values, which print as
  91. "(*sync/atomic.Mutex).Lock", but other attributes may be
  92. derived from them, e.g. Caller.Pkg.Pkg.Path yields the
  93. import path of the enclosing package. Consult the go/ssa
  94. API documentation for details.
  95. ` + loader.FromArgsUsage + `
  96. Examples:
  97. Show the call graph of the trivial web server application:
  98. callgraph -format digraph $GOROOT/src/net/http/triv.go
  99. Same, but show only the packages of each function:
  100. callgraph -format '{{.Caller.Pkg.Pkg.Path}} -> {{.Callee.Pkg.Pkg.Path}}' \
  101. $GOROOT/src/net/http/triv.go | sort | uniq
  102. Show functions that make dynamic calls into the 'fmt' test package,
  103. using the pointer analysis algorithm:
  104. callgraph -format='{{.Caller}} -{{.Dynamic}}-> {{.Callee}}' -test -algo=pta fmt |
  105. sed -ne 's/-dynamic-/--/p' |
  106. sed -ne 's/-->.*fmt_test.*$//p' | sort | uniq
  107. Show all functions directly called by the callgraph tool's main function:
  108. callgraph -format=digraph golang.org/x/tools/cmd/callgraph |
  109. digraph succs golang.org/x/tools/cmd/callgraph.main
  110. `
  111. func init() {
  112. // If $GOMAXPROCS isn't set, use the full capacity of the machine.
  113. // For small machines, use at least 4 threads.
  114. if os.Getenv("GOMAXPROCS") == "" {
  115. n := runtime.NumCPU()
  116. if n < 4 {
  117. n = 4
  118. }
  119. runtime.GOMAXPROCS(n)
  120. }
  121. }
  122. func main() {
  123. flag.Parse()
  124. if err := doCallgraph(&build.Default, *algoFlag, *formatFlag, *testFlag, flag.Args()); err != nil {
  125. fmt.Fprintf(os.Stderr, "callgraph: %s\n", err)
  126. os.Exit(1)
  127. }
  128. }
  129. var stdout io.Writer = os.Stdout
  130. func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []string) error {
  131. conf := loader.Config{Build: ctxt}
  132. if len(args) == 0 {
  133. fmt.Fprintln(os.Stderr, Usage)
  134. return nil
  135. }
  136. // Use the initial packages from the command line.
  137. _, err := conf.FromArgs(args, tests)
  138. if err != nil {
  139. return err
  140. }
  141. // Load, parse and type-check the whole program.
  142. iprog, err := conf.Load()
  143. if err != nil {
  144. return err
  145. }
  146. // Create and build SSA-form program representation.
  147. prog := ssautil.CreateProgram(iprog, 0)
  148. prog.Build()
  149. // -- call graph construction ------------------------------------------
  150. var cg *callgraph.Graph
  151. switch algo {
  152. case "static":
  153. cg = static.CallGraph(prog)
  154. case "cha":
  155. cg = cha.CallGraph(prog)
  156. case "pta":
  157. // Set up points-to analysis log file.
  158. var ptalog io.Writer
  159. if *ptalogFlag != "" {
  160. if f, err := os.Create(*ptalogFlag); err != nil {
  161. log.Fatalf("Failed to create PTA log file: %s", err)
  162. } else {
  163. buf := bufio.NewWriter(f)
  164. ptalog = buf
  165. defer func() {
  166. if err := buf.Flush(); err != nil {
  167. log.Printf("flush: %s", err)
  168. }
  169. if err := f.Close(); err != nil {
  170. log.Printf("close: %s", err)
  171. }
  172. }()
  173. }
  174. }
  175. mains, err := mainPackages(prog, tests)
  176. if err != nil {
  177. return err
  178. }
  179. config := &pointer.Config{
  180. Mains: mains,
  181. BuildCallGraph: true,
  182. Log: ptalog,
  183. }
  184. ptares, err := pointer.Analyze(config)
  185. if err != nil {
  186. return err // internal error in pointer analysis
  187. }
  188. cg = ptares.CallGraph
  189. case "rta":
  190. mains, err := mainPackages(prog, tests)
  191. if err != nil {
  192. return err
  193. }
  194. var roots []*ssa.Function
  195. for _, main := range mains {
  196. roots = append(roots, main.Func("init"), main.Func("main"))
  197. }
  198. rtares := rta.Analyze(roots, true)
  199. cg = rtares.CallGraph
  200. // NB: RTA gives us Reachable and RuntimeTypes too.
  201. default:
  202. return fmt.Errorf("unknown algorithm: %s", algo)
  203. }
  204. cg.DeleteSyntheticNodes()
  205. // -- output------------------------------------------------------------
  206. var before, after string
  207. // Pre-canned formats.
  208. switch format {
  209. case "digraph":
  210. format = `{{printf "%q %q" .Caller .Callee}}`
  211. case "graphviz":
  212. before = "digraph callgraph {\n"
  213. after = "}\n"
  214. format = ` {{printf "%q" .Caller}} -> {{printf "%q" .Callee}}`
  215. }
  216. tmpl, err := template.New("-format").Parse(format)
  217. if err != nil {
  218. return fmt.Errorf("invalid -format template: %v", err)
  219. }
  220. // Allocate these once, outside the traversal.
  221. var buf bytes.Buffer
  222. data := Edge{fset: prog.Fset}
  223. fmt.Fprint(stdout, before)
  224. if err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
  225. data.position.Offset = -1
  226. data.edge = edge
  227. data.Caller = edge.Caller.Func
  228. data.Callee = edge.Callee.Func
  229. buf.Reset()
  230. if err := tmpl.Execute(&buf, &data); err != nil {
  231. return err
  232. }
  233. stdout.Write(buf.Bytes())
  234. if len := buf.Len(); len == 0 || buf.Bytes()[len-1] != '\n' {
  235. fmt.Fprintln(stdout)
  236. }
  237. return nil
  238. }); err != nil {
  239. return err
  240. }
  241. fmt.Fprint(stdout, after)
  242. return nil
  243. }
  244. // mainPackages returns the main packages to analyze.
  245. // Each resulting package is named "main" and has a main function.
  246. func mainPackages(prog *ssa.Program, tests bool) ([]*ssa.Package, error) {
  247. pkgs := prog.AllPackages() // TODO(adonovan): use only initial packages
  248. // If tests, create a "testmain" package for each test.
  249. var mains []*ssa.Package
  250. if tests {
  251. for _, pkg := range pkgs {
  252. if main := prog.CreateTestMainPackage(pkg); main != nil {
  253. mains = append(mains, main)
  254. }
  255. }
  256. if mains == nil {
  257. return nil, fmt.Errorf("no tests")
  258. }
  259. return mains, nil
  260. }
  261. // Otherwise, use the main packages.
  262. mains = append(mains, ssautil.MainPackages(pkgs)...)
  263. if len(mains) == 0 {
  264. return nil, fmt.Errorf("no main packages")
  265. }
  266. return mains, nil
  267. }
  268. type Edge struct {
  269. Caller *ssa.Function
  270. Callee *ssa.Function
  271. edge *callgraph.Edge
  272. fset *token.FileSet
  273. position token.Position // initialized lazily
  274. }
  275. func (e *Edge) pos() *token.Position {
  276. if e.position.Offset == -1 {
  277. e.position = e.fset.Position(e.edge.Pos()) // called lazily
  278. }
  279. return &e.position
  280. }
  281. func (e *Edge) Filename() string { return e.pos().Filename }
  282. func (e *Edge) Column() int { return e.pos().Column }
  283. func (e *Edge) Line() int { return e.pos().Line }
  284. func (e *Edge) Offset() int { return e.pos().Offset }
  285. func (e *Edge) Dynamic() string {
  286. if e.edge.Site != nil && e.edge.Site.Common().StaticCallee() == nil {
  287. return "dynamic"
  288. }
  289. return "static"
  290. }
  291. func (e *Edge) Description() string { return e.edge.Description() }