diff --git a/go/callgraph/callgraph.go b/go/callgraph/callgraph.go index f97cc9cd..7a1a2aba 100644 --- a/go/callgraph/callgraph.go +++ b/go/callgraph/callgraph.go @@ -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) } diff --git a/go/callgraph/util.go b/go/callgraph/util.go index b6875ee5..d24cc710 100644 --- a/go/callgraph/util.go +++ b/go/callgraph/util.go @@ -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 diff --git a/go/pointer/analysis.go b/go/pointer/analysis.go index 44875e9f..3b4469be 100644 --- a/go/pointer/analysis.go +++ b/go/pointer/analysis.go @@ -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 { diff --git a/go/pointer/api.go b/go/pointer/api.go index 89325812..6da20146 100644 --- a/go/pointer/api.go +++ b/go/pointer/api.go @@ -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 diff --git a/go/pointer/callgraph.go b/go/pointer/callgraph.go index be4d6015..6ef415d2 100644 --- a/go/pointer/callgraph.go +++ b/go/pointer/callgraph.go @@ -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 { diff --git a/go/pointer/example_test.go b/go/pointer/example_test.go index 7f518afa..dcf167c5 100644 --- a/go/pointer/example_test.go +++ b/go/pointer/example_test.go @@ -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 }) diff --git a/go/pointer/gen.go b/go/pointer/gen.go index 1a2bfcf8..77cf1596 100644 --- a/go/pointer/gen.go +++ b/go/pointer/gen.go @@ -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, "") a.addressOf(T, a.objectNode(nil, os.Var("Args")), obj) } - - return root } diff --git a/go/pointer/intrinsics.go b/go/pointer/intrinsics.go index c4a5a850..69ce8780 100644 --- a/go/pointer/intrinsics.go +++ b/go/pointer/intrinsics.go @@ -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) } } diff --git a/go/pointer/labels.go b/go/pointer/labels.go index be7c20af..25f64921 100644 --- a/go/pointer/labels.go +++ b/go/pointer/labels.go @@ -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("", l.obj.cgn.Func()) + s = fmt.Sprintf("", l.obj.cgn.fn) } else { s = "" // should be unreachable } diff --git a/go/pointer/pointer_test.go b/go/pointer/pointer_test.go index f8bee71a..73fc4ac1 100644 --- a/go/pointer/pointer_test.go +++ b/go/pointer/pointer_test.go @@ -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 ("") 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 } diff --git a/oracle/callees.go b/oracle/callees.go index fc08ce2d..e9d08854 100644 --- a/oracle/callees.go +++ b/oracle/callees.go @@ -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) diff --git a/oracle/callers.go b/oracle/callers.go index d9415da8..1ece1a4c 100644 --- a/oracle/callers.go +++ b/oracle/callers.go @@ -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 { diff --git a/oracle/callgraph.go b/oracle/callgraph.go index df597024..a806e1e0 100644 --- a/oracle/callgraph.go +++ b/oracle/callgraph.go @@ -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) } diff --git a/oracle/callstack.go b/oracle/callstack.go index 22552386..7071f94a 100644 --- a/oracle/callstack.go +++ b/oracle/callstack.go @@ -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 } @@ -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(), }) } diff --git a/oracle/oracle_test.go b/oracle/oracle_test.go index 19b4edb3..8fcda5e3 100644 --- a/oracle/oracle_test.go +++ b/oracle/oracle_test.go @@ -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() diff --git a/oracle/testdata/src/main/callgraph-json.golden b/oracle/testdata/src/main/callgraph-json.golden index e2aaaf5a..cde494af 100644 --- a/oracle/testdata/src/main/callgraph-json.golden +++ b/oracle/testdata/src/main/callgraph-json.golden @@ -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"