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