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:
Alan Donovan 2014-02-20 11:57:48 -05:00
parent 28104d2c91
commit 829d52f2e8
16 changed files with 152 additions and 283 deletions

View File

@ -4,11 +4,8 @@
/* /*
Package callgraph defines the call graph abstraction and various algorithms Package callgraph defines the call graph and various algorithms
and utilities to operate on it. It does not provide a concrete and utilities to operate on it.
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 A call graph is a labelled directed graph whose nodes represent
functions and whose edge labels represent syntactic function call 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 (caller, site, *) that differ only by callee; this indicates a
polymorphic call. 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 A SOUND call graph is one that overapproximates the dynamic calling
behaviors of the program in all possible executions. One call graph behaviors of the program in all possible executions. One call graph
is more PRECISE than another if it is a smaller overapproximation of is more PRECISE than another if it is a smaller overapproximation of
@ -58,6 +34,11 @@ language.
*/ */
package callgraph 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" import "code.google.com/p/go.tools/go/ssa"
// A Graph represents a call graph. // 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 // If the call graph is sound, such nodes indicate unreachable
// functions. // functions.
// //
type Graph interface { type Graph struct {
Root() Node // the distinguished root node Root *Node // the distinguished root node
Nodes() []Node // new unordered set of all nodes 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. // A Node represents a node in a call graph.
// type Node struct {
// If the call graph is context sensitive, there may be multiple Func *ssa.Function // the function this node represents
// Nodes with the same Func(); the identity of the graph node ID int // 0-based sequence number
// indicates the context. In []*Edge // unordered set of incoming call edges (n.In[*].Callee == n)
// Out []*Edge // unordered set of outgoing call edges (n.Out[*].Caller == n)
// 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
} }
// A Edge represents an edge in the call graph. // 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 { type Edge struct {
Caller Node Caller *Node
Site ssa.CallInstruction 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)
} }

View File

