--- /dev/null
+// 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) }