vta.go (7173B)
1 // Copyright 2021 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 5 // Package vta computes the call graph of a Go program using the Variable 6 // Type Analysis (VTA) algorithm originally described in "Practical Virtual 7 // Method Call Resolution for Java," Vijay Sundaresan, Laurie Hendren, 8 // Chrislain Razafimahefa, Raja Vallée-Rai, Patrick Lam, Etienne Gagnon, and 9 // Charles Godin. 10 // 11 // Note: this package is in experimental phase and its interface is 12 // subject to change. 13 // TODO(zpavlinovic): reiterate on documentation. 14 // 15 // The VTA algorithm overapproximates the set of types (and function literals) 16 // a variable can take during runtime by building a global type propagation 17 // graph and propagating types (and function literals) through the graph. 18 // 19 // A type propagation is a directed, labeled graph. A node can represent 20 // one of the following: 21 // - A field of a struct type. 22 // - A local (SSA) variable of a method/function. 23 // - All pointers to a non-interface type. 24 // - The return value of a method. 25 // - All elements in an array. 26 // - All elements in a slice. 27 // - All elements in a map. 28 // - All elements in a channel. 29 // - A global variable. 30 // 31 // In addition, the implementation used in this package introduces 32 // a few Go specific kinds of nodes: 33 // - (De)references of nested pointers to interfaces are modeled 34 // as a unique nestedPtrInterface node in the type propagation graph. 35 // - Each function literal is represented as a function node whose 36 // internal value is the (SSA) representation of the function. This 37 // is done to precisely infer flow of higher-order functions. 38 // 39 // Edges in the graph represent flow of types (and function literals) through 40 // the program. That is, the model 1) typing constraints that are induced by 41 // assignment statements or function and method calls and 2) higher-order flow 42 // of functions in the program. 43 // 44 // The labeling function maps each node to a set of types and functions that 45 // can intuitively reach the program construct the node represents. Initially, 46 // every node is assigned a type corresponding to the program construct it 47 // represents. Function nodes are also assigned the function they represent. 48 // The labeling function then propagates types and function through the graph. 49 // 50 // The result of VTA is a type propagation graph in which each node is labeled 51 // with a conservative overapproximation of the set of types (and functions) 52 // it may have. This information is then used to construct the call graph. 53 // For each unresolved call site, vta uses the set of types and functions 54 // reaching the node representing the call site to create a set of callees. 55 package vta 56 57 // TODO(zpavlinovic): update VTA for how it handles generic function bodies and instantiation wrappers. 58 59 import ( 60 "go/types" 61 62 "golang.org/x/tools/go/callgraph" 63 "golang.org/x/tools/go/ssa" 64 ) 65 66 // CallGraph uses the VTA algorithm to compute call graph for all functions 67 // f:true in funcs. VTA refines the results of initial call graph and uses it 68 // to establish interprocedural type flow. If initial is nil, VTA uses a more 69 // efficient approach to construct a CHA call graph. 70 // 71 // The resulting graph does not have a root node. 72 // 73 // CallGraph does not make any assumptions on initial types global variables 74 // and function/method inputs can have. CallGraph is then sound, modulo use of 75 // reflection and unsafe, if the initial call graph is sound. 76 // 77 // The supplied SSA functions must have been constructed with the 78 // [ssa.InstantiateGenerics] mode flag. 79 func CallGraph(funcs map[*ssa.Function]bool, initial *callgraph.Graph) *callgraph.Graph { 80 callees := makeCalleesFunc(funcs, initial) 81 vtaG, canon := typePropGraph(funcs, callees) 82 types := propagate(vtaG, canon) 83 84 c := &constructor{types: types, callees: callees, cache: make(methodCache)} 85 return c.construct(funcs) 86 } 87 88 // constructor type linearly traverses the input program 89 // and constructs a callgraph based on the results of the 90 // VTA type propagation phase. 91 type constructor struct { 92 types propTypeMap 93 cache methodCache 94 callees calleesFunc 95 } 96 97 func (c *constructor) construct(funcs map[*ssa.Function]bool) *callgraph.Graph { 98 cg := &callgraph.Graph{Nodes: make(map[*ssa.Function]*callgraph.Node)} 99 for f, in := range funcs { 100 if in { 101 c.constrct(cg, f) 102 } 103 } 104 return cg 105 } 106 107 func (c *constructor) constrct(g *callgraph.Graph, f *ssa.Function) { 108 caller := g.CreateNode(f) 109 for _, call := range calls(f) { 110 for _, c := range c.resolves(call) { 111 callgraph.AddEdge(caller, call, g.CreateNode(c)) 112 } 113 } 114 } 115 116 // resolves computes the set of functions to which VTA resolves `c`. The resolved 117 // functions are intersected with functions to which `c.initial` resolves `c`. 118 func (c *constructor) resolves(call ssa.CallInstruction) []*ssa.Function { 119 cc := call.Common() 120 if cc.StaticCallee() != nil { 121 return []*ssa.Function{cc.StaticCallee()} 122 } 123 124 // Skip builtins as they are not *ssa.Function. 125 if _, ok := cc.Value.(*ssa.Builtin); ok { 126 return nil 127 } 128 129 // Cover the case of dynamic higher-order and interface calls. 130 var res []*ssa.Function 131 resolved := resolve(call, c.types, c.cache) 132 for f := range siteCallees(call, c.callees) { 133 if _, ok := resolved[f]; ok { 134 res = append(res, f) 135 } 136 } 137 return res 138 } 139 140 // resolve returns a set of functions `c` resolves to based on the 141 // type propagation results in `types`. 142 func resolve(c ssa.CallInstruction, types propTypeMap, cache methodCache) map[*ssa.Function]empty { 143 fns := make(map[*ssa.Function]empty) 144 n := local{val: c.Common().Value} 145 for p := range types.propTypes(n) { 146 for _, f := range propFunc(p, c, cache) { 147 fns[f] = empty{} 148 } 149 } 150 return fns 151 } 152 153 // propFunc returns the functions modeled with the propagation type `p` 154 // assigned to call site `c`. If no such function exists, nil is returned. 155 func propFunc(p propType, c ssa.CallInstruction, cache methodCache) []*ssa.Function { 156 if p.f != nil { 157 return []*ssa.Function{p.f} 158 } 159 160 if c.Common().Method == nil { 161 return nil 162 } 163 164 return cache.methods(p.typ, c.Common().Method.Name(), c.Parent().Prog) 165 } 166 167 // methodCache serves as a type -> method name -> methods 168 // cache when computing methods of a type using the 169 // ssa.Program.MethodSets and ssa.Program.MethodValue 170 // APIs. The cache is used to speed up querying of 171 // methods of a type as the mentioned APIs are expensive. 172 type methodCache map[types.Type]map[string][]*ssa.Function 173 174 // methods returns methods of a type `t` named `name`. First consults 175 // `mc` and otherwise queries `prog` for the method. If no such method 176 // exists, nil is returned. 177 func (mc methodCache) methods(t types.Type, name string, prog *ssa.Program) []*ssa.Function { 178 if ms, ok := mc[t]; ok { 179 return ms[name] 180 } 181 182 ms := make(map[string][]*ssa.Function) 183 mset := prog.MethodSets.MethodSet(t) 184 for i, n := 0, mset.Len(); i < n; i++ { 185 // f can be nil when t is an interface or some 186 // other type without any runtime methods. 187 if f := prog.MethodValue(mset.At(i)); f != nil { 188 ms[f.Name()] = append(ms[f.Name()], f) 189 } 190 } 191 mc[t] = ms 192 return ms[name] 193 }