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