src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }