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.

181 lines
4.6 KiB

  1. // Copyright 2013 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 callgraph
  5. import "golang.org/x/tools/go/ssa"
  6. // This file provides various utilities over call graphs, such as
  7. // visitation and path search.
  8. // CalleesOf returns a new set containing all direct callees of the
  9. // caller node.
  10. //
  11. func CalleesOf(caller *Node) map[*Node]bool {
  12. callees := make(map[*Node]bool)
  13. for _, e := range caller.Out {
  14. callees[e.Callee] = true
  15. }
  16. return callees
  17. }
  18. // GraphVisitEdges visits all the edges in graph g in depth-first order.
  19. // The edge function is called for each edge in postorder. If it
  20. // returns non-nil, visitation stops and GraphVisitEdges returns that
  21. // value.
  22. //
  23. func GraphVisitEdges(g *Graph, edge func(*Edge) error) error {
  24. seen := make(map[*Node]bool)
  25. var visit func(n *Node) error
  26. visit = func(n *Node) error {
  27. if !seen[n] {
  28. seen[n] = true
  29. for _, e := range n.Out {
  30. if err := visit(e.Callee); err != nil {
  31. return err
  32. }
  33. if err := edge(e); err != nil {
  34. return err
  35. }
  36. }
  37. }
  38. return nil
  39. }
  40. for _, n := range g.Nodes {
  41. if err := visit(n); err != nil {
  42. return err
  43. }
  44. }
  45. return nil
  46. }
  47. // PathSearch finds an arbitrary path starting at node start and
  48. // ending at some node for which isEnd() returns true. On success,
  49. // PathSearch returns the path as an ordered list of edges; on
  50. // failure, it returns nil.
  51. //
  52. func PathSearch(start *Node, isEnd func(*Node) bool) []*Edge {
  53. stack := make([]*Edge, 0, 32)
  54. seen := make(map[*Node]bool)
  55. var search func(n *Node) []*Edge
  56. search = func(n *Node) []*Edge {
  57. if !seen[n] {
  58. seen[n] = true
  59. if isEnd(n) {
  60. return stack
  61. }
  62. for _, e := range n.Out {
  63. stack = append(stack, e) // push
  64. if found := search(e.Callee); found != nil {
  65. return found
  66. }
  67. stack = stack[:len(stack)-1] // pop
  68. }
  69. }
  70. return nil
  71. }
  72. return search(start)
  73. }
  74. // DeleteSyntheticNodes removes from call graph g all nodes for
  75. // synthetic functions (except g.Root and package initializers),
  76. // preserving the topology. In effect, calls to synthetic wrappers
  77. // are "inlined".
  78. //
  79. func (g *Graph) DeleteSyntheticNodes() {
  80. // Measurements on the standard library and go.tools show that
  81. // resulting graph has ~15% fewer nodes and 4-8% fewer edges
  82. // than the input.
  83. //
  84. // Inlining a wrapper of in-degree m, out-degree n adds m*n
  85. // and removes m+n edges. Since most wrappers are monomorphic
  86. // (n=1) this results in a slight reduction. Polymorphic
  87. // wrappers (n>1), e.g. from embedding an interface value
  88. // inside a struct to satisfy some interface, cause an
  89. // increase in the graph, but they seem to be uncommon.
  90. // Hash all existing edges to avoid creating duplicates.
  91. edges := make(map[Edge]bool)
  92. for _, cgn := range g.Nodes {
  93. for _, e := range cgn.Out {
  94. edges[*e] = true
  95. }
  96. }
  97. for fn, cgn := range g.Nodes {
  98. if cgn == g.Root || fn.Synthetic == "" || isInit(cgn.Func) {
  99. continue // keep
  100. }
  101. for _, eIn := range cgn.In {
  102. for _, eOut := range cgn.Out {
  103. newEdge := Edge{eIn.Caller, eIn.Site, eOut.Callee}
  104. if edges[newEdge] {
  105. continue // don't add duplicate
  106. }
  107. AddEdge(eIn.Caller, eIn.Site, eOut.Callee)
  108. edges[newEdge] = true
  109. }
  110. }
  111. g.DeleteNode(cgn)
  112. }
  113. }
  114. func isInit(fn *ssa.Function) bool {
  115. return fn.Pkg != nil && fn.Pkg.Func("init") == fn
  116. }
  117. // DeleteNode removes node n and its edges from the graph g.
  118. // (NB: not efficient for batch deletion.)
  119. func (g *Graph) DeleteNode(n *Node) {
  120. n.deleteIns()
  121. n.deleteOuts()
  122. delete(g.Nodes, n.Func)
  123. }
  124. // deleteIns deletes all incoming edges to n.
  125. func (n *Node) deleteIns() {
  126. for _, e := range n.In {
  127. removeOutEdge(e)
  128. }
  129. n.In = nil
  130. }
  131. // deleteOuts deletes all outgoing edges from n.
  132. func (n *Node) deleteOuts() {
  133. for _, e := range n.Out {
  134. removeInEdge(e)
  135. }
  136. n.Out = nil
  137. }
  138. // removeOutEdge removes edge.Caller's outgoing edge 'edge'.
  139. func removeOutEdge(edge *Edge) {
  140. caller := edge.Caller
  141. n := len(caller.Out)
  142. for i, e := range caller.Out {
  143. if e == edge {
  144. // Replace it with the final element and shrink the slice.
  145. caller.Out[i] = caller.Out[n-1]
  146. caller.Out[n-1] = nil // aid GC
  147. caller.Out = caller.Out[:n-1]
  148. return
  149. }
  150. }
  151. panic("edge not found: " + edge.String())
  152. }
  153. // removeInEdge removes edge.Callee's incoming edge 'edge'.
  154. func removeInEdge(edge *Edge) {
  155. caller := edge.Callee
  156. n := len(caller.In)
  157. for i, e := range caller.In {
  158. if e == edge {
  159. // Replace it with the final element and shrink the slice.
  160. caller.In[i] = caller.In[n-1]
  161. caller.In[n-1] = nil // aid GC
  162. caller.In = caller.In[:n-1]
  163. return
  164. }
  165. }
  166. panic("edge not found: " + edge.String())
  167. }