250 lines
7.4 KiB
Go
250 lines
7.4 KiB
Go
// 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
|
|
}
|