// 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 } // 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: ""}) 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) }