lazy.go (3412B)
1 // Copyright 2024 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 chautil provides helper functions related to 6 // class hierarchy analysis (CHA) for use in x/tools. 7 package chautil 8 9 import ( 10 "go/types" 11 12 "golang.org/x/tools/go/ssa" 13 "golang.org/x/tools/go/types/typeutil" 14 ) 15 16 // LazyCallees returns a function that maps a call site (in a function in fns) 17 // to its callees within fns. The set of callees is computed using the CHA algorithm, 18 // i.e., on the entire implements relation between interfaces and concrete types 19 // in fns. Please see golang.org/x/tools/go/callgraph/cha for more information. 20 // 21 // The resulting function is not concurrency safe. 22 func LazyCallees(fns map[*ssa.Function]bool) func(site ssa.CallInstruction) []*ssa.Function { 23 // funcsBySig contains all functions, keyed by signature. It is 24 // the effective set of address-taken functions used to resolve 25 // a dynamic call of a particular signature. 26 var funcsBySig typeutil.Map // value is []*ssa.Function 27 28 // methodsByID contains all methods, grouped by ID for efficient 29 // lookup. 30 // 31 // We must key by ID, not name, for correct resolution of interface 32 // calls to a type with two (unexported) methods spelled the same but 33 // from different packages. The fact that the concrete type implements 34 // the interface does not mean the call dispatches to both methods. 35 methodsByID := make(map[string][]*ssa.Function) 36 37 // An imethod represents an interface method I.m. 38 // (There's no go/types object for it; 39 // a *types.Func may be shared by many interfaces due to interface embedding.) 40 type imethod struct { 41 I *types.Interface 42 id string 43 } 44 // methodsMemo records, for every abstract method call I.m on 45 // interface type I, the set of concrete methods C.m of all 46 // types C that satisfy interface I. 47 // 48 // Abstract methods may be shared by several interfaces, 49 // hence we must pass I explicitly, not guess from m. 50 // 51 // methodsMemo is just a cache, so it needn't be a typeutil.Map. 52 methodsMemo := make(map[imethod][]*ssa.Function) 53 lookupMethods := func(I *types.Interface, m *types.Func) []*ssa.Function { 54 id := m.Id() 55 methods, ok := methodsMemo[imethod{I, id}] 56 if !ok { 57 for _, f := range methodsByID[id] { 58 C := f.Signature.Recv().Type() // named or *named 59 if types.Implements(C, I) { 60 methods = append(methods, f) 61 } 62 } 63 methodsMemo[imethod{I, id}] = methods 64 } 65 return methods 66 } 67 68 for f := range fns { 69 if f.Signature.Recv() == nil { 70 // Package initializers can never be address-taken. 71 if f.Name() == "init" && f.Synthetic == "package initializer" { 72 continue 73 } 74 funcs, _ := funcsBySig.At(f.Signature).([]*ssa.Function) 75 funcs = append(funcs, f) 76 funcsBySig.Set(f.Signature, funcs) 77 } else if obj := f.Object(); obj != nil { 78 id := obj.(*types.Func).Id() 79 methodsByID[id] = append(methodsByID[id], f) 80 } 81 } 82 83 return func(site ssa.CallInstruction) []*ssa.Function { 84 call := site.Common() 85 if call.IsInvoke() { 86 tiface := call.Value.Type().Underlying().(*types.Interface) 87 return lookupMethods(tiface, call.Method) 88 } else if g := call.StaticCallee(); g != nil { 89 return []*ssa.Function{g} 90 } else if _, ok := call.Value.(*ssa.Builtin); !ok { 91 fns, _ := funcsBySig.At(call.Signature()).([]*ssa.Function) 92 return fns 93 } 94 return nil 95 } 96 }