Skip to content

Commit c538e2c

Browse files
committed
go/callgraph/static: avoid ssautil.AllFunctions
AllFunctions is complex mess. This change rewrites the static algorithm to avoid it. It does reduce the number of call graph nodes that are discovered (by something like 15%), and the running time by a similar amount, but the principle is slightly more defensible. Also, document exactly what it does, and why it is the way it is. Updates golang/go#69231 Change-Id: I7e25237f0908315602ba0092083f247a140b9e22 Reviewed-on: https://go-review.googlesource.com/c/tools/+/609280 Reviewed-by: Zvonimir Pavlinovic <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 2db563b commit c538e2c

File tree

3 files changed

+88
-27
lines changed

3 files changed

+88
-27
lines changed

go/callgraph/callgraph.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ language.
3232
*/
3333
package callgraph // import "golang.org/x/tools/go/callgraph"
3434

35-
// TODO(adonovan): add a function to eliminate wrappers from the
36-
// callgraph, preserving topology.
37-
// More generally, we could eliminate "uninteresting" nodes such as
38-
// nodes from packages we don't care about.
39-
4035
// TODO(zpavlinovic): decide how callgraphs handle calls to and from generic function bodies.
4136

4237
import (
@@ -52,11 +47,11 @@ import (
5247
// If the call graph is sound, such nodes indicate unreachable
5348
// functions.
5449
type Graph struct {
55-
Root *Node // the distinguished root node
50+
Root *Node // the distinguished root node (Root.Func may be nil)
5651
Nodes map[*ssa.Function]*Node // all nodes by function
5752
}
5853

59-
// New returns a new Graph with the specified root node.
54+
// New returns a new Graph with the specified (optional) root node.
6055
func New(root *ssa.Function) *Graph {
6156
g := &Graph{Nodes: make(map[*ssa.Function]*Node)}
6257
g.Root = g.CreateNode(root)

go/callgraph/static/static.go

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,95 @@
44

55
// Package static computes the call graph of a Go program containing
66
// only static call edges.
7-
package static // import "golang.org/x/tools/go/callgraph/static"
8-
9-
// TODO(zpavlinovic): update static for how it handles generic function bodies.
7+
package static
108

119
import (
10+
"go/types"
11+
1212
"golang.org/x/tools/go/callgraph"
1313
"golang.org/x/tools/go/ssa"
14-
"golang.org/x/tools/go/ssa/ssautil"
1514
)
1615

17-
// CallGraph computes the call graph of the specified program
18-
// considering only static calls.
16+
// CallGraph computes the static call graph of the specified program.
17+
//
18+
// The resulting graph includes:
19+
// - all package-level functions;
20+
// - all methods of package-level non-parameterized non-interface types;
21+
// - pointer wrappers (*C).F for source-level methods C.F;
22+
// - and all functions reachable from them following only static calls.
23+
//
24+
// It does not consider exportedness, nor treat main packages specially.
1925
func CallGraph(prog *ssa.Program) *callgraph.Graph {
20-
cg := callgraph.New(nil) // TODO(adonovan) eliminate concept of rooted callgraph
21-
22-
// TODO(adonovan): opt: use only a single pass over the ssa.Program.
23-
// TODO(adonovan): opt: this is slower than RTA (perhaps because
24-
// the lower precision means so many edges are allocated)!
25-
for f := range ssautil.AllFunctions(prog) {
26-
fnode := cg.CreateNode(f)
27-
for _, b := range f.Blocks {
28-
for _, instr := range b.Instrs {
29-
if site, ok := instr.(ssa.CallInstruction); ok {
30-
if g := site.Common().StaticCallee(); g != nil {
31-
gnode := cg.CreateNode(g)
32-
callgraph.AddEdge(fnode, site, gnode)
26+
cg := callgraph.New(nil)
27+
28+
// Recursively follow all static calls.
29+
seen := make(map[int]bool) // node IDs already seen
30+
var visit func(fnode *callgraph.Node)
31+
visit = func(fnode *callgraph.Node) {
32+
if !seen[fnode.ID] {
33+
seen[fnode.ID] = true
34+
35+
for _, b := range fnode.Func.Blocks {
36+
for _, instr := range b.Instrs {
37+
if site, ok := instr.(ssa.CallInstruction); ok {
38+
if g := site.Common().StaticCallee(); g != nil {
39+
gnode := cg.CreateNode(g)
40+
callgraph.AddEdge(fnode, site, gnode)
41+
visit(gnode)
42+
}
43+
}
44+
}
45+
}
46+
}
47+
}
48+
49+
// If we were ever to redesign this function, we should allow
50+
// the caller to provide the set of root functions and just
51+
// perform the reachability step. This would allow them to
52+
// work forwards from main entry points:
53+
//
54+
// rootNames := []string{"init", "main"}
55+
// for _, main := range ssautil.MainPackages(prog.AllPackages()) {
56+
// for _, rootName := range rootNames {
57+
// visit(cg.CreateNode(main.Func(rootName)))
58+
// }
59+
// }
60+
//
61+
// or to control whether to include non-exported
62+
// functions/methods, wrapper methods, and so on.
63+
// Unfortunately that's not consistent with its historical
64+
// behavior and existing tests.
65+
//
66+
// The logic below is a slight simplification and
67+
// rationalization of ssautil.AllFunctions. (Having to include
68+
// (*T).F wrapper methods is unfortunate--they are not source
69+
// functions, and if they're reachable, they'll be in the
70+
// graph--but the existing tests will break without it.)
71+
72+
methodsOf := func(T types.Type) {
73+
if !types.IsInterface(T) {
74+
mset := prog.MethodSets.MethodSet(T)
75+
for i := 0; i < mset.Len(); i++ {
76+
visit(cg.CreateNode(prog.MethodValue(mset.At(i))))
77+
}
78+
}
79+
}
80+
81+
// Start from package-level symbols.
82+
for _, pkg := range prog.AllPackages() {
83+
for _, mem := range pkg.Members {
84+
switch mem := mem.(type) {
85+
case *ssa.Function:
86+
// package-level function
87+
visit(cg.CreateNode(mem))
88+
89+
case *ssa.Type:
90+
// methods of package-level non-interface non-parameterized types
91+
if !types.IsInterface(mem.Type()) {
92+
if named, ok := mem.Type().(*types.Named); ok &&
93+
named.TypeParams() == nil {
94+
methodsOf(named) // T
95+
methodsOf(types.NewPointer(named)) // *T
3396
}
3497
}
3598
}

go/callgraph/static/static_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"golang.org/x/tools/go/ssa/ssautil"
1919
)
2020

21-
const input = `package P
21+
const input = `package main
2222
2323
type C int
2424
func (C) f()
@@ -46,6 +46,9 @@ func g() {
4646
func h()
4747
4848
var unknown bool
49+
50+
func main() {
51+
}
4952
`
5053

5154
const genericsInput = `package P

0 commit comments

Comments
 (0)