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.

351 lines
9.4 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. package analysis
  5. // This file computes the CALLERS and CALLEES relations from the call
  6. // graph. CALLERS/CALLEES information is displayed in the lower pane
  7. // when a "func" token or ast.CallExpr.Lparen is clicked, respectively.
  8. import (
  9. "fmt"
  10. "go/ast"
  11. "go/token"
  12. "go/types"
  13. "log"
  14. "math/big"
  15. "sort"
  16. "golang.org/x/tools/go/callgraph"
  17. "golang.org/x/tools/go/ssa"
  18. )
  19. // doCallgraph computes the CALLEES and CALLERS relations.
  20. func (a *analysis) doCallgraph(cg *callgraph.Graph) {
  21. log.Print("Deleting synthetic nodes...")
  22. // TODO(adonovan): opt: DeleteSyntheticNodes is asymptotically
  23. // inefficient and can be (unpredictably) slow.
  24. cg.DeleteSyntheticNodes()
  25. log.Print("Synthetic nodes deleted")
  26. // Populate nodes of package call graphs (PCGs).
  27. for _, n := range cg.Nodes {
  28. a.pcgAddNode(n.Func)
  29. }
  30. // Within each PCG, sort funcs by name.
  31. for _, pcg := range a.pcgs {
  32. pcg.sortNodes()
  33. }
  34. calledFuncs := make(map[ssa.CallInstruction]map[*ssa.Function]bool)
  35. callingSites := make(map[*ssa.Function]map[ssa.CallInstruction]bool)
  36. for _, n := range cg.Nodes {
  37. for _, e := range n.Out {
  38. if e.Site == nil {
  39. continue // a call from a synthetic node such as <root>
  40. }
  41. // Add (site pos, callee) to calledFuncs.
  42. // (Dynamic calls only.)
  43. callee := e.Callee.Func
  44. a.pcgAddEdge(n.Func, callee)
  45. if callee.Synthetic != "" {
  46. continue // call of a package initializer
  47. }
  48. if e.Site.Common().StaticCallee() == nil {
  49. // dynamic call
  50. // (CALLEES information for static calls
  51. // is computed using SSA information.)
  52. lparen := e.Site.Common().Pos()
  53. if lparen != token.NoPos {
  54. fns := calledFuncs[e.Site]
  55. if fns == nil {
  56. fns = make(map[*ssa.Function]bool)
  57. calledFuncs[e.Site] = fns
  58. }
  59. fns[callee] = true
  60. }
  61. }
  62. // Add (callee, site) to callingSites.
  63. fns := callingSites[callee]
  64. if fns == nil {
  65. fns = make(map[ssa.CallInstruction]bool)
  66. callingSites[callee] = fns
  67. }
  68. fns[e.Site] = true
  69. }
  70. }
  71. // CALLEES.
  72. log.Print("Callees...")
  73. for site, fns := range calledFuncs {
  74. var funcs funcsByPos
  75. for fn := range fns {
  76. funcs = append(funcs, fn)
  77. }
  78. sort.Sort(funcs)
  79. a.addCallees(site, funcs)
  80. }
  81. // CALLERS
  82. log.Print("Callers...")
  83. for callee, sites := range callingSites {
  84. pos := funcToken(callee)
  85. if pos == token.NoPos {
  86. log.Printf("CALLERS: skipping %s: no pos", callee)
  87. continue
  88. }
  89. var this *types.Package // for relativizing names
  90. if callee.Pkg != nil {
  91. this = callee.Pkg.Pkg
  92. }
  93. // Compute sites grouped by parent, with text and URLs.
  94. sitesByParent := make(map[*ssa.Function]sitesByPos)
  95. for site := range sites {
  96. fn := site.Parent()
  97. sitesByParent[fn] = append(sitesByParent[fn], site)
  98. }
  99. var funcs funcsByPos
  100. for fn := range sitesByParent {
  101. funcs = append(funcs, fn)
  102. }
  103. sort.Sort(funcs)
  104. v := callersJSON{
  105. Callee: callee.String(),
  106. Callers: []callerJSON{}, // (JS wants non-nil)
  107. }
  108. for _, fn := range funcs {
  109. caller := callerJSON{
  110. Func: prettyFunc(this, fn),
  111. Sites: []anchorJSON{}, // (JS wants non-nil)
  112. }
  113. sites := sitesByParent[fn]
  114. sort.Sort(sites)
  115. for _, site := range sites {
  116. pos := site.Common().Pos()
  117. if pos != token.NoPos {
  118. caller.Sites = append(caller.Sites, anchorJSON{
  119. Text: fmt.Sprintf("%d", a.prog.Fset.Position(pos).Line),
  120. Href: a.posURL(pos, len("(")),
  121. })
  122. }
  123. }
  124. v.Callers = append(v.Callers, caller)
  125. }
  126. fi, offset := a.fileAndOffset(pos)
  127. fi.addLink(aLink{
  128. start: offset,
  129. end: offset + len("func"),
  130. title: fmt.Sprintf("%d callers", len(sites)),
  131. onclick: fmt.Sprintf("onClickCallers(%d)", fi.addData(v)),
  132. })
  133. }
  134. // PACKAGE CALLGRAPH
  135. log.Print("Package call graph...")
  136. for pkg, pcg := range a.pcgs {
  137. // Maps (*ssa.Function).RelString() to index in JSON CALLGRAPH array.
  138. index := make(map[string]int)
  139. // Treat exported functions (and exported methods of
  140. // exported named types) as roots even if they aren't
  141. // actually called from outside the package.
  142. for i, n := range pcg.nodes {
  143. if i == 0 || n.fn.Object() == nil || !n.fn.Object().Exported() {
  144. continue
  145. }
  146. recv := n.fn.Signature.Recv()
  147. if recv == nil || deref(recv.Type()).(*types.Named).Obj().Exported() {
  148. roots := &pcg.nodes[0].edges
  149. roots.SetBit(roots, i, 1)
  150. }
  151. index[n.fn.RelString(pkg.Pkg)] = i
  152. }
  153. json := a.pcgJSON(pcg)
  154. // TODO(adonovan): pkg.Path() is not unique!
  155. // It is possible to declare a non-test package called x_test.
  156. a.result.pkgInfo(pkg.Pkg.Path()).setCallGraph(json, index)
  157. }
  158. }
  159. // addCallees adds client data and links for the facts that site calls fns.
  160. func (a *analysis) addCallees(site ssa.CallInstruction, fns []*ssa.Function) {
  161. v := calleesJSON{
  162. Descr: site.Common().Description(),
  163. Callees: []anchorJSON{}, // (JS wants non-nil)
  164. }
  165. var this *types.Package // for relativizing names
  166. if p := site.Parent().Package(); p != nil {
  167. this = p.Pkg
  168. }
  169. for _, fn := range fns {
  170. v.Callees = append(v.Callees, anchorJSON{
  171. Text: prettyFunc(this, fn),
  172. Href: a.posURL(funcToken(fn), len("func")),
  173. })
  174. }
  175. fi, offset := a.fileAndOffset(site.Common().Pos())
  176. fi.addLink(aLink{
  177. start: offset,
  178. end: offset + len("("),
  179. title: fmt.Sprintf("%d callees", len(v.Callees)),
  180. onclick: fmt.Sprintf("onClickCallees(%d)", fi.addData(v)),
  181. })
  182. }
  183. // -- utilities --------------------------------------------------------
  184. // stable order within packages but undefined across packages.
  185. type funcsByPos []*ssa.Function
  186. func (a funcsByPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() }
  187. func (a funcsByPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  188. func (a funcsByPos) Len() int { return len(a) }
  189. type sitesByPos []ssa.CallInstruction
  190. func (a sitesByPos) Less(i, j int) bool { return a[i].Common().Pos() < a[j].Common().Pos() }
  191. func (a sitesByPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  192. func (a sitesByPos) Len() int { return len(a) }
  193. func funcToken(fn *ssa.Function) token.Pos {
  194. switch syntax := fn.Syntax().(type) {
  195. case *ast.FuncLit:
  196. return syntax.Type.Func
  197. case *ast.FuncDecl:
  198. return syntax.Type.Func
  199. }
  200. return token.NoPos
  201. }
  202. // prettyFunc pretty-prints fn for the user interface.
  203. // TODO(adonovan): return HTML so we have more markup freedom.
  204. func prettyFunc(this *types.Package, fn *ssa.Function) string {
  205. if fn.Parent() != nil {
  206. return fmt.Sprintf("%s in %s",
  207. types.TypeString(fn.Signature, types.RelativeTo(this)),
  208. prettyFunc(this, fn.Parent()))
  209. }
  210. if fn.Synthetic != "" && fn.Name() == "init" {
  211. // (This is the actual initializer, not a declared 'func init').
  212. if fn.Pkg.Pkg == this {
  213. return "package initializer"
  214. }
  215. return fmt.Sprintf("%q package initializer", fn.Pkg.Pkg.Path())
  216. }
  217. return fn.RelString(this)
  218. }
  219. // -- intra-package callgraph ------------------------------------------
  220. // pcgNode represents a node in the package call graph (PCG).
  221. type pcgNode struct {
  222. fn *ssa.Function
  223. pretty string // cache of prettyFunc(fn)
  224. edges big.Int // set of callee func indices
  225. }
  226. // A packageCallGraph represents the intra-package edges of the global call graph.
  227. // The zeroth node indicates "all external functions".
  228. type packageCallGraph struct {
  229. nodeIndex map[*ssa.Function]int // maps func to node index (a small int)
  230. nodes []*pcgNode // maps node index to node
  231. }
  232. // sortNodes populates pcg.nodes in name order and updates the nodeIndex.
  233. func (pcg *packageCallGraph) sortNodes() {
  234. nodes := make([]*pcgNode, 0, len(pcg.nodeIndex))
  235. nodes = append(nodes, &pcgNode{fn: nil, pretty: "<external>"})
  236. for fn := range pcg.nodeIndex {
  237. nodes = append(nodes, &pcgNode{
  238. fn: fn,
  239. pretty: prettyFunc(fn.Pkg.Pkg, fn),
  240. })
  241. }
  242. sort.Sort(pcgNodesByPretty(nodes[1:]))
  243. for i, n := range nodes {
  244. pcg.nodeIndex[n.fn] = i
  245. }
  246. pcg.nodes = nodes
  247. }
  248. func (pcg *packageCallGraph) addEdge(caller, callee *ssa.Function) {
  249. var callerIndex int
  250. if caller.Pkg == callee.Pkg {
  251. // intra-package edge
  252. callerIndex = pcg.nodeIndex[caller]
  253. if callerIndex < 1 {
  254. panic(caller)
  255. }
  256. }
  257. edges := &pcg.nodes[callerIndex].edges
  258. edges.SetBit(edges, pcg.nodeIndex[callee], 1)
  259. }
  260. func (a *analysis) pcgAddNode(fn *ssa.Function) {
  261. if fn.Pkg == nil {
  262. return
  263. }
  264. pcg, ok := a.pcgs[fn.Pkg]
  265. if !ok {
  266. pcg = &packageCallGraph{nodeIndex: make(map[*ssa.Function]int)}
  267. a.pcgs[fn.Pkg] = pcg
  268. }
  269. pcg.nodeIndex[fn] = -1
  270. }
  271. func (a *analysis) pcgAddEdge(caller, callee *ssa.Function) {
  272. if callee.Pkg != nil {
  273. a.pcgs[callee.Pkg].addEdge(caller, callee)
  274. }
  275. }
  276. // pcgJSON returns a new slice of callgraph JSON values.
  277. func (a *analysis) pcgJSON(pcg *packageCallGraph) []*PCGNodeJSON {
  278. var nodes []*PCGNodeJSON
  279. for _, n := range pcg.nodes {
  280. // TODO(adonovan): why is there no good way to iterate
  281. // over the set bits of a big.Int?
  282. var callees []int
  283. nbits := n.edges.BitLen()
  284. for j := 0; j < nbits; j++ {
  285. if n.edges.Bit(j) == 1 {
  286. callees = append(callees, j)
  287. }
  288. }
  289. var pos token.Pos
  290. if n.fn != nil {
  291. pos = funcToken(n.fn)
  292. }
  293. nodes = append(nodes, &PCGNodeJSON{
  294. Func: anchorJSON{
  295. Text: n.pretty,
  296. Href: a.posURL(pos, len("func")),
  297. },
  298. Callees: callees,
  299. })
  300. }
  301. return nodes
  302. }
  303. type pcgNodesByPretty []*pcgNode
  304. func (a pcgNodesByPretty) Less(i, j int) bool { return a[i].pretty < a[j].pretty }
  305. func (a pcgNodesByPretty) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  306. func (a pcgNodesByPretty) Len() int { return len(a) }