go.tools/call: a callgraph API.
This package provides a simple abstraction of a call graph, capable of representing context sensitive and insensitive graphs. It also provides some basic utilities and algorithms. This simplifies clients such as the oracle, and makes similar clients (e.g. an offline version of the oracle) easier to write. R=crawshaw, gri CC=golang-dev https://golang.org/cl/13901044
This commit is contained in:
parent
a6c151c04d
commit
39779f52c3
|
@ -0,0 +1,108 @@
|
|||
// 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 call defines the call graph abstraction and various algorithms
|
||||
and utilities to operate on it. It does not provide a concrete
|
||||
implementation but permits other analyses (such as pointer analyses or
|
||||
Rapid Type Analysis) to expose their own call graphs in a
|
||||
representation-independent manner.
|
||||
|
||||
A call graph is a labelled directed graph whose nodes represent
|
||||
functions and whose edge labels represent syntactic function call
|
||||
sites. The presence of a labelled edge (caller, site, callee)
|
||||
indicates that caller may call callee at the specified call site.
|
||||
|
||||
A call graph is a multigraph: it may contain multiple edges (caller,
|
||||
*, callee) connecting the same pair of nodes, so long as the edges
|
||||
differ by label; this occurs when one function calls another function
|
||||
from multiple call sites. Also, it may contain multiple edges
|
||||
(caller, site, *) that differ only by callee; this indicates a
|
||||
polymorphic call.
|
||||
|
||||
A call graph is called CONTEXT INSENSITIVE if no two nodes in N
|
||||
represent the same syntactic function declaration, i.e. the set of
|
||||
nodes and the set of syntactic functions are in one-to-one
|
||||
correspondence.
|
||||
|
||||
A context-sensitive call graph may have multiple nodes corresponding
|
||||
to the same function; this may yield a more precise approximation to
|
||||
the calling behavior of the program. Consider this program:
|
||||
|
||||
func Apply(fn func(V), value V) { fn(value) }
|
||||
Apply(F, v1)
|
||||
...
|
||||
Apply(G, v2)
|
||||
|
||||
A context-insensitive call graph would represent all calls to Apply by
|
||||
the same node, so that node would have successors F and G. A
|
||||
context-sensitive call graph might represent the first and second
|
||||
calls to Apply by distinct nodes, so that the first would have
|
||||
successor F and the second would have successor G. This is a more
|
||||
precise representation of the possible behaviors of the program.
|
||||
|
||||
A SOUND call graph is one that overapproximates the dynamic calling
|
||||
behaviors of the program in all possible executions. One call graph
|
||||
is more PRECISE than another if it is a smaller overapproximation of
|
||||
the dynamic behavior.
|
||||
|
||||
All call graphs have a synthetic root node which is responsible for
|
||||
calling main() and init().
|
||||
|
||||
Calls to built-in functions (e.g. panic, println) are not represented
|
||||
in the call graph; they are treated like built-in operators of the
|
||||
language.
|
||||
|
||||
*/
|
||||
package call
|
||||
|
||||
import "code.google.com/p/go.tools/ssa"
|
||||
|
||||
// A Graph represents a call graph.
|
||||
//
|
||||
// A graph may contain nodes that are not reachable from the root.
|
||||
// If the call graph is sound, such nodes indicate unreachable
|
||||
// functions.
|
||||
//
|
||||
type Graph interface {
|
||||
Root() GraphNode // the distinguished root node
|
||||
Nodes() []GraphNode // new unordered set of all nodes
|
||||
}
|
||||
|
||||
// A GraphNode represents a node in a call graph.
|
||||
//
|
||||
// If the call graph is context sensitive, there may be multiple
|
||||
// GraphNodes with the same Func(); the identity of the graph node
|
||||
// indicates the context.
|
||||
//
|
||||
// Sites returns the set of syntactic call sites within this function.
|
||||
//
|
||||
// For nodes representing synthetic or intrinsic functions
|
||||
// (e.g. reflect.Call, or the root of the call graph), Sites() returns
|
||||
// a slice containing a single nil value to indicate the synthetic
|
||||
// call site, and each edge in Edges() has a nil Site.
|
||||
//
|
||||
// All nodes "belong" to a single graph and must not be mixed with
|
||||
// nodes belonging to another graph.
|
||||
//
|
||||
// A site may appear in Sites() but not in {e.Site | e ∈ Edges()}.
|
||||
// This indicates that that caller node was unreachable, or that the
|
||||
// call was dynamic yet no func or interface values flow to the call
|
||||
// site.
|
||||
//
|
||||
// Clients should not mutate the node via the results of its methods.
|
||||
//
|
||||
type GraphNode interface {
|
||||
Func() *ssa.Function // the function this node represents
|
||||
Sites() []ssa.CallInstruction // new unordered set of call sites within this function
|
||||
Edges() []Edge // new unordered set of outgoing edges
|
||||
}
|
||||
|
||||
// A Edge represents an edge in the call graph.
|
||||
type Edge struct {
|
||||
Caller GraphNode
|
||||
Site ssa.CallInstruction
|
||||
Callee GraphNode
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// 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 call
|
||||
|
||||
// This file provides various representation-independent utilities
|
||||
// over call graphs, such as visitation and path search.
|
||||
//
|
||||
// TODO(adonovan):
|
||||
//
|
||||
// Consider adding lookup functions such as:
|
||||
// FindSitesByPos(g Graph, lparen token.Pos) []Site
|
||||
// FindSitesByCallExpr(g Graph, expr *ast.CallExpr) []Site
|
||||
// FindSitesByInstr(g Graph, instr ssa.CallInstruction) []Site
|
||||
// FindNodesByFunc(g Graph, fn *ssa.Function) []GraphNode
|
||||
// (Counterargument: they're all inefficient linear scans; if the
|
||||
// caller does it explicitly there may be opportunities to optimize.
|
||||
//
|
||||
// Add a utility function to eliminate all context from a call graph.
|
||||
|
||||
// CalleesOf returns a new set containing all direct callees of the
|
||||
// caller node in call graph g.
|
||||
//
|
||||
func CalleesOf(caller GraphNode) map[GraphNode]bool {
|
||||
callees := make(map[GraphNode]bool)
|
||||
for _, e := range caller.Edges() {
|
||||
callees[e.Callee] = true
|
||||
}
|
||||
return callees
|
||||
}
|
||||
|
||||
// GraphVisitEdges visits all the edges in graph g in depth-first order.
|
||||
// The edge function is called for each edge in postorder.
|
||||
//
|
||||
func GraphVisitEdges(g Graph, edge func(Edge)) {
|
||||
seen := make(map[GraphNode]bool)
|
||||
var visit func(n GraphNode)
|
||||
visit = func(n GraphNode) {
|
||||
if !seen[n] {
|
||||
seen[n] = true
|
||||
for _, e := range n.Edges() {
|
||||
visit(e.Callee)
|
||||
edge(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, n := range g.Nodes() {
|
||||
visit(n)
|
||||
}
|
||||
}
|
||||
|
||||
// PathSearch finds an arbitrary path starting at node start and
|
||||
// ending at some node for which isEnd() returns true. On success,
|
||||
// PathSearch returns the path as an ordered list of edges; on
|
||||
// failure, it returns nil.
|
||||
//
|
||||
func PathSearch(start GraphNode, isEnd func(GraphNode) bool) []Edge {
|
||||
stack := make([]Edge, 0, 32)
|
||||
seen := make(map[GraphNode]bool)
|
||||
var search func(n GraphNode) []Edge
|
||||
search = func(n GraphNode) []Edge {
|
||||
if !seen[n] {
|
||||
seen[n] = true
|
||||
if isEnd(n) {
|
||||
return stack
|
||||
}
|
||||
for _, e := range n.Edges() {
|
||||
stack = append(stack, e) // push
|
||||
if found := search(e.Callee); found != nil {
|
||||
return found
|
||||
}
|
||||
stack = stack[:len(stack)-1] // pop
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return search(start)
|
||||
}
|
Loading…
Reference in New Issue