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