go.tools/go/callgraph: simplifications to API.
1) We remove context sensitivity from API. The pointer analysis is not sufficiently context-sensitive for the context information to be worth exposing. (The actual analysis precision still benefits from being context-sensitive, though.) Since all clients would discard the context info, we now do that for them. 2) Make the graph doubly-linked. Edges are now shared by the Nodes at both ends of the edge so it's possible to navigate more easily (e.g. to the callers). 3) Graph and Node are now concrete, not interfaces. Less code in every file! LGTM=crawshaw R=crawshaw CC=golang-codereviews https://golang.org/cl/66460043
This commit is contained in:
parent
28104d2c91
commit
829d52f2e8
|
@ -4,11 +4,8 @@
|
|||
|
||||
/*
|
||||
|
||||
Package callgraph 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.
|
||||
Package callgraph defines the call graph and various algorithms
|
||||
and utilities to operate on it.
|
||||
|
||||
A call graph is a labelled directed graph whose nodes represent
|
||||
functions and whose edge labels represent syntactic function call
|
||||
|
@ -22,27 +19,6 @@ 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
|
||||
|
@ -58,6 +34,11 @@ language.
|
|||
*/
|
||||
package callgraph
|
||||
|
||||
// TODO(adonovan): add a function to eliminate wrappers from the
|
||||
// callgraph, preserving topology.
|
||||
// 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"
|
||||
|
||||
// A Graph represents a call graph.
|
||||
|
@ -66,43 +47,49 @@ import "code.google.com/p/go.tools/go/ssa"
|
|||
// If the call graph is sound, such nodes indicate unreachable
|
||||
// functions.
|
||||
//
|
||||
type Graph interface {
|
||||
Root() Node // the distinguished root node
|
||||
Nodes() []Node // new unordered set of all nodes
|
||||
type Graph struct {
|
||||
Root *Node // the distinguished root node
|
||||
Nodes map[*ssa.Function]*Node // all nodes by function
|
||||
}
|
||||
|
||||
// New returns a new Graph with the specified root node.
|
||||
func New(root *ssa.Function) *Graph {
|
||||
g := &Graph{Nodes: make(map[*ssa.Function]*Node)}
|
||||
g.Root = g.CreateNode(root)
|
||||
return g
|
||||
}
|
||||
|
||||
// CreateNode returns the Node for fn, creating it if not present.
|
||||
func (g *Graph) CreateNode(fn *ssa.Function) *Node {
|
||||
n, ok := g.Nodes[fn]
|
||||
if !ok {
|
||||
n = &Node{Func: fn, ID: len(g.Nodes)}
|
||||
g.Nodes[fn] = n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// A Node represents a node in a call graph.
|
||||
//
|
||||
// If the call graph is context sensitive, there may be multiple
|
||||
// Nodes 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 Node 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
|
||||
type Node struct {
|
||||
Func *ssa.Function // the function this node represents
|
||||
ID int // 0-based sequence number
|
||||
In []*Edge // unordered set of incoming call edges (n.In[*].Callee == n)
|
||||
Out []*Edge // unordered set of outgoing call edges (n.Out[*].Caller == n)
|
||||
}
|
||||
|
||||
// A Edge represents an edge in the call graph.
|
||||
//
|
||||
// Site is nil for edges originating in synthetic or intrinsic
|
||||
// functions, e.g. reflect.Call or the root of the call graph.
|
||||
type Edge struct {
|
||||
Caller Node
|
||||
Caller *Node
|
||||
Site ssa.CallInstruction
|
||||
Callee Node
|
||||
Callee *Node
|
||||
}
|
||||
|
||||
// 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}
|
||||
callee.In = append(callee.In, e)
|
||||
caller.Out = append(caller.Out, e)
|
||||
}
|
||||
|
|
|
@ -4,27 +4,15 @@
|
|||
|
||||
package callgraph
|
||||
|
||||
// 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) []Node
|
||||
// (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.
|
||||
// This file provides various utilities over call graphs, such as
|
||||
// visitation and path search.
|
||||
|
||||
// CalleesOf returns a new set containing all direct callees of the
|
||||
// caller node.
|
||||
//
|
||||
func CalleesOf(caller Node) map[Node]bool {
|
||||
callees := make(map[Node]bool)
|
||||
for _, e := range caller.Edges() {
|
||||
func CalleesOf(caller *Node) map[*Node]bool {
|
||||
callees := make(map[*Node]bool)
|
||||
for _, e := range caller.Out {
|
||||
callees[e.Callee] = true
|
||||
}
|
||||
return callees
|
||||
|
@ -35,13 +23,13 @@ func CalleesOf(caller Node) map[Node]bool {
|
|||
// returns non-nil, visitation stops and GraphVisitEdges returns that
|
||||
// value.
|
||||
//
|
||||
func GraphVisitEdges(g Graph, edge func(Edge) error) error {
|
||||
seen := make(map[Node]bool)
|
||||
var visit func(n Node) error
|
||||
visit = func(n Node) error {
|
||||
func GraphVisitEdges(g *Graph, edge func(*Edge) error) error {
|
||||
seen := make(map[*Node]bool)
|
||||
var visit func(n *Node) error
|
||||
visit = func(n *Node) error {
|
||||
if !seen[n] {
|
||||
seen[n] = true
|
||||
for _, e := range n.Edges() {
|
||||
for _, e := range n.Out {
|
||||
if err := visit(e.Callee); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -52,7 +40,7 @@ func GraphVisitEdges(g Graph, edge func(Edge) error) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
for _, n := range g.Nodes() {
|
||||
for _, n := range g.Nodes {
|
||||
if err := visit(n); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -65,17 +53,17 @@ func GraphVisitEdges(g Graph, edge func(Edge) error) error {
|
|||
// PathSearch returns the path as an ordered list of edges; on
|
||||
// failure, it returns nil.
|
||||
//
|
||||
func PathSearch(start Node, isEnd func(Node) bool) []Edge {
|
||||
stack := make([]Edge, 0, 32)
|
||||
seen := make(map[Node]bool)
|
||||
var search func(n Node) []Edge
|
||||
search = func(n Node) []Edge {
|
||||
func PathSearch(start *Node, isEnd func(*Node) bool) []*Edge {
|
||||
stack := make([]*Edge, 0, 32)
|
||||
seen := make(map[*Node]bool)
|
||||
var search func(n *Node) []*Edge
|
||||
search = func(n *Node) []*Edge {
|
||||
if !seen[n] {
|
||||
seen[n] = true
|
||||
if isEnd(n) {
|
||||
return stack
|
||||
}
|
||||
for _, e := range n.Edges() {
|
||||
for _, e := range n.Out {
|
||||
stack = append(stack, e) // push
|
||||
if found := search(e.Callee); found != nil {
|
||||
return found
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"os"
|
||||
"reflect"
|
||||
|
||||
"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/go/types/typeutil"
|
||||
|
@ -346,7 +347,7 @@ func Analyze(config *Config) *Result {
|
|||
}
|
||||
a.computeTrackBits()
|
||||
|
||||
root := a.generate()
|
||||
a.generate()
|
||||
|
||||
if a.log != nil {
|
||||
// Show size of constraint system.
|
||||
|
@ -374,34 +375,37 @@ func Analyze(config *Config) *Result {
|
|||
}
|
||||
}
|
||||
|
||||
// Create callgraph.Nodes in deterministic order.
|
||||
if cg := a.result.CallGraph; cg != nil {
|
||||
for _, caller := range a.cgnodes {
|
||||
cg.CreateNode(caller.fn)
|
||||
}
|
||||
}
|
||||
|
||||
// Add dynamic edges to call graph.
|
||||
for _, caller := range a.cgnodes {
|
||||
for _, site := range caller.sites {
|
||||
for callee := range a.nodes[site.targets].pts {
|
||||
a.callEdge(site, callee)
|
||||
a.callEdge(caller, site, callee)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if a.config.BuildCallGraph {
|
||||
a.result.CallGraph = &cgraph{root, a.cgnodes}
|
||||
}
|
||||
|
||||
return a.result
|
||||
}
|
||||
|
||||
// callEdge is called for each edge in the callgraph.
|
||||
// calleeid is the callee's object node (has otFunction flag).
|
||||
//
|
||||
func (a *analysis) callEdge(site *callsite, calleeid nodeid) {
|
||||
func (a *analysis) callEdge(caller *cgnode, site *callsite, calleeid nodeid) {
|
||||
obj := a.nodes[calleeid].obj
|
||||
if obj.flags&otFunction == 0 {
|
||||
panic(fmt.Sprintf("callEdge %s -> n%d: not a function object", site, calleeid))
|
||||
}
|
||||
callee := obj.cgn
|
||||
|
||||
if a.config.BuildCallGraph {
|
||||
site.callees = append(site.callees, callee)
|
||||
if cg := a.result.CallGraph; cg != nil {
|
||||
callgraph.AddEdge(cg.CreateNode(caller.fn), site.instr, cg.CreateNode(callee.fn))
|
||||
}
|
||||
|
||||
if a.log != nil {
|
||||
|
|
|
@ -115,7 +115,7 @@ type Warning struct {
|
|||
// See Config for how to request the various Result components.
|
||||
//
|
||||
type Result struct {
|
||||
CallGraph callgraph.Graph // discovered call graph
|
||||
CallGraph *callgraph.Graph // discovered call graph
|
||||
Queries map[ssa.Value]Pointer // pts(v) for each v in Config.Queries.
|
||||
IndirectQueries map[ssa.Value]Pointer // pts(*v) for each v in Config.IndirectQueries.
|
||||
Warnings []Warning // warnings of unsoundness
|
||||
|
|
|
@ -4,35 +4,15 @@
|
|||
|
||||
package pointer
|
||||
|
||||
// This file defines our implementation of the callgraph API.
|
||||
// This file defines the internal (context-sensitive) call graph.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"code.google.com/p/go.tools/go/callgraph"
|
||||
"code.google.com/p/go.tools/go/ssa"
|
||||
)
|
||||
|
||||
// cgraph implements callgraph.Graph.
|
||||
type cgraph struct {
|
||||
root *cgnode
|
||||
nodes []*cgnode
|
||||
}
|
||||
|
||||
func (g *cgraph) Nodes() []callgraph.Node {
|
||||
nodes := make([]callgraph.Node, len(g.nodes))
|
||||
for i, node := range g.nodes {
|
||||
nodes[i] = node
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (g *cgraph) Root() callgraph.Node {
|
||||
return g.root
|
||||
}
|
||||
|
||||
// cgnode implements callgraph.Node.
|
||||
type cgnode struct {
|
||||
fn *ssa.Function
|
||||
obj nodeid // start of this contour's object block
|
||||
|
@ -40,33 +20,6 @@ type cgnode struct {
|
|||
callersite *callsite // where called from, if known; nil for shared contours
|
||||
}
|
||||
|
||||
func (n *cgnode) Func() *ssa.Function {
|
||||
return n.fn
|
||||
}
|
||||
|
||||
func (n *cgnode) Sites() []ssa.CallInstruction {
|
||||
sites := make([]ssa.CallInstruction, len(n.sites))
|
||||
for i, site := range n.sites {
|
||||
sites[i] = site.instr
|
||||
}
|
||||
return sites
|
||||
}
|
||||
|
||||
func (n *cgnode) Edges() []callgraph.Edge {
|
||||
var numEdges int
|
||||
for _, site := range n.sites {
|
||||
numEdges += len(site.callees)
|
||||
}
|
||||
edges := make([]callgraph.Edge, 0, numEdges)
|
||||
|
||||
for _, site := range n.sites {
|
||||
for _, callee := range site.callees {
|
||||
edges = append(edges, callgraph.Edge{Caller: n, Site: site.instr, Callee: callee})
|
||||
}
|
||||
}
|
||||
return edges
|
||||
}
|
||||
|
||||
func (n *cgnode) String() string {
|
||||
return fmt.Sprintf("cg%d:%s", n.obj, n.fn)
|
||||
}
|
||||
|
@ -79,7 +32,6 @@ func (n *cgnode) String() string {
|
|||
type callsite struct {
|
||||
targets nodeid // pts(·) contains objects for dynamically called functions
|
||||
instr ssa.CallInstruction // the call instruction; nil for synthetic/intrinsic
|
||||
callees []*cgnode // unordered set of callees of this site
|
||||
}
|
||||
|
||||
func (c *callsite) String() string {
|
||||
|
|
|
@ -85,10 +85,10 @@ func main() {
|
|||
// By converting to strings, we de-duplicate nodes
|
||||
// representing the same function due to context sensitivity.
|
||||
var edges []string
|
||||
callgraph.GraphVisitEdges(result.CallGraph, func(edge callgraph.Edge) error {
|
||||
caller := edge.Caller.Func()
|
||||
callgraph.GraphVisitEdges(result.CallGraph, func(edge *callgraph.Edge) error {
|
||||
caller := edge.Caller.Func
|
||||
if caller.Pkg == mainPkg {
|
||||
edges = append(edges, fmt.Sprint(caller, " --> ", edge.Callee.Func()))
|
||||
edges = append(edges, fmt.Sprint(caller, " --> ", edge.Callee.Func))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"code.google.com/p/go.tools/go/callgraph"
|
||||
"code.google.com/p/go.tools/go/ssa"
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
)
|
||||
|
@ -620,7 +621,7 @@ func (a *analysis) genStaticCall(caller *cgnode, site *callsite, call *ssa.CallC
|
|||
} else {
|
||||
obj = a.objectNode(nil, fn) // shared contour
|
||||
}
|
||||
a.callEdge(site, obj)
|
||||
a.callEdge(caller, site, obj)
|
||||
|
||||
sig := call.Signature()
|
||||
|
||||
|
@ -726,7 +727,7 @@ func (a *analysis) genInvokeReflectType(caller *cgnode, site *callsite, call *ss
|
|||
fn := a.prog.LookupMethod(a.reflectRtypePtr, call.Method.Pkg(), call.Method.Name())
|
||||
|
||||
obj := a.makeFunctionObject(fn, site) // new contour for this call
|
||||
a.callEdge(site, obj)
|
||||
a.callEdge(caller, site, obj)
|
||||
|
||||
// From now on, it's essentially a static call, but little is
|
||||
// gained by factoring together the code for both cases.
|
||||
|
@ -1213,9 +1214,7 @@ func (a *analysis) genMethodsOf(T types.Type) {
|
|||
}
|
||||
|
||||
// generate generates offline constraints for the entire program.
|
||||
// It returns the synthetic root of the callgraph.
|
||||
//
|
||||
func (a *analysis) generate() *cgnode {
|
||||
func (a *analysis) generate() {
|
||||
// Create a dummy node since we use the nodeid 0 for
|
||||
// non-pointerlike variables.
|
||||
a.addNodes(tInvalid, "(zero)")
|
||||
|
@ -1232,6 +1231,10 @@ func (a *analysis) generate() *cgnode {
|
|||
|
||||
root := a.genRootCalls()
|
||||
|
||||
if a.config.BuildCallGraph {
|
||||
a.result.CallGraph = callgraph.New(root.fn)
|
||||
}
|
||||
|
||||
// Create nodes and constraints for all methods of all types
|
||||
// that are dynamically accessible via reflection or interfaces.
|
||||
for _, T := range a.prog.TypesWithMethodSets() {
|
||||
|
@ -1253,6 +1256,4 @@ func (a *analysis) generate() *cgnode {
|
|||
a.endObject(obj, nil, "<command-line args>")
|
||||
a.addressOf(T, a.objectNode(nil, os.Var("Args")), obj)
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
||||
|
|
|
@ -355,7 +355,7 @@ func ext۰NotYetImplemented(a *analysis, cgn *cgnode) {
|
|||
// TODO(adonovan): enable this warning when we've implemented
|
||||
// enough that it's not unbearably annoying.
|
||||
if true {
|
||||
fn := cgn.Func()
|
||||
fn := cgn.fn
|
||||
a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"go/token"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/go.tools/go/callgraph"
|
||||
"code.google.com/p/go.tools/go/ssa"
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
)
|
||||
|
@ -54,13 +53,6 @@ func (l Label) ReflectType() types.Type {
|
|||
return rtype
|
||||
}
|
||||
|
||||
// Context returns the analytic context in which this label's object was allocated,
|
||||
// or nil for global objects: global, const, and shared contours for functions.
|
||||
//
|
||||
func (l Label) Context() callgraph.Node {
|
||||
return l.obj.cgn
|
||||
}
|
||||
|
||||
// Path returns the path to the subelement of the object containing
|
||||
// this label. For example, ".x[*].y".
|
||||
//
|
||||
|
@ -79,7 +71,7 @@ func (l Label) Pos() token.Pos {
|
|||
}
|
||||
}
|
||||
if cgn := l.obj.cgn; cgn != nil {
|
||||
return cgn.Func().Pos()
|
||||
return cgn.fn.Pos()
|
||||
}
|
||||
return token.NoPos
|
||||
}
|
||||
|
@ -115,7 +107,7 @@ func (l Label) String() string {
|
|||
case nil:
|
||||
if l.obj.cgn != nil {
|
||||
// allocation by intrinsic or reflective operation
|
||||
s = fmt.Sprintf("<alloc in %s>", l.obj.cgn.Func())
|
||||
s = fmt.Sprintf("<alloc in %s>", l.obj.cgn.fn)
|
||||
} else {
|
||||
s = "<unknown>" // should be unreachable
|
||||
}
|
||||
|
|
|
@ -472,14 +472,14 @@ func checkTypesExpectation(e *expectation, pts pointer.PointsToSet, typ types.Ty
|
|||
|
||||
var errOK = errors.New("OK")
|
||||
|
||||
func checkCallsExpectation(prog *ssa.Program, e *expectation, cg callgraph.Graph) bool {
|
||||
func checkCallsExpectation(prog *ssa.Program, e *expectation, cg *callgraph.Graph) bool {
|
||||
found := make(map[string]int)
|
||||
err := callgraph.GraphVisitEdges(cg, func(edge callgraph.Edge) error {
|
||||
err := callgraph.GraphVisitEdges(cg, func(edge *callgraph.Edge) error {
|
||||
// Name-based matching is inefficient but it allows us to
|
||||
// match functions whose names that would not appear in an
|
||||
// index ("<root>") or which are not unique ("func@1.2").
|
||||
if edge.Caller.Func().String() == e.args[0] {
|
||||
calleeStr := edge.Callee.Func().String()
|
||||
if edge.Caller.Func.String() == e.args[0] {
|
||||
calleeStr := edge.Callee.Func.String()
|
||||
if calleeStr == e.args[1] {
|
||||
return errOK // expectation satisified; stop the search
|
||||
}
|
||||
|
|
|
@ -103,26 +103,21 @@ func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
|||
|
||||
// Dynamic call: use pointer analysis.
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
callgraph := ptrAnalysis(o).CallGraph
|
||||
cg := ptrAnalysis(o).CallGraph
|
||||
|
||||
// Find all call edges from the site.
|
||||
calleesMap := make(map[*ssa.Function]bool)
|
||||
var foundCGNode bool
|
||||
for _, n := range callgraph.Nodes() {
|
||||
if n.Func() == site.Parent() {
|
||||
foundCGNode = true
|
||||
for _, edge := range n.Edges() {
|
||||
if edge.Site == site {
|
||||
calleesMap[edge.Callee.Func()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundCGNode {
|
||||
n := cg.Nodes[site.Parent()]
|
||||
if n == nil {
|
||||
return nil, fmt.Errorf("this call site is unreachable in this analysis")
|
||||
}
|
||||
calleesMap := make(map[*ssa.Function]bool)
|
||||
for _, edge := range n.Out {
|
||||
if edge.Site == site {
|
||||
calleesMap[edge.Callee.Func] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Discard context, de-duplicate and sort.
|
||||
// De-duplicate and sort.
|
||||
funcs := make([]*ssa.Function, 0, len(calleesMap))
|
||||
for f := range calleesMap {
|
||||
funcs = append(funcs, f)
|
||||
|
|
|
@ -38,13 +38,7 @@ func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
// call found to originate from target.
|
||||
o.ptaConfig.BuildCallGraph = true
|
||||
cg := ptrAnalysis(o).CallGraph
|
||||
var edges []callgraph.Edge
|
||||
callgraph.GraphVisitEdges(cg, func(edge callgraph.Edge) error {
|
||||
if edge.Callee.Func() == target {
|
||||
edges = append(edges, edge)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
edges := cg.CreateNode(target).In
|
||||
// TODO(adonovan): sort + dedup calls to ensure test determinism.
|
||||
|
||||
return &callersResult{
|
||||
|
@ -56,12 +50,12 @@ func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
|
||||
type callersResult struct {
|
||||
target *ssa.Function
|
||||
callgraph callgraph.Graph
|
||||
edges []callgraph.Edge
|
||||
callgraph *callgraph.Graph
|
||||
edges []*callgraph.Edge
|
||||
}
|
||||
|
||||
func (r *callersResult) display(printf printfFunc) {
|
||||
root := r.callgraph.Root()
|
||||
root := r.callgraph.Root
|
||||
if r.edges == nil {
|
||||
printf(r.target, "%s is not reachable in this program.", r.target)
|
||||
} else {
|
||||
|
@ -70,18 +64,18 @@ func (r *callersResult) display(printf printfFunc) {
|
|||
if edge.Caller == root {
|
||||
printf(r.target, "the root of the call graph")
|
||||
} else {
|
||||
printf(edge.Site, "\t%s from %s", edge.Site.Common().Description(), edge.Caller.Func())
|
||||
printf(edge.Site, "\t%s from %s", edge.Site.Common().Description(), edge.Caller.Func)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *callersResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
||||
root := r.callgraph.Root()
|
||||
root := r.callgraph.Root
|
||||
var callers []serial.Caller
|
||||
for _, edge := range r.edges {
|
||||
var c serial.Caller
|
||||
c.Caller = edge.Caller.Func().String()
|
||||
c.Caller = edge.Caller.Func.String()
|
||||
if edge.Caller == root {
|
||||
c.Desc = "synthetic call"
|
||||
} else {
|
||||
|
|
|
@ -15,15 +15,9 @@ import (
|
|||
|
||||
// doCallgraph displays the entire callgraph of the current program.
|
||||
//
|
||||
// Nodes may be seem to appear multiple times due to (limited)
|
||||
// context sensitivity.
|
||||
//
|
||||
// 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 project away context sensitivity.
|
||||
// The callgraph API should provide this feature.
|
||||
//
|
||||
// TODO(adonovan): add an option to partition edges by call site.
|
||||
//
|
||||
// TODO(adonovan): elide nodes for synthetic functions?
|
||||
|
@ -41,7 +35,7 @@ func doCallgraph(o *Oracle, _ *QueryPos) (queryResult, error) {
|
|||
}
|
||||
|
||||
type callgraphResult struct {
|
||||
callgraph callgraph.Graph
|
||||
callgraph *callgraph.Graph
|
||||
}
|
||||
|
||||
func (r *callgraphResult) display(printf printfFunc) {
|
||||
|
@ -51,36 +45,10 @@ The numbered nodes form a spanning tree.
|
|||
Non-numbered nodes indicate back- or cross-edges to the node whose
|
||||
number follows in parentheses.
|
||||
`)
|
||||
root := r.callgraph.Root()
|
||||
|
||||
// context-insensitive (CI) call graph.
|
||||
ci := make(map[*ssa.Function]map[*ssa.Function]bool)
|
||||
|
||||
// 1. Visit the CS call graph and build the CI call graph.
|
||||
visited := make(map[callgraph.Node]bool)
|
||||
var visit func(caller callgraph.Node)
|
||||
visit = func(caller callgraph.Node) {
|
||||
if !visited[caller] {
|
||||
visited[caller] = true
|
||||
|
||||
cicallees := ci[caller.Func()]
|
||||
if cicallees == nil {
|
||||
cicallees = make(map[*ssa.Function]bool)
|
||||
ci[caller.Func()] = cicallees
|
||||
}
|
||||
|
||||
for _, e := range caller.Edges() {
|
||||
cicallees[e.Callee.Func()] = true
|
||||
visit(e.Callee)
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(root)
|
||||
|
||||
// 2. Print the CI callgraph.
|
||||
printed := make(map[*ssa.Function]int)
|
||||
var print func(caller *ssa.Function, indent int)
|
||||
print = func(caller *ssa.Function, indent int) {
|
||||
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
|
||||
|
@ -88,20 +56,20 @@ Non-numbered nodes indicate back- or cross-edges to the node whose
|
|||
// Sort the children into name order for deterministic* output.
|
||||
// (*mostly: anon funcs' names are not globally unique.)
|
||||
var funcs funcsByName
|
||||
for callee := range ci[caller] {
|
||||
funcs = append(funcs, callee)
|
||||
for callee := range callgraph.CalleesOf(caller) {
|
||||
funcs = append(funcs, callee.Func)
|
||||
}
|
||||
sort.Sort(funcs)
|
||||
|
||||
printf(caller, "%d\t%*s%s", num, 4*indent, "", caller)
|
||||
printf(caller.Func, "%d\t%*s%s", num, 4*indent, "", caller.Func)
|
||||
for _, callee := range funcs {
|
||||
print(callee, indent+1)
|
||||
print(r.callgraph.Nodes[callee], indent+1)
|
||||
}
|
||||
} else {
|
||||
printf(caller, "\t%*s%s (%d)", 4*indent, "", caller, num)
|
||||
printf(caller, "\t%*s%s (%d)", 4*indent, "", caller.Func, num)
|
||||
}
|
||||
}
|
||||
print(root.Func(), 0)
|
||||
print(r.callgraph.Root, 0)
|
||||
}
|
||||
|
||||
type funcsByName []*ssa.Function
|
||||
|
@ -111,21 +79,14 @@ 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) {
|
||||
nodes := r.callgraph.Nodes()
|
||||
|
||||
numbering := make(map[callgraph.Node]int)
|
||||
for i, n := range nodes {
|
||||
numbering[n] = i
|
||||
}
|
||||
|
||||
cg := make([]serial.CallGraph, len(nodes))
|
||||
for i, n := range nodes {
|
||||
j := &cg[i]
|
||||
fn := n.Func()
|
||||
cg := make([]serial.CallGraph, len(r.callgraph.Nodes))
|
||||
for _, n := range r.callgraph.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, numbering[callee])
|
||||
j.Children = append(j.Children, callee.ID)
|
||||
}
|
||||
sort.Ints(j.Children)
|
||||
}
|
||||
|
|
|
@ -45,8 +45,8 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
cg := ptrAnalysis(o).CallGraph
|
||||
|
||||
// Search for an arbitrary path from a root to the target function.
|
||||
isEnd := func(n callgraph.Node) bool { return n.Func() == target }
|
||||
callpath := callgraph.PathSearch(cg.Root(), isEnd)
|
||||
isEnd := func(n *callgraph.Node) bool { return n.Func == target }
|
||||
callpath := callgraph.PathSearch(cg.Root, isEnd)
|
||||
if callpath != nil {
|
||||
callpath = callpath[1:] // remove synthetic edge from <root>
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
type callstackResult struct {
|
||||
qpos *QueryPos
|
||||
target *ssa.Function
|
||||
callpath []callgraph.Edge
|
||||
callpath []*callgraph.Edge
|
||||
}
|
||||
|
||||
func (r *callstackResult) display(printf printfFunc) {
|
||||
|
@ -70,7 +70,7 @@ func (r *callstackResult) display(printf printfFunc) {
|
|||
printf(r.target, "%s", r.target)
|
||||
for i := len(r.callpath) - 1; i >= 0; i-- {
|
||||
edge := r.callpath[i]
|
||||
printf(edge.Site, "%s from %s", edge.Site.Common().Description(), edge.Caller.Func())
|
||||
printf(edge.Site, "%s from %s", edge.Site.Common().Description(), edge.Caller.Func)
|
||||
}
|
||||
} else {
|
||||
printf(r.target, "%s is unreachable in this analysis scope", r.target)
|
||||
|
@ -83,7 +83,7 @@ func (r *callstackResult) toSerial(res *serial.Result, fset *token.FileSet) {
|
|||
edge := r.callpath[i]
|
||||
callers = append(callers, serial.Caller{
|
||||
Pos: fset.Position(edge.Site.Pos()).String(),
|
||||
Caller: edge.Caller.Func().String(),
|
||||
Caller: edge.Caller.Func.String(),
|
||||
Desc: edge.Site.Common().Description(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -259,7 +259,7 @@ func TestMultipleQueries(t *testing.T) {
|
|||
// Loader
|
||||
var buildContext = build.Default
|
||||
buildContext.GOPATH = "testdata"
|
||||
conf := loader.Config{Build: &buildContext}
|
||||
conf := loader.Config{Build: &buildContext, SourceImports: true}
|
||||
filename := "testdata/src/main/multi.go"
|
||||
conf.CreateFromFilenames("", filename)
|
||||
iprog, err := conf.Load()
|
||||
|
|
|
@ -7,31 +7,41 @@
|
|||
"pos": "-",
|
||||
"children": [
|
||||
1,
|
||||
2
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main.init",
|
||||
"pos": "-"
|
||||
},
|
||||
{
|
||||
"name": "main.main",
|
||||
"pos": "testdata/src/main/callgraph-json.go:24:6",
|
||||
"children": [
|
||||
2,
|
||||
3,
|
||||
6,
|
||||
7,
|
||||
8
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main.call",
|
||||
"pos": "testdata/src/main/callgraph-json.go:12:6",
|
||||
"children": [
|
||||
4,
|
||||
5
|
||||
6,
|
||||
7
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main.nop",
|
||||
"pos": "testdata/src/main/callgraph-json.go:17:6"
|
||||
},
|
||||
{
|
||||
"name": "main.call2",
|
||||
"pos": "testdata/src/main/callgraph-json.go:19:6",
|
||||
"children": [
|
||||
8
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "main.init",
|
||||
"pos": "-"
|
||||
},
|
||||
{
|
||||
"name": "main.A",
|
||||
"pos": "testdata/src/main/callgraph-json.go:7:6"
|
||||
|
@ -40,21 +50,6 @@
|
|||
"name": "main.B",
|
||||
"pos": "testdata/src/main/callgraph-json.go:9:6"
|
||||
},
|
||||
{
|
||||
"name": "main.nop",
|
||||
"pos": "testdata/src/main/callgraph-json.go:17:6"
|
||||
},
|
||||
{
|
||||
"name": "main.nop",
|
||||
"pos": "testdata/src/main/callgraph-json.go:17:6"
|
||||
},
|
||||
{
|
||||
"name": "main.call2",
|
||||
"pos": "testdata/src/main/callgraph-json.go:19:6",
|
||||
"children": [
|
||||
9
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "func@31.8",
|
||||
"pos": "testdata/src/main/callgraph-json.go:31:8"
|
||||
|
|
Loading…
Reference in New Issue