| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package analysis |
| |
| // This file computes the CALLERS and CALLEES relations from the call |
| // graph. CALLERS/CALLEES information is displayed in the lower pane |
| // when a "func" token or ast.CallExpr.Lparen is clicked, respectively. |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/token" |
| "go/types" |
| "log" |
| "math/big" |
| "sort" |
| |
| "golang.org/x/tools/go/callgraph" |
| "golang.org/x/tools/go/ssa" |
| ) |
| |
| // doCallgraph computes the CALLEES and CALLERS relations. |
| func (a *analysis) doCallgraph(cg *callgraph.Graph) { |
| log.Print("Deleting synthetic nodes...") |
| // TODO(adonovan): opt: DeleteSyntheticNodes is asymptotically |
| // inefficient and can be (unpredictably) slow. |
| cg.DeleteSyntheticNodes() |
| log.Print("Synthetic nodes deleted") |
| |
| // Populate nodes of package call graphs (PCGs). |
| for _, n := range cg.Nodes { |
| a.pcgAddNode(n.Func) |
| } |
| // Within each PCG, sort funcs by name. |
| for _, pcg := range a.pcgs { |
| pcg.sortNodes() |
| } |
| |
| calledFuncs := make(map[ssa.CallInstruction]map[*ssa.Function]bool) |
| callingSites := make(map[*ssa.Function]map[ssa.CallInstruction]bool) |
| for _, n := range cg.Nodes { |
| for _, e := range n.Out { |
| if e.Site == nil { |
| continue // a call from a synthetic node such as <root> |
| } |
| |
| // Add (site pos, callee) to calledFuncs. |
| // (Dynamic calls only.) |
| callee := e.Callee.Func |
| |
| a.pcgAddEdge(n.Func, callee) |
| |
| if callee.Synthetic != "" { |
| continue // call of a package initializer |
| } |
| |
| if e.Site.Common().StaticCallee() == nil { |
| // dynamic call |
| // (CALLEES information for static calls |
| // is computed using SSA information.) |
| lparen := e.Site.Common().Pos() |
| if lparen != token.NoPos { |
| fns := calledFuncs[e.Site] |
| if fns == nil { |
| fns = make(map[*ssa.Function]bool) |
| calledFuncs[e.Site] = fns |
| } |
| fns[callee] = true |
| } |
| } |
| |
| // Add (callee, site) to callingSites. |
| fns := callingSites[callee] |
| if fns == nil { |
| fns = make(map[ssa.CallInstruction]bool) |
| callingSites[callee] = fns |
| } |
| fns[e.Site] = true |
| } |
| } |
| |
| // CALLEES. |
| log.Print("Callees...") |
| for site, fns := range calledFuncs { |
| var funcs funcsByPos |
| for fn := range fns { |
| funcs = append(funcs, fn) |
| } |
| sort.Sort(funcs) |
| |
| a.addCallees(site, funcs) |
| } |
| |
| // CALLERS |
| log.Print("Callers...") |
| for callee, sites := range callingSites { |
| pos := funcToken(callee) |
| if pos == token.NoPos { |
| log.Printf("CALLERS: skipping %s: no pos", callee) |
| continue |
| } |
| |
| var this *types.Package // for relativizing names |
| if callee.Pkg != nil { |
| this = callee.Pkg.Pkg |
| } |
| |
| // Compute sites grouped by parent, with text and URLs. |
| sitesByParent := make(map[*ssa.Function]sitesByPos) |
| for site := range sites { |
| fn := site.Parent() |
| sitesByParent[fn] = append(sitesByParent[fn], site) |
| } |
| var funcs funcsByPos |
| for fn := range sitesByParent { |
| funcs = append(funcs, fn) |
| } |
| sort.Sort(funcs) |
| |
| v := callersJSON{ |
| Callee: callee.String(), |
| Callers: []callerJSON{}, // (JS wants non-nil) |
| } |
| for _, fn := range funcs { |
| caller := callerJSON{ |
| Func: prettyFunc(this, fn), |
| Sites: []anchorJSON{}, // (JS wants non-nil) |
| } |
| sites := sitesByParent[fn] |
| sort.Sort(sites) |
| for _, site := range sites { |
| pos := site.Common().Pos() |
| if pos != token.NoPos { |
| caller.Sites = append(caller.Sites, anchorJSON{ |
| Text: fmt.Sprintf("%d", a.prog.Fset.Position(pos).Line), |
| Href: a.posURL(pos, len("(")), |
| }) |
| } |
| } |
| v.Callers = append(v.Callers, caller) |
| } |
| |
| fi, offset := a.fileAndOffset(pos) |
| fi.addLink(aLink{ |
| start: offset, |
| end: offset + len("func"), |
| title: fmt.Sprintf("%d callers", len(sites)), |
| onclick: fmt.Sprintf("onClickCallers(%d)", fi.addData(v)), |
| }) |
| } |
| |
| // PACKAGE CALLGRAPH |
| log.Print("Package call graph...") |
| for pkg, pcg := range a.pcgs { |
| // Maps (*ssa.Function).RelString() to index in JSON CALLGRAPH array. |
| index := make(map[string]int) |
| |
| // Treat exported functions (and exported methods of |
| // exported named types) as roots even if they aren't |
| // actually called from outside the package. |
| for i, n := range pcg.nodes { |
| if i == 0 || n.fn.Object() == nil || !n.fn.Object().Exported() { |
| continue |
| } |
| recv := n.fn.Signature.Recv() |
| if recv == nil || deref(recv.Type()).(*types.Named).Obj().Exported() { |
| roots := &pcg.nodes[0].edges |
| roots.SetBit(roots, i, 1) |
| } |
| index[n.fn.RelString(pkg.Pkg)] = i |
| } |
| |
| json := a.pcgJSON(pcg) |
| |
| // TODO(adonovan): pkg.Path() is not unique! |
| // It is possible to declare a non-test package called x_test. |
| a.result.pkgInfo(pkg.Pkg.Path()).setCallGraph(json, index) |
| } |
| } |
| |
| // addCallees adds client data and links for the facts that site calls fns. |
| func (a *analysis) addCallees(site ssa.CallInstruction, fns []*ssa.Function) { |
| v := calleesJSON{ |
| Descr: site.Common().Description(), |
| Callees: []anchorJSON{}, // (JS wants non-nil) |
| } |
| var this *types.Package // for relativizing names |
| if p := site.Parent().Package(); p != nil { |
| this = p.Pkg |
| } |
| |
| for _, fn := range fns { |
| v.Callees = append(v.Callees, anchorJSON{ |
| Text: prettyFunc(this, fn), |
| Href: a.posURL(funcToken(fn), len("func")), |
| }) |
| } |
| |
| fi, offset := a.fileAndOffset(site.Common().Pos()) |
| fi.addLink(aLink{ |
| start: offset, |
| end: offset + len("("), |
| title: fmt.Sprintf("%d callees", len(v.Callees)), |
| onclick: fmt.Sprintf("onClickCallees(%d)", fi.addData(v)), |
| }) |
| } |
| |
| // -- utilities -------------------------------------------------------- |
| |
| // stable order within packages but undefined across packages. |
| type funcsByPos []*ssa.Function |
| |
| func (a funcsByPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() } |
| func (a funcsByPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
| func (a funcsByPos) Len() int { return len(a) } |
| |
| type sitesByPos []ssa.CallInstruction |
| |
| func (a sitesByPos) Less(i, j int) bool { return a[i].Common().Pos() < a[j].Common().Pos() } |
| func (a sitesByPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
| func (a sitesByPos) Len() int { return len(a) } |
| |
| func funcToken(fn *ssa.Function) token.Pos { |
| switch syntax := fn.Syntax().(type) { |
| case *ast.FuncLit: |
| return syntax.Type.Func |
| case *ast.FuncDecl: |
| return syntax.Type.Func |
| } |
| return token.NoPos |
| } |
| |
| // prettyFunc pretty-prints fn for the user interface. |
| // TODO(adonovan): return HTML so we have more markup freedom. |
| func prettyFunc(this *types.Package, fn *ssa.Function) string { |
| if fn.Parent() != nil { |
| return fmt.Sprintf("%s in %s", |
| types.TypeString(fn.Signature, types.RelativeTo(this)), |
| prettyFunc(this, fn.Parent())) |
| } |
| if fn.Synthetic != "" && fn.Name() == "init" { |
| // (This is the actual initializer, not a declared 'func init'). |
| if fn.Pkg.Pkg == this { |
| return "package initializer" |
| } |
| return fmt.Sprintf("%q package initializer", fn.Pkg.Pkg.Path()) |
| } |
| return fn.RelString(this) |
| } |
| |
| // -- intra-package callgraph ------------------------------------------ |
| |
| // pcgNode represents a node in the package call graph (PCG). |
| type pcgNode struct { |
| fn *ssa.Function |
| pretty string // cache of prettyFunc(fn) |
| edges big.Int // set of callee func indices |
| } |
| |
| // A packageCallGraph represents the intra-package edges of the global call graph. |
| // The zeroth node indicates "all external functions". |
| type packageCallGraph struct { |
| nodeIndex map[*ssa.Function]int // maps func to node index (a small int) |
| nodes []*pcgNode // maps node index to node |
| } |
| |
| // sortNodes populates pcg.nodes in name order and updates the nodeIndex. |
| func (pcg *packageCallGraph) sortNodes() { |
| nodes := make([]*pcgNode, 0, len(pcg.nodeIndex)) |
| nodes = append(nodes, &pcgNode{fn: nil, pretty: "<external>"}) |
| for fn := range pcg.nodeIndex { |
| nodes = append(nodes, &pcgNode{ |
| fn: fn, |
| pretty: prettyFunc(fn.Pkg.Pkg, fn), |
| }) |
| } |
| sort.Sort(pcgNodesByPretty(nodes[1:])) |
| for i, n := range nodes { |
| pcg.nodeIndex[n.fn] = i |
| } |
| pcg.nodes = nodes |
| } |
| |
| func (pcg *packageCallGraph) addEdge(caller, callee *ssa.Function) { |
| var callerIndex int |
| if caller.Pkg == callee.Pkg { |
| // intra-package edge |
| callerIndex = pcg.nodeIndex[caller] |
| if callerIndex < 1 { |
| panic(caller) |
| } |
| } |
| edges := &pcg.nodes[callerIndex].edges |
| edges.SetBit(edges, pcg.nodeIndex[callee], 1) |
| } |
| |
| func (a *analysis) pcgAddNode(fn *ssa.Function) { |
| if fn.Pkg == nil { |
| return |
| } |
| pcg, ok := a.pcgs[fn.Pkg] |
| if !ok { |
| pcg = &packageCallGraph{nodeIndex: make(map[*ssa.Function]int)} |
| a.pcgs[fn.Pkg] = pcg |
| } |
| pcg.nodeIndex[fn] = -1 |
| } |
| |
| func (a *analysis) pcgAddEdge(caller, callee *ssa.Function) { |
| if callee.Pkg != nil { |
| a.pcgs[callee.Pkg].addEdge(caller, callee) |
| } |
| } |
| |
| // pcgJSON returns a new slice of callgraph JSON values. |
| func (a *analysis) pcgJSON(pcg *packageCallGraph) []*PCGNodeJSON { |
| var nodes []*PCGNodeJSON |
| for _, n := range pcg.nodes { |
| |
| // TODO(adonovan): why is there no good way to iterate |
| // over the set bits of a big.Int? |
| var callees []int |
| nbits := n.edges.BitLen() |
| for j := 0; j < nbits; j++ { |
| if n.edges.Bit(j) == 1 { |
| callees = append(callees, j) |
| } |
| } |
| |
| var pos token.Pos |
| if n.fn != nil { |
| pos = funcToken(n.fn) |
| } |
| nodes = append(nodes, &PCGNodeJSON{ |
| Func: anchorJSON{ |
| Text: n.pretty, |
| Href: a.posURL(pos, len("func")), |
| }, |
| Callees: callees, |
| }) |
| } |
| return nodes |
| } |
| |
| type pcgNodesByPretty []*pcgNode |
| |
| func (a pcgNodesByPretty) Less(i, j int) bool { return a[i].pretty < a[j].pretty } |
| func (a pcgNodesByPretty) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
| func (a pcgNodesByPretty) Len() int { return len(a) } |