src

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

rta.go (16595B)


      1 // Copyright 2013 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 // This package provides Rapid Type Analysis (RTA) for Go, a fast
      6 // algorithm for call graph construction and discovery of reachable code
      7 // (and hence dead code) and runtime types.  The algorithm was first
      8 // described in:
      9 //
     10 // David F. Bacon and Peter F. Sweeney. 1996.
     11 // Fast static analysis of C++ virtual function calls. (OOPSLA '96)
     12 // http://doi.acm.org/10.1145/236337.236371
     13 //
     14 // The algorithm uses dynamic programming to tabulate the cross-product
     15 // of the set of known "address-taken" functions with the set of known
     16 // dynamic calls of the same type.  As each new address-taken function
     17 // is discovered, call graph edges are added from each known callsite,
     18 // and as each new call site is discovered, call graph edges are added
     19 // from it to each known address-taken function.
     20 //
     21 // A similar approach is used for dynamic calls via interfaces: it
     22 // tabulates the cross-product of the set of known "runtime types",
     23 // i.e. types that may appear in an interface value, or may be derived from
     24 // one via reflection, with the set of known "invoke"-mode dynamic
     25 // calls.  As each new runtime type is discovered, call edges are
     26 // added from the known call sites, and as each new call site is
     27 // discovered, call graph edges are added to each compatible
     28 // method.
     29 //
     30 // In addition, we must consider as reachable all address-taken
     31 // functions and all exported methods of any runtime type, since they
     32 // may be called via reflection.
     33 //
     34 // Each time a newly added call edge causes a new function to become
     35 // reachable, the code of that function is analyzed for more call sites,
     36 // address-taken functions, and runtime types.  The process continues
     37 // until a fixed point is reached.
     38 package rta // import "golang.org/x/tools/go/callgraph/rta"
     39 
     40 import (
     41 	"fmt"
     42 	"go/types"
     43 	"hash/crc32"
     44 
     45 	"golang.org/x/tools/go/callgraph"
     46 	"golang.org/x/tools/go/ssa"
     47 	"golang.org/x/tools/go/types/typeutil"
     48 	"golang.org/x/tools/internal/typesinternal"
     49 )
     50 
     51 // A Result holds the results of Rapid Type Analysis, which includes the
     52 // set of reachable functions/methods, runtime types, and the call graph.
     53 type Result struct {
     54 	// CallGraph is the discovered callgraph.
     55 	// It does not include edges for calls made via reflection.
     56 	CallGraph *callgraph.Graph
     57 
     58 	// Reachable contains the set of reachable functions and methods.
     59 	// This includes exported methods of runtime types, since
     60 	// they may be accessed via reflection.
     61 	// The value indicates whether the function is address-taken.
     62 	//
     63 	// (We wrap the bool in a struct to avoid inadvertent use of
     64 	// "if Reachable[f] {" to test for set membership.)
     65 	Reachable map[*ssa.Function]struct{ AddrTaken bool }
     66 
     67 	// RuntimeTypes contains the set of types that are needed at
     68 	// runtime, for interfaces or reflection.
     69 	//
     70 	// The value indicates whether the type is inaccessible to reflection.
     71 	// Consider:
     72 	// 	type A struct{B}
     73 	// 	fmt.Println(new(A))
     74 	// Types *A, A and B are accessible to reflection, but the unnamed
     75 	// type struct{B} is not.
     76 	//
     77 	// TODO(adonovan): populating this field is expensive yet it
     78 	// is never used in x/tools. Add a revised [Analyze] API that
     79 	// provides the option not to set it.
     80 	RuntimeTypes typeutil.Map
     81 }
     82 
     83 // Working state of the RTA algorithm.
     84 type rta struct {
     85 	result *Result
     86 
     87 	prog *ssa.Program
     88 
     89 	reflectValueCall *ssa.Function // (*reflect.Value).Call, iff part of prog
     90 
     91 	worklist []*ssa.Function // list of functions to visit
     92 
     93 	// addrTakenFuncsBySig contains all address-taken *Functions, grouped by signature.
     94 	// Keys are *types.Signature, values are map[*ssa.Function]bool sets.
     95 	addrTakenFuncsBySig typeutil.Map
     96 
     97 	// dynCallSites contains all dynamic "call"-mode call sites, grouped by signature.
     98 	// Keys are *types.Signature, values are unordered []ssa.CallInstruction.
     99 	dynCallSites typeutil.Map
    100 
    101 	// invokeSites contains all "invoke"-mode call sites, grouped by interface.
    102 	// Keys are *types.Interface (never *types.Named),
    103 	// Values are unordered []ssa.CallInstruction sets.
    104 	invokeSites typeutil.Map
    105 
    106 	// The following two maps together define the subset of the
    107 	// m:n "implements" relation needed by the algorithm.
    108 
    109 	// concreteTypes maps each concrete type to information about it.
    110 	// Keys are types.Type, values are *concreteTypeInfo.
    111 	// Only concrete types used as MakeInterface operands are included.
    112 	concreteTypes typeutil.Map
    113 
    114 	// interfaceTypes maps each interface type to information about it.
    115 	// Keys are *types.Interface, values are *interfaceTypeInfo.
    116 	// Only interfaces used in "invoke"-mode CallInstructions are included.
    117 	interfaceTypes typeutil.Map
    118 }
    119 
    120 type concreteTypeInfo struct {
    121 	C          types.Type
    122 	fprint     uint64             // fingerprint of method set
    123 	implements []*types.Interface // unordered set of implemented interfaces
    124 }
    125 
    126 type interfaceTypeInfo struct {
    127 	I               *types.Interface
    128 	fprint          uint64       // fingerprint of method set
    129 	implementations []types.Type // unordered set of concrete implementations
    130 }
    131 
    132 // addReachable marks a function as potentially callable at run-time,
    133 // and ensures that it gets processed.
    134 func (r *rta) addReachable(f *ssa.Function, addrTaken bool) {
    135 	reachable := r.result.Reachable
    136 	n := len(reachable)
    137 	v := reachable[f]
    138 	if addrTaken {
    139 		v.AddrTaken = true
    140 	}
    141 	reachable[f] = v
    142 	if len(reachable) > n {
    143 		// First time seeing f.  Add it to the worklist.
    144 		r.worklist = append(r.worklist, f)
    145 	}
    146 }
    147 
    148 // addEdge adds the specified call graph edge, and marks it reachable.
    149 // addrTaken indicates whether to mark the callee as "address-taken".
    150 // site is nil for calls made via reflection.
    151 func (r *rta) addEdge(caller *ssa.Function, site ssa.CallInstruction, callee *ssa.Function, addrTaken bool) {
    152 	r.addReachable(callee, addrTaken)
    153 
    154 	if g := r.result.CallGraph; g != nil {
    155 		if caller == nil {
    156 			panic(site)
    157 		}
    158 		from := g.CreateNode(caller)
    159 		to := g.CreateNode(callee)
    160 		callgraph.AddEdge(from, site, to)
    161 	}
    162 }
    163 
    164 // ---------- addrTakenFuncs × dynCallSites ----------
    165 
    166 // visitAddrTakenFunc is called each time we encounter an address-taken function f.
    167 func (r *rta) visitAddrTakenFunc(f *ssa.Function) {
    168 	// Create two-level map (Signature -> Function -> bool).
    169 	S := f.Signature
    170 	funcs, _ := r.addrTakenFuncsBySig.At(S).(map[*ssa.Function]bool)
    171 	if funcs == nil {
    172 		funcs = make(map[*ssa.Function]bool)
    173 		r.addrTakenFuncsBySig.Set(S, funcs)
    174 	}
    175 	if !funcs[f] {
    176 		// First time seeing f.
    177 		funcs[f] = true
    178 
    179 		// If we've seen any dyncalls of this type, mark it reachable,
    180 		// and add call graph edges.
    181 		sites, _ := r.dynCallSites.At(S).([]ssa.CallInstruction)
    182 		for _, site := range sites {
    183 			r.addEdge(site.Parent(), site, f, true)
    184 		}
    185 
    186 		// If the program includes (*reflect.Value).Call,
    187 		// add a dynamic call edge from it to any address-taken
    188 		// function, regardless of signature.
    189 		//
    190 		// This isn't perfect.
    191 		// - The actual call comes from an internal function
    192 		//   called reflect.call, but we can't rely on that here.
    193 		// - reflect.Value.CallSlice behaves similarly,
    194 		//   but we don't bother to create callgraph edges from
    195 		//   it as well as it wouldn't fundamentally change the
    196 		//   reachability but it would add a bunch more edges.
    197 		// - We assume that if reflect.Value.Call is among
    198 		//   the dependencies of the application, it is itself
    199 		//   reachable. (It would be more accurate to defer
    200 		//   all the addEdges below until r.V.Call itself
    201 		//   becomes reachable.)
    202 		// - Fake call graph edges are added from r.V.Call to
    203 		//   each address-taken function, but not to every
    204 		//   method reachable through a materialized rtype,
    205 		//   which is a little inconsistent. Still, the
    206 		//   reachable set includes both kinds, which is what
    207 		//   matters for e.g. deadcode detection.)
    208 		if r.reflectValueCall != nil {
    209 			var site ssa.CallInstruction = nil // can't find actual call site
    210 			r.addEdge(r.reflectValueCall, site, f, true)
    211 		}
    212 	}
    213 }
    214 
    215 // visitDynCall is called each time we encounter a dynamic "call"-mode call.
    216 func (r *rta) visitDynCall(site ssa.CallInstruction) {
    217 	S := site.Common().Signature()
    218 
    219 	// Record the call site.
    220 	sites, _ := r.dynCallSites.At(S).([]ssa.CallInstruction)
    221 	r.dynCallSites.Set(S, append(sites, site))
    222 
    223 	// For each function of signature S that we know is address-taken,
    224 	// add an edge and mark it reachable.
    225 	funcs, _ := r.addrTakenFuncsBySig.At(S).(map[*ssa.Function]bool)
    226 	for g := range funcs {
    227 		r.addEdge(site.Parent(), site, g, true)
    228 	}
    229 }
    230 
    231 // ---------- concrete types × invoke sites ----------
    232 
    233 // addInvokeEdge is called for each new pair (site, C) in the matrix.
    234 func (r *rta) addInvokeEdge(site ssa.CallInstruction, C types.Type) {
    235 	// Ascertain the concrete method of C to be called.
    236 	imethod := site.Common().Method
    237 	cmethod := r.prog.LookupMethod(C, imethod.Pkg(), imethod.Name())
    238 	r.addEdge(site.Parent(), site, cmethod, true)
    239 }
    240 
    241 // visitInvoke is called each time the algorithm encounters an "invoke"-mode call.
    242 func (r *rta) visitInvoke(site ssa.CallInstruction) {
    243 	I := site.Common().Value.Type().Underlying().(*types.Interface)
    244 
    245 	// Record the invoke site.
    246 	sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction)
    247 	r.invokeSites.Set(I, append(sites, site))
    248 
    249 	// Add callgraph edge for each existing
    250 	// address-taken concrete type implementing I.
    251 	for _, C := range r.implementations(I) {
    252 		r.addInvokeEdge(site, C)
    253 	}
    254 }
    255 
    256 // ---------- main algorithm ----------
    257 
    258 // visitFunc processes function f.
    259 func (r *rta) visitFunc(f *ssa.Function) {
    260 	var space [32]*ssa.Value // preallocate space for common case
    261 
    262 	for _, b := range f.Blocks {
    263 		for _, instr := range b.Instrs {
    264 			rands := instr.Operands(space[:0])
    265 
    266 			switch instr := instr.(type) {
    267 			case ssa.CallInstruction:
    268 				call := instr.Common()
    269 				if call.IsInvoke() {
    270 					r.visitInvoke(instr)
    271 				} else if g := call.StaticCallee(); g != nil {
    272 					r.addEdge(f, instr, g, false)
    273 				} else if _, ok := call.Value.(*ssa.Builtin); !ok {
    274 					r.visitDynCall(instr)
    275 				}
    276 
    277 				// Ignore the call-position operand when
    278 				// looking for address-taken Functions.
    279 				// Hack: assume this is rands[0].
    280 				rands = rands[1:]
    281 
    282 			case *ssa.MakeInterface:
    283 				// Converting a value of type T to an
    284 				// interface materializes its runtime
    285 				// type, allowing any of its exported
    286 				// methods to be called though reflection.
    287 				r.addRuntimeType(instr.X.Type())
    288 			}
    289 
    290 			// Process all address-taken functions.
    291 			for _, op := range rands {
    292 				if g, ok := (*op).(*ssa.Function); ok {
    293 					r.visitAddrTakenFunc(g)
    294 				}
    295 			}
    296 		}
    297 	}
    298 }
    299 
    300 // Analyze performs Rapid Type Analysis, starting at the specified root
    301 // functions.  It returns nil if no roots were specified.
    302 //
    303 // The root functions must be one or more entrypoints (main and init
    304 // functions) of a complete SSA program, with function bodies for all
    305 // dependencies, constructed with the [ssa.InstantiateGenerics] mode
    306 // flag.
    307 //
    308 // If buildCallGraph is true, Result.CallGraph will contain a call
    309 // graph; otherwise, only the other fields (reachable functions) are
    310 // populated.
    311 func Analyze(roots []*ssa.Function, buildCallGraph bool) *Result {
    312 	if len(roots) == 0 {
    313 		return nil
    314 	}
    315 
    316 	r := &rta{
    317 		result: &Result{Reachable: make(map[*ssa.Function]struct{ AddrTaken bool })},
    318 		prog:   roots[0].Prog,
    319 	}
    320 
    321 	if buildCallGraph {
    322 		// TODO(adonovan): change callgraph API to eliminate the
    323 		// notion of a distinguished root node.  Some callgraphs
    324 		// have many roots, or none.
    325 		r.result.CallGraph = callgraph.New(roots[0])
    326 	}
    327 
    328 	// Grab ssa.Function for (*reflect.Value).Call,
    329 	// if "reflect" is among the dependencies.
    330 	if reflectPkg := r.prog.ImportedPackage("reflect"); reflectPkg != nil {
    331 		reflectValue := reflectPkg.Members["Value"].(*ssa.Type)
    332 		r.reflectValueCall = r.prog.LookupMethod(reflectValue.Object().Type(), reflectPkg.Pkg, "Call")
    333 	}
    334 
    335 	hasher := typeutil.MakeHasher()
    336 	r.result.RuntimeTypes.SetHasher(hasher)
    337 	r.addrTakenFuncsBySig.SetHasher(hasher)
    338 	r.dynCallSites.SetHasher(hasher)
    339 	r.invokeSites.SetHasher(hasher)
    340 	r.concreteTypes.SetHasher(hasher)
    341 	r.interfaceTypes.SetHasher(hasher)
    342 
    343 	for _, root := range roots {
    344 		r.addReachable(root, false)
    345 	}
    346 
    347 	// Visit functions, processing their instructions, and adding
    348 	// new functions to the worklist, until a fixed point is
    349 	// reached.
    350 	var shadow []*ssa.Function // for efficiency, we double-buffer the worklist
    351 	for len(r.worklist) > 0 {
    352 		shadow, r.worklist = r.worklist, shadow[:0]
    353 		for _, f := range shadow {
    354 			r.visitFunc(f)
    355 		}
    356 	}
    357 	return r.result
    358 }
    359 
    360 // interfaces(C) returns all currently known interfaces implemented by C.
    361 func (r *rta) interfaces(C types.Type) []*types.Interface {
    362 	// Create an info for C the first time we see it.
    363 	var cinfo *concreteTypeInfo
    364 	if v := r.concreteTypes.At(C); v != nil {
    365 		cinfo = v.(*concreteTypeInfo)
    366 	} else {
    367 		cinfo = &concreteTypeInfo{
    368 			C:      C,
    369 			fprint: fingerprint(r.prog.MethodSets.MethodSet(C)),
    370 		}
    371 		r.concreteTypes.Set(C, cinfo)
    372 
    373 		// Ascertain set of interfaces C implements
    374 		// and update the 'implements' relation.
    375 		r.interfaceTypes.Iterate(func(I types.Type, v any) {
    376 			iinfo := v.(*interfaceTypeInfo)
    377 			if I := types.Unalias(I).(*types.Interface); implements(cinfo, iinfo) {
    378 				iinfo.implementations = append(iinfo.implementations, C)
    379 				cinfo.implements = append(cinfo.implements, I)
    380 			}
    381 		})
    382 	}
    383 
    384 	return cinfo.implements
    385 }
    386 
    387 // implementations(I) returns all currently known concrete types that implement I.
    388 func (r *rta) implementations(I *types.Interface) []types.Type {
    389 	// Create an info for I the first time we see it.
    390 	var iinfo *interfaceTypeInfo
    391 	if v := r.interfaceTypes.At(I); v != nil {
    392 		iinfo = v.(*interfaceTypeInfo)
    393 	} else {
    394 		iinfo = &interfaceTypeInfo{
    395 			I:      I,
    396 			fprint: fingerprint(r.prog.MethodSets.MethodSet(I)),
    397 		}
    398 		r.interfaceTypes.Set(I, iinfo)
    399 
    400 		// Ascertain set of concrete types that implement I
    401 		// and update the 'implements' relation.
    402 		r.concreteTypes.Iterate(func(C types.Type, v any) {
    403 			cinfo := v.(*concreteTypeInfo)
    404 			if implements(cinfo, iinfo) {
    405 				cinfo.implements = append(cinfo.implements, I)
    406 				iinfo.implementations = append(iinfo.implementations, C)
    407 			}
    408 		})
    409 	}
    410 	return iinfo.implementations
    411 }
    412 
    413 // addRuntimeType is called for each concrete type that can be the
    414 // dynamic type of some interface or reflect.Value.
    415 func (r *rta) addRuntimeType(T types.Type) {
    416 	methodSetOf := r.prog.MethodSets.MethodSet
    417 	typesinternal.ForEachElement(methodSetOf, T, func(T types.Type, access bool) bool {
    418 		if prevInaccess, ok := r.result.RuntimeTypes.At(T).(bool); ok {
    419 			if prevInaccess && access {
    420 				// A type previously marked inaccessible (ok && prevInaccess)
    421 				// is now found to be accessible (access):
    422 				// record that it is no longer inaccessible (false).
    423 				// (The inverted sense of the map is regrettable.)
    424 				r.result.RuntimeTypes.Set(T, false)
    425 			}
    426 			return true // seen; prune traversal
    427 		}
    428 		r.result.RuntimeTypes.Set(T, !access) // record inaccessibility
    429 
    430 		if !types.IsInterface(T) {
    431 			// T is a new concrete type.
    432 
    433 			// Exported methods are always potentially callable via reflection.
    434 			for sel := range methodSetOf(T).Methods() {
    435 				if sel.Obj().Exported() {
    436 					r.addReachable(r.prog.MethodValue(sel), true)
    437 				}
    438 			}
    439 
    440 			// Add callgraph edge for each existing dynamic
    441 			// "invoke"-mode call via that interface.
    442 			for _, I := range r.interfaces(T) {
    443 				sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction)
    444 				for _, site := range sites {
    445 					r.addInvokeEdge(site, T)
    446 				}
    447 			}
    448 		}
    449 
    450 		return false
    451 	})
    452 }
    453 
    454 // fingerprint returns a bitmask with one bit set per method id,
    455 // enabling 'implements' to quickly reject most candidates.
    456 func fingerprint(mset *types.MethodSet) uint64 {
    457 	var space [64]byte
    458 	var mask uint64
    459 	for method := range mset.Methods() {
    460 		method := method.Obj()
    461 		sig := method.Type().(*types.Signature)
    462 		if sig.TypeParams() != nil {
    463 			continue // skip generic methods since interfaces don't have them
    464 		}
    465 		sum := crc32.ChecksumIEEE(fmt.Appendf(space[:], "%s/%d/%d",
    466 			method.Id(),
    467 			sig.Params().Len(),
    468 			sig.Results().Len()))
    469 		mask |= 1 << (sum % 64)
    470 	}
    471 	return mask
    472 }
    473 
    474 // implements reports whether types.Implements(cinfo.C, iinfo.I),
    475 // but more efficiently.
    476 func implements(cinfo *concreteTypeInfo, iinfo *interfaceTypeInfo) (got bool) {
    477 	// The concrete type must have at least the methods
    478 	// (bits) of the interface type. Use a bitwise subset
    479 	// test to reject most candidates quickly.
    480 	return iinfo.fprint & ^cinfo.fprint == 0 && types.Implements(cinfo.C, iinfo.I)
    481 }