| // Copyright 2013 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package ssa |
| |
| // This file defines utilities for population of method sets. |
| |
| import ( |
| "fmt" |
| "go/token" |
| "go/types" |
| |
| "golang.org/x/tools/go/types/typeutil" |
| "golang.org/x/tools/internal/typesinternal" |
| ) |
| |
| // MethodValue returns the Function implementing method sel, building |
| // wrapper methods on demand. It returns nil if sel denotes an |
| // interface or generic method, or a method of a generic type. |
| // |
| // Precondition: sel.Kind() == MethodVal. |
| // |
| // Thread-safe. |
| // |
| // Acquires prog.methodsMu. |
| func (prog *Program) MethodValue(sel *types.Selection) *Function { |
| if sel.Kind() != types.MethodVal { |
| panic(fmt.Sprintf("MethodValue(%s) kind != MethodVal", sel)) |
| } |
| |
| method := sel.Obj().(*types.Func) |
| if method.Signature().TypeParams().Len() > 0 { |
| return nil // generic method |
| } |
| |
| T := sel.Recv() |
| if types.IsInterface(T) { |
| return nil // interface method or type parameter |
| } |
| |
| // We can avoid materializing sel.Type(): it will be parameterized iff |
| // the receiver type T is too (see go.dev/issue/81308). |
| if prog.isParameterized(T) { |
| return nil // method on generic type |
| } |
| |
| if prog.mode&LogSource != 0 { |
| defer logStack("MethodValue %s %v", T, sel)() |
| } |
| |
| key := methodKeyOf(method.Pkg(), method.Name()) |
| |
| // The critical section returns a builder only if it created a |
| // new Function. In the common case, a method created by an |
| // earlier call, it allocates nothing: a builder declared out |
| // here would escape to the heap on every call. |
| fn, b := func() (*Function, *builder) { |
| prog.methodsMu.Lock() |
| defer prog.methodsMu.Unlock() |
| |
| // Get or create SSA method set. |
| mset, ok := prog.methodSets.At(T).(*methodSet) |
| if !ok { |
| mset = &methodSet{mapping: make(map[methodKey]*Function)} |
| prog.methodSets.Set(T, mset) |
| } |
| |
| // Get or create SSA method. |
| if fn, ok := mset.mapping[key]; ok { |
| return fn, nil |
| } |
| b := new(builder) |
| var fn *Function |
| needsPromotion := len(sel.Index()) > 1 |
| needsIndirection := !isPointer(recvType(method)) && isPointer(T) |
| if needsPromotion || needsIndirection { |
| fn = createWrapper(prog, toSelection(sel), nil) |
| fn.buildshared = b.shared() |
| b.enqueue(fn) |
| } else { |
| fn = prog.objectMethod(method, nil, b) |
| } |
| if fn.Signature.Recv() == nil { |
| panic(fn) |
| } |
| mset.mapping[key] = fn |
| return fn, b |
| }() |
| |
| if b != nil { |
| b.iterate() |
| } else if !fn.buildshared.isTransitivelyDone() { |
| // fn was created by an earlier call and another builder |
| // is still building it: wait for that to finish. |
| var b builder |
| b.waitForSharedFunction(fn) |
| b.iterate() |
| } |
| |
| return fn |
| } |
| |
| // objectMethod returns the Function for a given method symbol. |
| // The symbol may be an instance of a generic function. It need not |
| // belong to an existing SSA package created by a call to |
| // prog.CreatePackage. |
| // |
| // objectMethod panics if the function is not a method. |
| // |
| // Acquires prog.objectMethodsMu. |
| func (prog *Program) objectMethod(obj *types.Func, targs []types.Type, b *builder) *Function { |
| sig := obj.Type().(*types.Signature) |
| if sig.Recv() == nil { |
| panic("not a method: " + obj.String()) |
| } |
| |
| // Instantiation of generic? |
| if orig := obj.Origin(); orig != obj || len(targs) > 0 { |
| return prog.objectMethod(orig, nil, b).instance(receiverTypeArgs(obj), targs, b) |
| } |
| |
| // Belongs to a created package? |
| if fn := prog.FuncValue(obj); fn != nil { |
| return fn |
| } |
| |
| // Consult/update cache of methods created from types.Func. |
| prog.objectMethodsMu.Lock() |
| defer prog.objectMethodsMu.Unlock() |
| fn, ok := prog.objectMethods[obj] |
| if !ok { |
| fn = createFunction(prog, obj, obj.Name(), nil, nil, "") |
| fn.Synthetic = "from type information (on demand)" |
| fn.buildshared = b.shared() |
| b.enqueue(fn) |
| |
| if prog.objectMethods == nil { |
| prog.objectMethods = make(map[*types.Func]*Function) |
| } |
| prog.objectMethods[obj] = fn |
| } else { |
| b.waitForSharedFunction(fn) |
| } |
| return fn |
| } |
| |
| // LookupMethod returns the implementation of the method of type T |
| // identified by (pkg, name). It returns nil if the method exists but |
| // is an interface method or generic method, and panics if T has no such method. |
| func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function { |
| // Fast path: the method was created and built by an earlier |
| // call. RTA calls LookupMethod once per (call site, concrete |
| // type) pair, so this is the common case; it avoids computing |
| // the method set of T and searching it, which dominates the |
| // cost of the slow path below. |
| if fn := prog.existingMethod(T, methodKeyOf(pkg, name)); fn != nil { |
| return fn |
| } |
| |
| sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name) |
| if sel == nil { |
| panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name))) |
| } |
| return prog.MethodValue(sel) |
| } |
| |
| // existingMethod returns the Function implementing the method of |
| // the concrete type T identified by key, if it has already been |
| // created and built, or nil. A recorded method implies that T was |
| // found to be concrete and non-parameterized and the method |
| // non-generic when it was created. |
| // |
| // Acquires prog.methodsMu. |
| func (prog *Program) existingMethod(T types.Type, key methodKey) *Function { |
| prog.methodsMu.Lock() |
| defer prog.methodsMu.Unlock() |
| if mset, ok := prog.methodSets.At(T).(*methodSet); ok { |
| if fn := mset.mapping[key]; fn != nil && fn.buildshared.isTransitivelyDone() { |
| return fn |
| } |
| } |
| return nil |
| } |
| |
| // methodSet contains the (concrete) methods of a concrete type (non-interface, non-parameterized). |
| type methodSet struct { |
| mapping map[methodKey]*Function // populated lazily |
| } |
| |
| // methodKey identifies a method within a method set the same way |
| // types.Id does, by name for exported methods and by package path |
| // and name for unexported ones, without building a string. |
| type methodKey struct { |
| path string // package path, or "" if name is exported |
| name string |
| } |
| |
| func methodKeyOf(pkg *types.Package, name string) methodKey { |
| if token.IsExported(name) { |
| return methodKey{"", name} |
| } |
| path := "_" // as types.Id does when pkg is nil |
| if pkg != nil { |
| path = pkg.Path() |
| } |
| return methodKey{path, name} |
| } |
| |
| // RuntimeTypes returns a new unordered slice containing all types in |
| // the program for which a runtime type is required. |
| // |
| // A runtime type is required for any non-parameterized, non-interface |
| // type that is converted to an interface, or for any type (including |
| // interface types) derivable from one through reflection. |
| // |
| // The methods of such types may be reachable through reflection or |
| // interface calls even if they are never called directly. |
| // |
| // Thread-safe. |
| // |
| // Acquires prog.makeInterfaceTypesMu. |
| func (prog *Program) RuntimeTypes() []types.Type { |
| prog.makeInterfaceTypesMu.Lock() |
| defer prog.makeInterfaceTypesMu.Unlock() |
| |
| // Compute the derived types on demand, since many SSA clients |
| // never call RuntimeTypes, and those that do typically call |
| // it once (often within ssautil.AllFunctions, which will |
| // eventually not use it; see Go issue #69291.) This |
| // eliminates the need to eagerly compute all the element |
| // types during SSA building. |
| var runtimeTypes []types.Type |
| var set typeutil.Map // for de-duping identical types |
| for t := range prog.makeInterfaceTypes { |
| typesinternal.ForEachElement(prog.MethodSets.MethodSet, t, func(t types.Type, access bool) bool { |
| if !access { |
| return false // inaccessible to reflection |
| } |
| seen, _ := set.Set(t, true).(bool) |
| if !seen { |
| runtimeTypes = append(runtimeTypes, t) |
| } |
| return seen |
| }) |
| } |
| |
| return runtimeTypes |
| } |