golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/go/callgraph/callgraph_test.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 package callgraph_test 5 6 import ( 7 "log" 8 "sync" 9 "testing" 10 11 "golang.org/x/tools/go/callgraph" 12 "golang.org/x/tools/go/callgraph/cha" 13 "golang.org/x/tools/go/callgraph/rta" 14 "golang.org/x/tools/go/callgraph/static" 15 "golang.org/x/tools/go/callgraph/vta" 16 "golang.org/x/tools/go/loader" 17 "golang.org/x/tools/go/ssa" 18 "golang.org/x/tools/go/ssa/ssautil" 19 ) 20 21 // Benchmarks comparing different callgraph algorithms implemented in 22 // x/tools/go/callgraph. Comparison is on both speed, memory and precision. 23 // Fewer edges and fewer reachable nodes implies a more precise result. 24 // Comparison is done on a hello world http server using net/http. 25 // 26 // Current results were on an i7 macbook on go version devel go1.20-2730. 27 // Number of nodes, edges, and reachable function are expected to vary between 28 // go versions. Timing results are expected to vary between machines. 29 // BenchmarkStatic-12 53 ms/op 6 MB/op 12113 nodes 37355 edges 1522 reachable 30 // BenchmarkCHA-12 86 ms/op 16 MB/op 12113 nodes 131717 edges 7640 reachable 31 // BenchmarkRTA-12 110 ms/op 12 MB/op 6566 nodes 42291 edges 5099 reachable 32 // BenchmarkPTA-12 1427 ms/op 600 MB/op 8714 nodes 28244 edges 4184 reachable 33 // BenchmarkVTA-12 600 ms/op 78 MB/op 12114 nodes 44861 edges 4919 reachable 34 // BenchmarkVTA2-12 793 ms/op 104 MB/op 5450 nodes 22208 edges 4042 reachable 35 // BenchmarkVTA3-12 977 ms/op 124 MB/op 4621 nodes 19331 edges 3700 reachable 36 // BenchmarkVTAAlt-12 372 ms/op 57 MB/op 7763 nodes 29912 edges 4258 reachable 37 // BenchmarkVTAAlt2-12 570 ms/op 78 MB/op 4838 nodes 20169 edges 3737 reachable 38 // 39 // Note: 40 // * Static is unsound and may miss real edges. 41 // * RTA starts from a main function and only includes reachable functions. 42 // * CHA starts from all functions. 43 // * VTA, VTA2, and VTA3 are starting from all functions and the CHA callgraph. 44 // VTA2 and VTA3 are the result of re-applying VTA to the functions reachable 45 // from main() via the callgraph of the previous stage. 46 // * VTAAlt, and VTAAlt2 start from the functions reachable from main via the 47 // CHA callgraph. 48 // * All algorithms are unsound w.r.t. reflection. 49 50 const httpEx = `package main 51 52 import ( 53 "fmt" 54 "net/http" 55 ) 56 57 func hello(w http.ResponseWriter, req *http.Request) { 58 fmt.Fprintf(w, "hello world\n") 59 } 60 61 func main() { 62 http.HandleFunc("/hello", hello) 63 http.ListenAndServe(":8090", nil) 64 } 65 ` 66 67 var ( 68 once sync.Once 69 prog *ssa.Program 70 main *ssa.Function 71 ) 72 73 func example() (*ssa.Program, *ssa.Function) { 74 once.Do(func() { 75 var conf loader.Config 76 f, err := conf.ParseFile("<input>", httpEx) 77 if err != nil { 78 log.Fatal(err) 79 } 80 conf.CreateFromFiles(f.Name.Name, f) 81 82 lprog, err := conf.Load() 83 if err != nil { 84 log.Fatalf("test 'package %s': Load: %s", f.Name.Name, err) 85 } 86 prog = ssautil.CreateProgram(lprog, ssa.InstantiateGenerics) 87 prog.Build() 88 89 main = prog.Package(lprog.Created[0].Pkg).Members["main"].(*ssa.Function) 90 }) 91 return prog, main 92 } 93 94 var stats bool = false // print stats? 95 96 func logStats(b *testing.B, cnd bool, name string, cg *callgraph.Graph, main *ssa.Function) { 97 if cnd && stats { 98 e := 0 99 for _, n := range cg.Nodes { 100 e += len(n.Out) 101 } 102 r := len(reaches(main, cg, false)) 103 b.Logf("%s:\t%d nodes\t%d edges\t%d reachable", name, len(cg.Nodes), e, r) 104 } 105 } 106 107 func BenchmarkStatic(b *testing.B) { 108 b.StopTimer() 109 prog, main := example() 110 b.StartTimer() 111 112 for i := 0; i < b.N; i++ { 113 cg := static.CallGraph(prog) 114 logStats(b, i == 0, "static", cg, main) 115 } 116 } 117 118 func BenchmarkCHA(b *testing.B) { 119 b.StopTimer() 120 prog, main := example() 121 b.StartTimer() 122 123 for i := 0; i < b.N; i++ { 124 cg := cha.CallGraph(prog) 125 logStats(b, i == 0, "cha", cg, main) 126 } 127 } 128 129 func BenchmarkRTA(b *testing.B) { 130 b.StopTimer() 131 _, main := example() 132 b.StartTimer() 133 134 for i := 0; i < b.N; i++ { 135 res := rta.Analyze([]*ssa.Function{main}, true) 136 cg := res.CallGraph 137 logStats(b, i == 0, "rta", cg, main) 138 } 139 } 140 141 func BenchmarkVTA(b *testing.B) { 142 b.StopTimer() 143 prog, main := example() 144 b.StartTimer() 145 146 for i := 0; i < b.N; i++ { 147 cg := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog)) 148 logStats(b, i == 0, "vta", cg, main) 149 } 150 } 151 152 func BenchmarkVTA2(b *testing.B) { 153 b.StopTimer() 154 prog, main := example() 155 b.StartTimer() 156 157 for i := 0; i < b.N; i++ { 158 vta1 := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog)) 159 cg := vta.CallGraph(reaches(main, vta1, true), vta1) 160 logStats(b, i == 0, "vta2", cg, main) 161 } 162 } 163 164 func BenchmarkVTA3(b *testing.B) { 165 b.StopTimer() 166 prog, main := example() 167 b.StartTimer() 168 169 for i := 0; i < b.N; i++ { 170 vta1 := vta.CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog)) 171 vta2 := vta.CallGraph(reaches(main, vta1, true), vta1) 172 cg := vta.CallGraph(reaches(main, vta2, true), vta2) 173 logStats(b, i == 0, "vta3", cg, main) 174 } 175 } 176 177 func BenchmarkVTAAlt(b *testing.B) { 178 b.StopTimer() 179 prog, main := example() 180 b.StartTimer() 181 182 for i := 0; i < b.N; i++ { 183 cha := cha.CallGraph(prog) 184 cg := vta.CallGraph(reaches(main, cha, true), cha) // start from only functions reachable by CHA. 185 logStats(b, i == 0, "vta-alt", cg, main) 186 } 187 } 188 189 func BenchmarkVTAAlt2(b *testing.B) { 190 b.StopTimer() 191 prog, main := example() 192 b.StartTimer() 193 194 for i := 0; i < b.N; i++ { 195 cha := cha.CallGraph(prog) 196 vta1 := vta.CallGraph(reaches(main, cha, true), cha) 197 cg := vta.CallGraph(reaches(main, vta1, true), vta1) 198 logStats(b, i == 0, "vta-alt2", cg, main) 199 } 200 } 201 202 // reaches computes the transitive closure of functions forward reachable 203 // via calls in cg starting from `sources`. If refs is true, include 204 // functions referred to in an instruction. 205 func reaches(source *ssa.Function, cg *callgraph.Graph, refs bool) map[*ssa.Function]bool { 206 seen := make(map[*ssa.Function]bool) 207 var visit func(f *ssa.Function) 208 visit = func(f *ssa.Function) { 209 if seen[f] { 210 return 211 } 212 seen[f] = true 213 214 if n := cg.Nodes[f]; n != nil { 215 for _, e := range n.Out { 216 if e.Site != nil { 217 visit(e.Callee.Func) 218 } 219 } 220 } 221 222 if refs { 223 var buf [10]*ssa.Value // avoid alloc in common case 224 for _, b := range f.Blocks { 225 for _, instr := range b.Instrs { 226 for _, op := range instr.Operands(buf[:0]) { 227 if fn, ok := (*op).(*ssa.Function); ok { 228 visit(fn) 229 } 230 } 231 } 232 } 233 } 234 } 235 visit(source) 236 return seen 237 }