diff --git a/oracle/callgraph.go b/oracle/callgraph.go index 590fa640c84..745a5cf5b91 100644 --- a/oracle/callgraph.go +++ b/oracle/callgraph.go @@ -6,10 +6,11 @@ package oracle import ( "go/token" - "strings" + "sort" "code.google.com/p/go.tools/call" "code.google.com/p/go.tools/oracle/serial" + "code.google.com/p/go.tools/ssa" ) // callgraph displays the entire callgraph of the current program. @@ -49,29 +50,66 @@ Below is a call graph of the entire program. The numbered nodes form a spanning tree. Non-numbered nodes indicate back- or cross-edges to the node whose number follows in parentheses. -Some nodes may appear multiple times due to context-sensitive - treatment of some calls. `) + root := r.callgraph.Root() - seen := make(map[call.GraphNode]int) - var print func(cgn call.GraphNode, indent int) - print = func(cgn call.GraphNode, indent int) { - fn := cgn.Func() - if num, ok := seen[cgn]; !ok { - num = len(seen) - seen[cgn] = num - printf(fn, "%d\t%s%s", num, strings.Repeat(" ", indent), fn) - // Don't use Edges(), which distinguishes callees by call site. - for callee := range call.CalleesOf(cgn) { + // 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[call.GraphNode]bool) + var visit func(caller call.GraphNode) + visit = func(caller call.GraphNode) { + 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 { + num = len(printed) + printed[caller] = num + + // 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) + } + sort.Sort(funcs) + + printf(caller, "%d\t%*s%s", num, 4*indent, "", caller) + for _, callee := range funcs { print(callee, indent+1) } } else { - printf(fn, "\t%s%s (%d)", strings.Repeat(" ", indent), fn, num) + printf(caller, "\t%*s%s (%d)", 4*indent, "", caller, num) } } - print(r.callgraph.Root(), 0) + print(root.Func(), 0) } +type funcsByName []*ssa.Function + +func (s funcsByName) Len() int { return len(s) } +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() diff --git a/oracle/oracle_test.go b/oracle/oracle_test.go index cf9119c73b0..f78a5d089f5 100644 --- a/oracle/oracle_test.go +++ b/oracle/oracle_test.go @@ -200,7 +200,7 @@ func TestOracle(t *testing.T) { for _, filename := range []string{ "testdata/src/main/calls.go", "testdata/src/main/callgraph.go", - // "testdata/src/main/callgraph2.go", // TODO(adonovan): make printing deterministic + "testdata/src/main/callgraph2.go", "testdata/src/main/describe.go", "testdata/src/main/freevars.go", "testdata/src/main/implements.go", diff --git a/oracle/testdata/src/main/callgraph.golden b/oracle/testdata/src/main/callgraph.golden index 50f4b32fd88..70894dc089f 100644 --- a/oracle/testdata/src/main/callgraph.golden +++ b/oracle/testdata/src/main/callgraph.golden @@ -4,8 +4,6 @@ Below is a call graph of the entire program. The numbered nodes form a spanning tree. Non-numbered nodes indicate back- or cross-edges to the node whose number follows in parentheses. -Some nodes may appear multiple times due to context-sensitive - treatment of some calls. 0 1 main.init @@ -13,8 +11,7 @@ Some nodes may appear multiple times due to context-sensitive 3 main.call 4 main.A 5 main.B -6 main.nop -7 main.nop -8 main.call2 -9 func@31.8 +6 main.call2 +7 func@31.8 +8 main.nop diff --git a/oracle/testdata/src/main/callgraph2.golden b/oracle/testdata/src/main/callgraph2.golden index c069956d231..7712b4a91c3 100644 --- a/oracle/testdata/src/main/callgraph2.golden +++ b/oracle/testdata/src/main/callgraph2.golden @@ -4,8 +4,6 @@ Below is a call graph of the entire program. The numbered nodes form a spanning tree. Non-numbered nodes indicate back- or cross-edges to the node whose number follows in parentheses. -Some nodes may appear multiple times due to context-sensitive - treatment of some calls. 0 1 main.init