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 }