352 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			352 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			Go
		
	
	
	
// 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) }
 |