blob: 09f22052433829e84845126555cf4d56e54190ea [file] [log] [blame]
// 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"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/types"
"golang.org/x/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
}