153 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			153 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright 2013 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 oracle
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"go/token"
 | 
						|
	"sort"
 | 
						|
 | 
						|
	"code.google.com/p/go.tools/go/callgraph"
 | 
						|
	"code.google.com/p/go.tools/go/ssa"
 | 
						|
	"code.google.com/p/go.tools/go/types"
 | 
						|
	"code.google.com/p/go.tools/oracle/serial"
 | 
						|
)
 | 
						|
 | 
						|
// doCallgraph displays the entire callgraph of the current program,
 | 
						|
// or if a query -pos was provided, the query package.
 | 
						|
func doCallgraph(o *Oracle, qpos *QueryPos) (queryResult, error) {
 | 
						|
	buildSSA(o)
 | 
						|
 | 
						|
	// Run the pointer analysis and build the callgraph.
 | 
						|
	o.ptaConfig.BuildCallGraph = true
 | 
						|
	cg := ptrAnalysis(o).CallGraph
 | 
						|
	cg.DeleteSyntheticNodes()
 | 
						|
 | 
						|
	var qpkg *types.Package
 | 
						|
	var isQueryPkg func(fn *ssa.Function) bool
 | 
						|
	var keep, remove, roots []*callgraph.Node
 | 
						|
	if qpos == nil {
 | 
						|
		// No -pos provided: show complete callgraph.
 | 
						|
		roots = append(roots, cg.Root)
 | 
						|
		isQueryPkg = func(fn *ssa.Function) bool { return true }
 | 
						|
	} else {
 | 
						|
		// A query -pos was provided: restrict result to
 | 
						|
		// functions belonging to the query package.
 | 
						|
		qpkg = qpos.info.Pkg
 | 
						|
		isQueryPkg = func(fn *ssa.Function) bool {
 | 
						|
			return fn.Pkg != nil && fn.Pkg.Object == qpkg
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// First compute the nodes to keep and remove.
 | 
						|
	for fn, cgn := range cg.Nodes {
 | 
						|
		if isQueryPkg(fn) {
 | 
						|
			keep = append(keep, cgn)
 | 
						|
		} else {
 | 
						|
			remove = append(remove, cgn)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Compact the Node.ID sequence of the kept nodes,
 | 
						|
	// preserving the original order.
 | 
						|
	sort.Sort(nodesByID(keep))
 | 
						|
	for i, cgn := range keep {
 | 
						|
		cgn.ID = i
 | 
						|
	}
 | 
						|
 | 
						|
	// Compute the set of roots:
 | 
						|
	// in-package nodes with out-of-package callers.
 | 
						|
	// For determinism, roots are ordered by original Node.ID.
 | 
						|
	for _, cgn := range keep {
 | 
						|
		for _, e := range cgn.In {
 | 
						|
			if !isQueryPkg(e.Caller.Func) {
 | 
						|
				roots = append(roots, cgn)
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Finally, discard all out-of-package nodes.
 | 
						|
	for _, cgn := range remove {
 | 
						|
		cg.DeleteNode(cgn)
 | 
						|
	}
 | 
						|
 | 
						|
	return &callgraphResult{qpkg, cg.Nodes, roots}, nil
 | 
						|
}
 | 
						|
 | 
						|
type callgraphResult struct {
 | 
						|
	qpkg  *types.Package
 | 
						|
	nodes map[*ssa.Function]*callgraph.Node
 | 
						|
	roots []*callgraph.Node
 | 
						|
}
 | 
						|
 | 
						|
func (r *callgraphResult) display(printf printfFunc) {
 | 
						|
	descr := "the entire program"
 | 
						|
	if r.qpkg != nil {
 | 
						|
		descr = fmt.Sprintf("package %s", r.qpkg.Path())
 | 
						|
	}
 | 
						|
 | 
						|
	printf(nil, `
 | 
						|
Below is a call graph of %s.
 | 
						|
The numbered nodes form a spanning tree.
 | 
						|
Non-numbered nodes indicate back- or cross-edges to the node whose
 | 
						|
 number follows in parentheses.
 | 
						|
`, descr)
 | 
						|
 | 
						|
	printed := make(map[*callgraph.Node]int)
 | 
						|
	var print func(caller *callgraph.Node, indent int)
 | 
						|
	print = func(caller *callgraph.Node, indent int) {
 | 
						|
		if num, ok := printed[caller]; !ok {
 | 
						|
			num = len(printed)
 | 
						|
			printed[caller] = num
 | 
						|
 | 
						|
			// Sort the children into name order for deterministic* output.
 | 
						|
			// (*mostly: anon funcs' names are not globally unique.)
 | 
						|
			var funcs funcsByName
 | 
						|
			for callee := range callgraph.CalleesOf(caller) {
 | 
						|
				funcs = append(funcs, callee.Func)
 | 
						|
			}
 | 
						|
			sort.Sort(funcs)
 | 
						|
 | 
						|
			printf(caller.Func, "%d\t%*s%s", num, 4*indent, "", caller.Func.RelString(r.qpkg))
 | 
						|
			for _, callee := range funcs {
 | 
						|
				print(r.nodes[callee], indent+1)
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			printf(caller.Func, "\t%*s%s (%d)", 4*indent, "", caller.Func.RelString(r.qpkg), num)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for _, root := range r.roots {
 | 
						|
		print(root, 0)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type nodesByID []*callgraph.Node
 | 
						|
 | 
						|
func (s nodesByID) Len() int           { return len(s) }
 | 
						|
func (s nodesByID) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
 | 
						|
func (s nodesByID) Less(i, j int) bool { return s[i].ID < s[j].ID }
 | 
						|
 | 
						|
type funcsByName []*ssa.Function
 | 
						|
 | 
						|
func (s funcsByName) Len() int           { return len(s) }
 | 
						|
func (s funcsByName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
 | 
						|
func (s funcsByName) Less(i, j int) bool { return s[i].String() < s[j].String() }
 | 
						|
 | 
						|
func (r *callgraphResult) toSerial(res *serial.Result, fset *token.FileSet) {
 | 
						|
	cg := make([]serial.CallGraph, len(r.nodes))
 | 
						|
	for _, n := range r.nodes {
 | 
						|
		j := &cg[n.ID]
 | 
						|
		fn := n.Func
 | 
						|
		j.Name = fn.String()
 | 
						|
		j.Pos = fset.Position(fn.Pos()).String()
 | 
						|
		for callee := range callgraph.CalleesOf(n) {
 | 
						|
			j.Children = append(j.Children, callee.ID)
 | 
						|
		}
 | 
						|
		sort.Ints(j.Children)
 | 
						|
	}
 | 
						|
	res.Callgraph = cg
 | 
						|
}
 |