@ -4,27 +4,15 @@
package callgraph package callgraph
// This file provides various representation-independent utilities // This file provides various utilities over call graphs, such as
// over call graphs, such as visitation and path search. // 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.
// CalleesOf returns a new set containing all direct callees of the // CalleesOf returns a new set containing all direct callees of the
// caller node. // caller node.
// //
func CalleesOf(caller Node) map[Node]bool { func CalleesOf(caller *Node) map[*Node]bool {
callees := make(map[Node]bool) callees := make(map[*Node]bool)
for _, e := range caller.Edges() { for _, e := range caller.Out {
callees[e.Callee] = true callees[e.Callee] = true
} }
return callees return callees
@ -35,13 +23,13 @@ func CalleesOf(caller Node) map[Node]bool {
// returns non-nil, visitation stops and GraphVisitEdges returns that // returns non-nil, visitation stops and GraphVisitEdges returns that
// value. // value.
// //
func GraphVisitEdges(g Graph, edge func(Edge) error) error { func GraphVisitEdges(g *Graph, edge func(*Edge) error) error {
seen := make(map[Node]bool) seen := make(map[*Node]bool)
var visit func(n Node) error var visit func(n *Node) error
visit = func(n Node) error { visit = func(n *Node) error {
if !seen[n] { if !seen[n] {
seen[n] = true seen[n] = true
for _, e := range n.Edges() { for _, e := range n.Out {
if err := visit(e.Callee); err != nil { if err := visit(e.Callee); err != nil {
return err return err
} }
@ -52,7 +40,7 @@ func GraphVisitEdges(g Graph, edge func(Edge) error) error {
} }
return nil return nil
} }
for _, n := range g.Nodes() { for _, n := range g.Nodes {
if err := visit(n); err != nil { if err := visit(n); err != nil {
return err 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 // PathSearch returns the path as an ordered list of edges; on
// failure, it returns nil. // failure, it returns nil.
// //
func PathSearch(start Node, isEnd func(Node) bool) []Edge { func PathSearch(start *Node, isEnd func(*Node) bool) []*Edge {
stack := make([]Edge, 0, 32) stack := make([]*Edge, 0, 32)
seen := make(map[Node]bool) seen := make(map[*Node]bool)
var search func(n Node) []Edge var search func(n *Node) []*Edge
search = func(n Node) []Edge { search = func(n *Node) []*Edge {
if !seen[n] { if !seen[n] {
seen[n] = true seen[n] = true
if isEnd(n) { if isEnd(n) {
return stack return stack
} }
for _, e := range n.Edges() { for _, e := range n.Out {
stack = append(stack, e) // push stack = append(stack, e) // push
if found := search(e.Callee); found != nil { if found := search(e.Callee); found != nil {
return found return found

View File

@ -13,6 +13,7 @@ import (
"os" "os"
"reflect" "reflect"
"code.google.com/p/go.tools/go/callgraph"
"code.google.com/p/go.tools/go/ssa" "code.google.com/p/go.tools/go/ssa"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/go/types/typeutil" "code.google.com/p/go.tools/go/types/typeutil"
@ -346,7 +347,7 @@ func Analyze(config *Config) *Result {
} }
a.computeTrackBits() a.computeTrackBits()
root := a.generate() a.generate()
if a.log != nil { if a.log != nil {
// Show size of constraint system. // 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. // Add dynamic edges to call graph.
for _, caller := range a.cgnodes { for _, caller := range a.cgnodes {
for _, site := range caller.sites { for _, site := range caller.sites {
for callee := range a.nodes[site.targets].pts { 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 return a.result
} }
// callEdge is called for each edge in the callgraph. // callEdge is called for each edge in the callgraph.
// calleeid is the callee's object node (has otFunction flag). // 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 obj := a.nodes[calleeid].obj
if obj.flags&otFunction == 0 { if obj.flags&otFunction == 0 {
panic(fmt.Sprintf("callEdge %s -> n%d: not a function object", site, calleeid)) panic(fmt.Sprintf("callEdge %s -> n%d: not a function object", site, calleeid))
} }
callee := obj.cgn callee := obj.cgn
if a.config.BuildCallGraph { if cg := a.result.CallGraph; cg != nil {
site.callees = append(site.callees, callee) callgraph.AddEdge(cg.CreateNode(caller.fn), site.instr, cg.CreateNode(callee.fn))
} }
if a.log != nil { if a.log != nil {

View File

@ -115,7 +115,7 @@ type Warning struct {
// See Config for how to request the various Result components. // See Config for how to request the various Result components.
// //
type Result struct { 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. 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. IndirectQueries map[ssa.Value]Pointer // pts(*v) for each v in Config.IndirectQueries.
Warnings []Warning // warnings of unsoundness Warnings []Warning // warnings of unsoundness

View File

@ -4,35 +4,15 @@
package pointer package pointer
// This file defines our implementation of the callgraph API. // This file defines the internal (context-sensitive) call graph.
import ( import (
"fmt" "fmt"
"go/token" "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/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 { type cgnode struct {
fn *ssa.Function fn *ssa.Function
obj nodeid // start of this contour's object block 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 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 { func (n *cgnode) String() string {
return fmt.Sprintf("cg%d:%s", n.obj, n.fn) return fmt.Sprintf("cg%d:%s", n.obj, n.fn)
} }
@ -79,7 +32,6 @@ func (n *cgnode) String() string {
type callsite struct { type callsite struct {
targets nodeid // pts(·) contains objects for dynamically called functions targets nodeid // pts(·) contains objects for dynamically called functions
instr ssa.CallInstruction // the call instruction; nil for synthetic/intrinsic instr ssa.CallInstruction // the call instruction; nil for synthetic/intrinsic
callees []*cgnode // unordered set of callees of this site
} }
func (c *callsite) String() string { func (c *callsite) String() string {

View File

@ -85,10 +85,10 @@ func main() {
// By converting to strings, we de-duplicate nodes // By converting to strings, we de-duplicate nodes
// representing the same function due to context sensitivity. // representing the same function due to context sensitivity.
var edges []string var edges []string
callgraph.GraphVisitEdges(result.CallGraph, func(edge callgraph.Edge) error { callgraph.GraphVisitEdges(result.CallGraph, func(edge *callgraph.Edge) error {
caller := edge.Caller.Func() caller := edge.Caller.Func
if caller.Pkg == mainPkg { if caller.Pkg == mainPkg {
edges = append(edges, fmt.Sprint(caller, " --> ", edge.Callee.Func())) edges = append(edges, fmt.Sprint(caller, " --> ", edge.Callee.Func))
} }
return nil return nil
}) })

View File

@ -14,6 +14,7 @@ import (
"fmt" "fmt"
"go/token" "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/ssa"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
) )
@ -620,7 +621,7 @@ func (a *analysis) genStaticCall(caller *cgnode, site *callsite, call *ssa.CallC
} else { } else {
obj = a.objectNode(nil, fn) // shared contour obj = a.objectNode(nil, fn) // shared contour
} }
a.callEdge(site, obj) a.callEdge(caller, site, obj)
sig := call.Signature() 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()) fn := a.prog.LookupMethod(a.reflectRtypePtr, call.Method.Pkg(), call.Method.Name())
obj := a.makeFunctionObject(fn, site) // new contour for this call 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 // From now on, it's essentially a static call, but little is
// gained by factoring together the code for both cases. // 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. // generate generates offline constraints for the entire program.
// It returns the synthetic root of the callgraph. func (a *analysis) generate() {
//
func (a *analysis) generate() *cgnode {
// Create a dummy node since we use the nodeid 0 for // Create a dummy node since we use the nodeid 0 for
// non-pointerlike variables. // non-pointerlike variables.
a.addNodes(tInvalid, "(zero)") a.addNodes(tInvalid, "(zero)")
@ -1232,6 +1231,10 @@ func (a *analysis) generate() *cgnode {
root := a.genRootCalls() root := a.genRootCalls()
if a.config.BuildCallGraph {
a.result.CallGraph = callgraph.New(root.fn)
}
// Create nodes and constraints for all methods of all types // Create nodes and constraints for all methods of all types
// that are dynamically accessible via reflection or interfaces. // that are dynamically accessible via reflection or interfaces.
for _, T := range a.prog.TypesWithMethodSets() { for _, T := range a.prog.TypesWithMethodSets() {
@ -1253,6 +1256,4 @@ func (a *analysis) generate() *cgnode {
a.endObject(obj, nil, "<command-line args>") a.endObject(obj, nil, "<command-line args>")
a.addressOf(T, a.objectNode(nil, os.Var("Args")), obj) a.addressOf(T, a.objectNode(nil, os.Var("Args")), obj)
} }
return root
} }

View File

@ -355,7 +355,7 @@ func ext۰NotYetImplemented(a *analysis, cgn *cgnode) {
// TODO(adonovan): enable this warning when we've implemented // TODO(adonovan): enable this warning when we've implemented
// enough that it's not unbearably annoying. // enough that it's not unbearably annoying.
if true { if true {
fn := cgn.Func() fn := cgn.fn
a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn) a.warnf(fn.Pos(), "unsound: intrinsic treatment of %s not yet implemented", fn)
} }
} }

View File

@ -9,7 +9,6 @@ import (
"go/token" "go/token"
"strings" "strings"
"code.google.com/p/go.tools/go/callgraph"
"code.google.com/p/go.tools/go/ssa" "code.google.com/p/go.tools/go/ssa"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
) )
@ -54,13 +53,6 @@ func (l Label) ReflectType() types.Type {
return rtype 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 // Path returns the path to the subelement of the object containing
// this label. For example, ".x[*].y". // this label. For example, ".x[*].y".
// //
@ -79,7 +71,7 @@ func (l Label) Pos() token.Pos {
} }
} }
if cgn := l.obj.cgn; cgn != nil { if cgn := l.obj.cgn; cgn != nil {
return cgn.Func().Pos() return cgn.fn.Pos()
} }
return token.NoPos return token.NoPos
} }
@ -115,7 +107,7 @@ func (l Label) String() string {
case nil: case nil:
if l.obj.cgn != nil { if l.obj.cgn != nil {
// allocation by intrinsic or reflective operation // 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 { } else {
s = "<unknown>" // should be unreachable s = "<unknown>" // should be unreachable
} }

View File

@ -472,14 +472,14 @@ func checkTypesExpectation(e *expectation, pts pointer.PointsToSet, typ types.Ty
var errOK = errors.New("OK") 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) 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 // Name-based matching is inefficient but it allows us to
// match functions whose names that would not appear in an // match functions whose names that would not appear in an
// index ("<root>") or which are not unique ("func@1.2"). // index ("<root>") or which are not unique ("func@1.2").
if edge.Caller.Func().String() == e.args[0] { if edge.Caller.Func.String() == e.args[0] {
calleeStr := edge.Callee.Func().String() calleeStr := edge.Callee.Func.String()
if calleeStr == e.args[1] { if calleeStr == e.args[1] {
return errOK // expectation satisified; stop the search return errOK // expectation satisified; stop the search
} }

View File

@ -103,26 +103,21 @@ func findCallees(o *Oracle, site ssa.CallInstruction) ([]*ssa.Function, error) {
// Dynamic call: use pointer analysis. // Dynamic call: use pointer analysis.
o.ptaConfig.BuildCallGraph = true o.ptaConfig.BuildCallGraph = true
callgraph := ptrAnalysis(o).CallGraph cg := ptrAnalysis(o).CallGraph
// Find all call edges from the site. // Find all call edges from the site.
calleesMap := make(map[*ssa.Function]bool) n := cg.Nodes[site.Parent()]
var foundCGNode bool if n == nil {
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 {
return nil, fmt.Errorf("this call site is unreachable in this analysis") 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)) funcs := make([]*ssa.Function, 0, len(calleesMap))
for f := range calleesMap { for f := range calleesMap {
funcs = append(funcs, f) funcs = append(funcs, f)

View File

@ -38,13 +38,7 @@ func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
// call found to originate from target. // call found to originate from target.
o.ptaConfig.BuildCallGraph = true o.ptaConfig.BuildCallGraph = true
cg := ptrAnalysis(o).CallGraph cg := ptrAnalysis(o).CallGraph
var edges []callgraph.Edge edges := cg.CreateNode(target).In
callgraph.GraphVisitEdges(cg, func(edge callgraph.Edge) error {
if edge.Callee.Func() == target {
edges = append(edges, edge)
}
return nil
})
// TODO(adonovan): sort + dedup calls to ensure test determinism. // TODO(adonovan): sort + dedup calls to ensure test determinism.
return &callersResult{ return &callersResult{
@ -56,12 +50,12 @@ func callers(o *Oracle, qpos *QueryPos) (queryResult, error) {
type callersResult struct { type callersResult struct {
target *ssa.Function target *ssa.Function
callgraph callgraph.Graph callgraph *callgraph.Graph
edges []callgraph.Edge edges []*callgraph.Edge
} }
func (r *callersResult) display(printf printfFunc) { func (r *callersResult) display(printf printfFunc) {
root := r.callgraph.Root() root := r.callgraph.Root
if r.edges == nil { if r.edges == nil {
printf(r.target, "%s is not reachable in this program.", r.target) printf(r.target, "%s is not reachable in this program.", r.target)
} else { } else {
@ -70,18 +64,18 @@ func (r *callersResult) display(printf printfFunc) {
if edge.Caller == root { if edge.Caller == root {
printf(r.target, "the root of the call graph") printf(r.target, "the root of the call graph")
} else { } 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) { func (r *callersResult) toSerial(res *serial.Result, fset *token.FileSet) {
root := r.callgraph.Root() root := r.callgraph.Root
var callers []serial.Caller var callers []serial.Caller
for _, edge := range r.edges { for _, edge := range r.edges {
var c serial.Caller var c serial.Caller
c.Caller = edge.Caller.Func().String() c.Caller = edge.Caller.Func.String()
if edge.Caller == root { if edge.Caller == root {
c.Desc = "synthetic call" c.Desc = "synthetic call"
} else { } else {

View File

@ -15,15 +15,9 @@ import (
// doCallgraph displays the entire callgraph of the current program. // 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 // TODO(adonovan): add options for restricting the display to a region
// of interest: function, package, subgraph, dirtree, goroutine, etc. // 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): add an option to partition edges by call site.
// //
// TODO(adonovan): elide nodes for synthetic functions? // TODO(adonovan): elide nodes for synthetic functions?
@ -41,7 +35,7 @@ func doCallgraph(o *Oracle, _ *QueryPos) (queryResult, error) {
} }
type callgraphResult struct { type callgraphResult struct {
callgraph callgraph.Graph callgraph *callgraph.Graph
} }
func (r *callgraphResult) display(printf printfFunc) { 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 Non-numbered nodes indicate back- or cross-edges to the node whose
number follows in parentheses. number follows in parentheses.
`) `)
root := r.callgraph.Root()
// context-insensitive (CI) call graph. printed := make(map[*callgraph.Node]int)
ci := make(map[*ssa.Function]map[*ssa.Function]bool) var print func(caller *callgraph.Node, indent int)
print = func(caller *callgraph.Node, indent int) {
// 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) {
if num, ok := printed[caller]; !ok { if num, ok := printed[caller]; !ok {
num = len(printed) num = len(printed)
printed[caller] = num 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. // Sort the children into name order for deterministic* output.
// (*mostly: anon funcs' names are not globally unique.) // (*mostly: anon funcs' names are not globally unique.)
var funcs funcsByName var funcs funcsByName
for callee := range ci[caller] { for callee := range callgraph.CalleesOf(caller) {
funcs = append(funcs, callee) funcs = append(funcs, callee.Func)
} }
sort.Sort(funcs) 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 { for _, callee := range funcs {
print(callee, indent+1) print(r.callgraph.Nodes[callee], indent+1)
} }
} else { } 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 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 (s funcsByName) Less(i, j int) bool { return s[i].String() < s[j].String() }
func (r *callgraphResult) toSerial(res *serial.Result, fset *token.FileSet) { func (r *callgraphResult) toSerial(res *serial.Result, fset *token.FileSet) {
nodes := r.callgraph.Nodes() cg := make([]serial.CallGraph, len(r.callgraph.Nodes))
for _, n := range r.callgraph.Nodes {
numbering := make(map[callgraph.Node]int) j := &cg[n.ID]
for i, n := range nodes { fn := n.Func
numbering[n] = i
}
cg := make([]serial.CallGraph, len(nodes))
for i, n := range nodes {
j := &cg[i]
fn := n.Func()
j.Name = fn.String() j.Name = fn.String()
j.Pos = fset.Position(fn.Pos()).String() j.Pos = fset.Position(fn.Pos()).String()
for callee := range callgraph.CalleesOf(n) { for callee := range callgraph.CalleesOf(n) {
j.Children = append(j.Children, numbering[callee]) j.Children = append(j.Children, callee.ID)
} }
sort.Ints(j.Children) sort.Ints(j.Children)
} }

View File

@ -45,8 +45,8 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
cg := ptrAnalysis(o).CallGraph cg := ptrAnalysis(o).CallGraph
// Search for an arbitrary path from a root to the target function. // Search for an arbitrary path from a root to the target function.
isEnd := func(n callgraph.Node) bool { return n.Func() == target } isEnd := func(n *callgraph.Node) bool { return n.Func == target }
callpath := callgraph.PathSearch(cg.Root(), isEnd) callpath := callgraph.PathSearch(cg.Root, isEnd)
if callpath != nil { if callpath != nil {
callpath = callpath[1:] // remove synthetic edge from <root> callpath = callpath[1:] // remove synthetic edge from <root>
} }
@ -61,7 +61,7 @@ func callstack(o *Oracle, qpos *QueryPos) (queryResult, error) {
type callstackResult struct { type callstackResult struct {
qpos *QueryPos qpos *QueryPos
target *ssa.Function target *ssa.Function
callpath []callgraph.Edge callpath []*callgraph.Edge
} }
func (r *callstackResult) display(printf printfFunc) { func (r *callstackResult) display(printf printfFunc) {
@ -70,7 +70,7 @@ func (r *callstackResult) display(printf printfFunc) {
printf(r.target, "%s", r.target) printf(r.target, "%s", r.target)
for i := len(r.callpath) - 1; i >= 0; i-- { for i := len(r.callpath) - 1; i >= 0; i-- {
edge := r.callpath[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 { } else {
printf(r.target, "%s is unreachable in this analysis scope", r.target) 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] edge := r.callpath[i]
callers = append(callers, serial.Caller{ callers = append(callers, serial.Caller{
Pos: fset.Position(edge.Site.Pos()).String(), Pos: fset.Position(edge.Site.Pos()).String(),
Caller: edge.Caller.Func().String(), Caller: edge.Caller.Func.String(),
Desc: edge.Site.Common().Description(), Desc: edge.Site.Common().Description(),
}) })
} }

View File

@ -259,7 +259,7 @@ func TestMultipleQueries(t *testing.T) {
// Loader // Loader
var buildContext = build.Default var buildContext = build.Default
buildContext.GOPATH = "testdata" buildContext.GOPATH = "testdata"
conf := loader.Config{Build: &buildContext} conf := loader.Config{Build: &buildContext, SourceImports: true}
filename := "testdata/src/main/multi.go" filename := "testdata/src/main/multi.go"
conf.CreateFromFilenames("", filename) conf.CreateFromFilenames("", filename)
iprog, err := conf.Load() iprog, err := conf.Load()

View File

@ -7,31 +7,41 @@
"pos": "-", "pos": "-",
"children": [ "children": [
1, 1,
2 5
] ]
}, },
{
"name": "main.init",
"pos": "-"
},
{ {
"name": "main.main", "name": "main.main",
"pos": "testdata/src/main/callgraph-json.go:24:6", "pos": "testdata/src/main/callgraph-json.go:24:6",
"children": [ "children": [
2,
3, 3,
6, 4
7,
8
] ]
}, },
{ {
"name": "main.call", "name": "main.call",
"pos": "testdata/src/main/callgraph-json.go:12:6", "pos": "testdata/src/main/callgraph-json.go:12:6",
"children": [ "children": [
4, 6,
5 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", "name": "main.A",
"pos": "testdata/src/main/callgraph-json.go:7:6" "pos": "testdata/src/main/callgraph-json.go:7:6"
@ -40,21 +50,6 @@
"name": "main.B", "name": "main.B",
"pos": "testdata/src/main/callgraph-json.go:9:6" "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", "name": "func@31.8",
"pos": "testdata/src/main/callgraph-json.go:31:8" "pos": "testdata/src/main/callgraph-json.go:31:8"