mirror of
https://github.com/golang/go
synced 2024-11-18 20:24:41 -07:00
go.tools/oracle: optionally restrict 'callgraph' query to a single package.
If a -pos argument is specified, a 'callgraph' query reports only the functions within the query package. This produces a far more manageable amount of information, and because we don't need to package-qualify the names, the result is easier to read. Added tests: - callgraph query with/without -pos (The test driver was extended to allow "nopos" queries.) - callers and callees queries don't return wrappers Also, in go/callgraph: - (*Node).String, (*Edge).String - (*Graph).DeleteSyntheticNodes eliminates synthetic wrapper functions, preserving topology. Used in all four oracle "call*" queries. - (*Graph).DeleteNode LGTM=crawshaw R=crawshaw CC=golang-codereviews https://golang.org/cl/66240044
This commit is contained in:
parent
49eaa56ed1
commit
99b2441d95
@ -39,7 +39,11 @@ package callgraph
|
||||
// More generally, we could eliminate "uninteresting" nodes such as
|
||||
// nodes from packages we don't care about.
|
||||
|
||||
import "code.google.com/p/go.tools/go/ssa"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.google.com/p/go.tools/go/ssa"
|
||||
)
|
||||
|
||||
// A Graph represents a call graph.
|
||||
//
|
||||
@ -77,6 +81,10 @@ type Node struct {
|
||||
Out []*Edge // unordered set of outgoing call edges (n.Out[*].Caller == n)
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
return fmt.Sprintf("n%d:%s", n.ID, n.Func)
|
||||
}
|
||||
|
||||
// A Edge represents an edge in the call graph.
|
||||
//
|
||||
// Site is nil for edges originating in synthetic or intrinsic
|
||||
@ -87,6 +95,10 @@ type Edge struct {
|
||||
Callee *Node
|
||||
}
|
||||
|
||||
func (e Edge) String() string {
|
||||
return fmt.Sprintf("%s --> %s", e.Caller, e.Callee)
|
||||
}
|
||||
|
||||
// AddEdge adds the edge (caller, site, callee) to the call graph.
|
||||
func AddEdge(caller *Node, site ssa.CallInstruction, callee *Node) {
|
||||
e := &Edge{caller, site, callee}
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
package callgraph
|
||||
|
||||
import "code.google.com/p/go.tools/go/ssa"
|
||||
|
||||
// This file provides various utilities over call graphs, such as
|
||||
// visitation and path search.
|
||||
|
||||
@ -75,3 +77,80 @@ func PathSearch(start *Node, isEnd func(*Node) bool) []*Edge {
|
||||
}
|
||||
return search(start)
|
||||
}
|
||||
|
||||
// DeleteSyntheticNodes removes from call graph g all nodes for
|
||||
// synthetic functions (except g.Root and package initializers),
|
||||
// preserving the topology.
|
||||
func (g *Graph) DeleteSyntheticNodes() {
|
||||
for fn, cgn := range g.Nodes {
|
||||
if cgn == g.Root || fn.Synthetic == "" || isInit(cgn.Func) {
|
||||
continue // keep
|
||||
}
|
||||
for _, eIn := range cgn.In {
|
||||
for _, eOut := range cgn.Out {
|
||||
AddEdge(eIn.Caller, eIn.Site, eOut.Callee)
|
||||
}
|
||||
}
|
||||
g.DeleteNode(cgn)
|
||||
}
|
||||
}
|
||||
|
||||
func isInit(fn *ssa.Function) bool {
|
||||
return fn.Pkg != nil && fn.Pkg.Func("init") == fn
|
||||
}
|
||||
|
||||
// DeleteNode removes node n and its edges from the graph g.
|
||||
// (NB: not efficient for batch deletion.)
|
||||
func (g *Graph) DeleteNode(n *Node) {
|
||||
n.deleteIns()
|
||||
n.deleteOuts()
|
||||
delete(g.Nodes, n.Func)
|
||||
}
|
||||
|
||||
// deleteIns deletes all incoming edges to n.
|
||||
func (n *Node) deleteIns() {
|
||||
for _, e := range n.In {
|
||||
removeOutEdge(e)
|
||||
}
|
||||
n.In = nil
|
||||
}
|
||||
|
||||
// deleteOuts deletes all outgoing edges from n.
|
||||
func (n *Node) deleteOuts() {
|
||||
for _, e := range n.Out {
|
||||
removeInEdge(e)
|
||||
}
|
||||
n.Out = nil
|
||||
}
|
||||
|
||||
// removeOutEdge removes edge.Caller's outgoing edge 'edge'.
|
||||
func removeOutEdge(edge *Edge) {
|
||||
caller := edge.Caller
|
||||
n := len(caller.Out)
|
||||
for i, e := range caller.Out {
|
||||
if e == edge {
|
||||
// Replace it with the final element and shrink the slice.
|
||||
caller.Out[i] = caller.Out[n-1]
|
||||
caller.Out[n-1] = nil // aid GC
|
||||
caller.Out = caller.Out[:n-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("edge not found: " + edge.String())
|
||||
}
|
||||
|
||||
// removeInEdge removes edge.Callee's incoming edge 'edge'.
|
||||
func removeInEdge(edge *Edge) {
|
||||
caller := edge.Callee
|
||||
n := len(caller.In)
|
||||
for i, e := range caller.In {
|
||||
if e == edge {
|
||||
// Replace it with the final element and shrink the slice.
|
||||
caller.In[i] = caller.In[n-1]
|
||||
caller.In[n-1] = nil // aid GC
|
||||
caller.In = caller.In[:n-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("edge not found: " + edge.String())
|
||||
}
|
||||
|
@ -17,9 +17,6 @@ import (
|
||||
|
||||
// Callees reports the possible callees of the function call site
|
||||
// identified by the specified source location.
|
||||
//
|
||||
// TODO(adonovan): if a callee is a wrapper, show the callee's callee.
|
||||
//
|
||||
func callees(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
pkg := o.prog.Package(qpos.info.Pkg)
|
||||
if pkg == nil {
|
||||
@ -104,6 +101,7 @@ func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
||||
// Dynamic call: use pointer analysis.
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
cg := ptrAnalysis(o).CallGraph
|
||||
cg.DeleteSyntheticNodes()
|
||||
|
||||
// Find all call edges from the site.
|
||||
n := cg.Nodes[site.Parent()]
|
||||
|
@ -16,8 +16,6 @@ import (
|
||||
// Callers reports the possible callers of the function
|
||||
// immediately enclosing the specified source location.
|
||||
//
|
||||
// TODO(adonovan): if a caller is a wrapper, show the caller's caller.
|
||||
//
|
||||
func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
pkg := o.prog.Package(qpos.info.Pkg)
|
||||
if pkg == nil {
|
||||
@ -38,6 +36,7 @@ func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
// call found to originate from target.
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
cg := ptrAnalysis(o).CallGraph
|
||||
cg.DeleteSyntheticNodes()
|
||||
edges := cg.CreateNode(target).In
|
||||
// TODO(adonovan): sort + dedup calls to ensure test determinism.
|
||||
|
||||
|
@ -5,46 +5,96 @@
|
||||
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.
|
||||
//
|
||||
// TODO(adonovan): add options for restricting the display to a region
|
||||
// of interest: function, package, subgraph, dirtree, goroutine, etc.
|
||||
//
|
||||
// TODO(adonovan): add an option to partition edges by call site.
|
||||
//
|
||||
// TODO(adonovan): elide nodes for synthetic functions?
|
||||
//
|
||||
func doCallgraph(o *Oracle, _ *QueryPos) (queryResult, error) {
|
||||
// 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 complete callgraph.
|
||||
// Run the pointer analysis and build the callgraph.
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
ptares := ptrAnalysis(o)
|
||||
cg := ptrAnalysis(o).CallGraph
|
||||
cg.DeleteSyntheticNodes()
|
||||
|
||||
return &callgraphResult{
|
||||
callgraph: ptares.CallGraph,
|
||||
}, nil
|
||||
var qpkg *types.Package
|
||||
var roots []*callgraph.Node
|
||||
if qpos == nil {
|
||||
// No -pos provided: show complete callgraph.
|
||||
roots = append(roots, cg.Root)
|
||||
|
||||
} 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.
|
||||
var nodes, remove []*callgraph.Node
|
||||
for fn, cgn := range cg.Nodes {
|
||||
if isQueryPkg(fn) {
|
||||
nodes = append(nodes, cgn)
|
||||
} else {
|
||||
remove = append(remove, cgn)
|
||||
}
|
||||
}
|
||||
|
||||
// Compact the Node.ID sequence of the remaining
|
||||
// nodes, preserving the original order.
|
||||
sort.Sort(nodesByID(nodes))
|
||||
for i, cgn := range nodes {
|
||||
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 nodes {
|
||||
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 {
|
||||
callgraph *callgraph.Graph
|
||||
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 the entire program.
|
||||
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)
|
||||
@ -61,16 +111,24 @@ Non-numbered nodes indicate back- or cross-edges to the node whose
|
||||
}
|
||||
sort.Sort(funcs)
|
||||
|
||||
printf(caller.Func, "%d\t%*s%s", num, 4*indent, "", caller.Func)
|
||||
printf(caller.Func, "%d\t%*s%s", num, 4*indent, "", caller.Func.RelString(r.qpkg))
|
||||
for _, callee := range funcs {
|
||||
print(r.callgraph.Nodes[callee], indent+1)
|
||||
print(r.nodes[callee], indent+1)
|
||||
}
|
||||
} else {
|
||||
printf(caller, "\t%*s%s (%d)", 4*indent, "", caller.Func, num)
|
||||
printf(caller.Func, "\t%*s%s (%d)", 4*indent, "", caller.Func.RelString(r.qpkg), num)
|
||||
}
|
||||
}
|
||||
print(r.callgraph.Root, 0)
|
||||
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
|
||||
|
||||
@ -79,8 +137,8 @@ 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.callgraph.Nodes))
|
||||
for _, n := range r.callgraph.Nodes {
|
||||
cg := make([]serial.CallGraph, len(r.nodes))
|
||||
for _, n := range r.nodes {
|
||||
j := &cg[n.ID]
|
||||
fn := n.Func
|
||||
j.Name = fn.String()
|
||||
|
@ -43,6 +43,7 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
// Run the pointer analysis and build the complete call graph.
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
cg := ptrAnalysis(o).CallGraph
|
||||
cg.DeleteSyntheticNodes()
|
||||
|
||||
// Search for an arbitrary path from a root to the target function.
|
||||
isEnd := func(n *callgraph.Node) bool { return n.Func == target }
|
||||
|
@ -257,13 +257,10 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var qpos *QueryPos
|
||||
if minfo.needs&(needPos|needExactPos) != 0 {
|
||||
qpos, err = ParseQueryPos(iprog, pos, minfo.needs&needExactPos != 0)
|
||||
if err != nil {
|
||||
qpos, err := ParseQueryPos(iprog, pos, minfo.needs&needExactPos != 0)
|
||||
if err != nil && minfo.needs&(needPos|needExactPos) != 0 {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// SSA is built and we have the QueryPos.
|
||||
// Release the other ASTs and type info to the GC.
|
||||
|
@ -55,7 +55,7 @@ type query struct {
|
||||
verb string // query mode, e.g. "callees"
|
||||
posn token.Position // position of of query
|
||||
filename string
|
||||
start, end int // selection of file to pass to oracle
|
||||
queryPos string // value of -pos flag
|
||||
}
|
||||
|
||||
func parseRegexp(text string) (*regexp.Regexp, error) {
|
||||
@ -108,6 +108,14 @@ func parseQueries(t *testing.T, filename string) []*query {
|
||||
continue
|
||||
}
|
||||
|
||||
q := &query{
|
||||
id: id,
|
||||
verb: match[1],
|
||||
filename: filename,
|
||||
posn: posn,
|
||||
}
|
||||
|
||||
if match[3] != `"nopos"` {
|
||||
selectRe, err := parseRegexp(match[3])
|
||||
if err != nil {
|
||||
t.Errorf("%s: %s", posn, err)
|
||||
@ -129,15 +137,11 @@ func parseQueries(t *testing.T, filename string) []*query {
|
||||
// Assumes ASCII. TODO(adonovan): test on UTF-8.
|
||||
linestart := posn.Offset - (posn.Column - 1)
|
||||
|
||||
// Compute the file offsets
|
||||
q := &query{
|
||||
id: id,
|
||||
verb: match[1],
|
||||
posn: posn,
|
||||
filename: filename,
|
||||
start: linestart + loc[0],
|
||||
end: linestart + loc[1],
|
||||
// Compute the file offsets.
|
||||
q.queryPos = fmt.Sprintf("%s:#%d,#%d",
|
||||
filename, linestart+loc[0], linestart+loc[1])
|
||||
}
|
||||
|
||||
queries = append(queries, q)
|
||||
queriesById[id] = q
|
||||
}
|
||||
@ -168,7 +172,7 @@ func doQuery(out io.Writer, q *query, useJson bool) {
|
||||
buildContext.GOPATH = "testdata"
|
||||
res, err := oracle.Query([]string{q.filename},
|
||||
q.verb,
|
||||
fmt.Sprintf("%s:#%d,#%d", q.filename, q.start, q.end),
|
||||
q.queryPos,
|
||||
nil, // ptalog,
|
||||
&buildContext,
|
||||
true) // reflection
|
||||
|
18
oracle/testdata/src/main/callgraph-json.golden
vendored
18
oracle/testdata/src/main/callgraph-json.golden
vendored
@ -2,29 +2,21 @@
|
||||
{
|
||||
"mode": "callgraph",
|
||||
"callgraph": [
|
||||
{
|
||||
"name": "\u003croot\u003e",
|
||||
"pos": "-",
|
||||
"children": [
|
||||
1,
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main.main",
|
||||
"pos": "testdata/src/main/callgraph-json.go:24:6",
|
||||
"children": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main.call",
|
||||
"pos": "testdata/src/main/callgraph-json.go:12:6",
|
||||
"children": [
|
||||
6,
|
||||
7
|
||||
5,
|
||||
6
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -35,7 +27,7 @@
|
||||
"name": "main.call2",
|
||||
"pos": "testdata/src/main/callgraph-json.go:19:6",
|
||||
"children": [
|
||||
8
|
||||
7
|
||||
]
|
||||
},
|
||||
{
|
||||
|
8
oracle/testdata/src/main/callgraph.go
vendored
8
oracle/testdata/src/main/callgraph.go
vendored
@ -4,6 +4,8 @@ package main
|
||||
// See go.tools/oracle/oracle_test.go for explanation.
|
||||
// See callgraph.golden for expected query results.
|
||||
|
||||
import "lib"
|
||||
|
||||
func A() {}
|
||||
|
||||
func B() {}
|
||||
@ -45,10 +47,14 @@ func main() {
|
||||
f()
|
||||
}
|
||||
i.f()
|
||||
|
||||
lib.Func()
|
||||
}
|
||||
|
||||
func deadcode() {
|
||||
main()
|
||||
}
|
||||
|
||||
// @callgraph callgraph "^"
|
||||
// @callgraph callgraph-main "^"
|
||||
|
||||
// @callgraph callgraph-complete "nopos"
|
||||
|
34
oracle/testdata/src/main/callgraph.golden
vendored
34
oracle/testdata/src/main/callgraph.golden
vendored
@ -1,4 +1,20 @@
|
||||
-------- @callgraph callgraph --------
|
||||
-------- @callgraph callgraph-main --------
|
||||
|
||||
Below is a call graph of package main.
|
||||
The numbered nodes form a spanning tree.
|
||||
Non-numbered nodes indicate back- or cross-edges to the node whose
|
||||
number follows in parentheses.
|
||||
|
||||
0 init
|
||||
1 main
|
||||
2 call
|
||||
3 A
|
||||
4 B
|
||||
5 call2
|
||||
6 func@33.8
|
||||
7 nop
|
||||
|
||||
-------- @callgraph callgraph-complete --------
|
||||
|
||||
Below is a call graph of the entire program.
|
||||
The numbered nodes form a spanning tree.
|
||||
@ -7,11 +23,13 @@ Non-numbered nodes indicate back- or cross-edges to the node whose
|
||||
|
||||
0 <root>
|
||||
1 main.init
|
||||
2 main.main
|
||||
3 main.call
|
||||
4 main.A
|
||||
5 main.B
|
||||
6 main.call2
|
||||
7 func@31.8
|
||||
8 main.nop
|
||||
2 lib.init
|
||||
3 main.main
|
||||
4 lib.Func
|
||||
5 main.call
|
||||
6 main.A
|
||||
7 main.B
|
||||
8 main.call2
|
||||
9 func@33.8
|
||||
10 main.nop
|
||||
|
||||
|
10
oracle/testdata/src/main/callgraph2.golden
vendored
10
oracle/testdata/src/main/callgraph2.golden
vendored
@ -1,13 +1,11 @@
|
||||
-------- @callgraph callgraph --------
|
||||
|
||||
Below is a call graph of the entire program.
|
||||
Below is a call graph of package main.
|
||||
The numbered nodes form a spanning tree.
|
||||
Non-numbered nodes indicate back- or cross-edges to the node whose
|
||||
number follows in parentheses.
|
||||
|
||||
0 <root>
|
||||
1 main.init
|
||||
2 reflect.init
|
||||
3 main.main
|
||||
4 main.f
|
||||
0 init
|
||||
1 main
|
||||
2 f
|
||||
|
||||
|
10
oracle/testdata/src/main/calls.go
vendored
10
oracle/testdata/src/main/calls.go
vendored
@ -67,6 +67,15 @@ func main() {
|
||||
f()
|
||||
}
|
||||
i.f() // @callees callees-err-nil-interface "i.f"
|
||||
|
||||
i = new(myint)
|
||||
i.f() // @callees callees-not-a-wrapper "f"
|
||||
}
|
||||
|
||||
type myint int
|
||||
|
||||
func (myint) f() {
|
||||
// @callers callers-not-a-wrapper "^"
|
||||
}
|
||||
|
||||
var dynamic = func() {}
|
||||
@ -88,5 +97,4 @@ var global = 123 // @callers callers-global "global"
|
||||
// are in turn called by it.
|
||||
func init() {
|
||||
// @callstack callstack-init "^"
|
||||
|
||||
}
|
||||
|
8
oracle/testdata/src/main/calls.golden
vendored
8
oracle/testdata/src/main/calls.golden
vendored
@ -79,6 +79,14 @@ dynamic function call on nil value
|
||||
-------- @callees callees-err-nil-interface --------
|
||||
dynamic method call on nil value
|
||||
|
||||
-------- @callees callees-not-a-wrapper --------
|
||||
this dynamic method call dispatches to:
|
||||
(main.myint).f
|
||||
|
||||
-------- @callers callers-not-a-wrapper --------
|
||||
(main.myint).f is called from these 1 sites:
|
||||
dynamic method call from main.main
|
||||
|
||||
-------- @callees callees-err-deadcode2 --------
|
||||
this static function call dispatches to:
|
||||
main.main
|
||||
|
Loading…
Reference in New Issue
Block a user