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