mirror of
https://github.com/golang/go
synced 2024-11-18 17:04:41 -07:00
go.tools/ssa: create thunks for method expressions T.f.
Until now, the same Function was used to represent a method (T)func() and the "method expression" function func(T) formed from it. So the SSA code for this: var buf bytes.Buffer f := Buffer.Bytes f(buf) buf.Bytes() would involve an implicit cast (ChangeType) on line 2. However, compilers based on go/ssa may want to use different calling conventions for them, like gccgo does (see issue 7839). This change decouples them by using an anonymous function called a "thunk", rather like this: f := func(r *bytes.Buffer) []byte { return r.Bytes() } Thunks are similar to method wrappers; both are created by makeWrapper. "Interface method wrappers" were a special case of thunks for direct calls (no indirection/fields) of interface methods. They are now subsumed by thunks and have been deleted. Now that only the needed thunks are built, we don't need to populate the concrete method sets of interface types at all, so (*Program).Method and LookupMethod return nil for them. This results in a slight reduction in function count (>1%) and instruction count (<<1%). Details: go/ssa: - API: ChangeType no longer supports func/method conversions. - API: (*Program).FuncValue now returns nil for abstract (interface) methods. - API: (*Function).RelString simplified. "$bound" is now a suffix not a prefix, and the receiver type is rendered package-relative. - API: Function.Object is now defined for all wrappers too. - API: (*Program).Method and LookupMethod return nil for abstract methods. - emitConv no longer permits (non-identical) Signature->Signature conversions. Added assertion. - add and use isInterface helper - sanity: we check packages after Build, not Create, otherwise cross-package refs might fail. go/pointer: - update tests for new function strings. - pointer_test: don't add non-pointerlike probes to analysis. (The error was checked, but too late, causing a panic.) - fixed a minor bug: if a test probe print(x) was the sole reference to x, no nodes were generated for x. - (reflect.Type).MethodByName: updated due to ssa API changes. Also, fixed incorrect testdata/funcreflect.go expectation for MethodByName on interfaces. oracle: - fix for new FuncValue semantics. - a "pointsto" query on an I.f thunk now returns an error. Fixes golang/go#7839 LGTM=gri R=gri CC=golang-codereviews, pcc https://golang.org/cl/93780044
This commit is contained in:
parent
9fc9dd9a01
commit
fec252214b
@ -548,6 +548,11 @@ func (a *analysis) genBuiltinCall(instr ssa.CallInstruction, cgn *cgnode) {
|
||||
a.copy(a.valueNode(v), a.panicNode, 1)
|
||||
}
|
||||
|
||||
case "print":
|
||||
// In the tests, the probe might be the sole reference
|
||||
// to its arg, so make sure we create nodes for it.
|
||||
a.valueNode(call.Args[0])
|
||||
|
||||
default:
|
||||
// No-ops: close len cap real imag complex print println delete.
|
||||
}
|
||||
|
@ -296,7 +296,10 @@ func doOneInput(input, filename string) bool {
|
||||
Log: &log,
|
||||
}
|
||||
for probe := range probes {
|
||||
config.AddQuery(probe.Args[0])
|
||||
v := probe.Args[0]
|
||||
if pointer.CanPoint(v.Type()) {
|
||||
config.AddQuery(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Print the log is there was an error or a panic.
|
||||
|
@ -1786,7 +1786,7 @@ func (c *rtypeMethodByNameConstraint) solve(a *analysis, _ *node, delta nodeset)
|
||||
for tObj := range delta {
|
||||
T := a.nodes[tObj].obj.data.(types.Type)
|
||||
|
||||
_, isInterface := T.Underlying().(*types.Interface)
|
||||
isIface := isInterface(T)
|
||||
|
||||
// We don't use Lookup(c.name) when c.name != "" to avoid
|
||||
// ambiguity: >1 unexported methods could match.
|
||||
@ -1802,24 +1802,26 @@ func (c *rtypeMethodByNameConstraint) solve(a *analysis, _ *node, delta nodeset)
|
||||
// 4 Func Value
|
||||
// 5 Index int
|
||||
// }
|
||||
fn := a.prog.Method(sel)
|
||||
|
||||
sig := fn.Signature
|
||||
if isInterface {
|
||||
// discard receiver
|
||||
sig = types.NewSignature(nil, nil, sig.Params(), sig.Results(), sig.Variadic())
|
||||
var sig *types.Signature
|
||||
var fn *ssa.Function
|
||||
if isIface {
|
||||
sig = sel.Type().(*types.Signature)
|
||||
} else {
|
||||
fn = a.prog.Method(sel)
|
||||
// move receiver to params[0]
|
||||
sig = changeRecv(sig)
|
||||
|
||||
sig = changeRecv(fn.Signature)
|
||||
}
|
||||
|
||||
// a.offsetOf(Type) is 3.
|
||||
if id := c.result + 3; a.addLabel(id, a.makeRtype(sig)) {
|
||||
a.addWork(id)
|
||||
}
|
||||
// a.offsetOf(Func) is 4.
|
||||
if id := c.result + 4; a.addLabel(id, a.objectNode(nil, fn)) {
|
||||
a.addWork(id)
|
||||
if fn != nil {
|
||||
// a.offsetOf(Func) is 4.
|
||||
if id := c.result + 4; a.addLabel(id, a.objectNode(nil, fn)) {
|
||||
a.addWork(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
go/pointer/testdata/finalizer.go
vendored
2
go/pointer/testdata/finalizer.go
vendored
@ -52,7 +52,7 @@ func runtimeSetFinalizer3() {
|
||||
runtime.SetFinalizer(x, (*T).finalize)
|
||||
}
|
||||
|
||||
// @calls main.runtimeSetFinalizer3 -> (*main.T).finalize
|
||||
// @calls main.runtimeSetFinalizer3 -> (*main.T).finalize$thunk
|
||||
|
||||
// I hope I never live to see this code in the wild.
|
||||
var setFinalizer = runtime.SetFinalizer
|
||||
|
14
go/pointer/testdata/func.go
vendored
14
go/pointer/testdata/func.go
vendored
@ -113,8 +113,8 @@ func func5() {
|
||||
}
|
||||
|
||||
// @calls main.func5 -> (*main.T).f
|
||||
// @calls main.func5 -> (*main.T).g
|
||||
// @calls main.func5 -> (*main.T).h
|
||||
// @calls main.func5 -> (*main.T).g$thunk
|
||||
// @calls main.func5 -> (*main.T).h$thunk
|
||||
|
||||
func func6() {
|
||||
A := &a
|
||||
@ -138,18 +138,18 @@ func func7() {
|
||||
var i I = D{}
|
||||
imethodClosure := i.f
|
||||
imethodClosure()
|
||||
// @calls main.func7 -> bound$(main.I).f
|
||||
// @calls bound$(main.I).f -> (main.D).f
|
||||
// @calls main.func7 -> (main.I).f$bound
|
||||
// @calls (main.I).f$bound -> (main.D).f
|
||||
|
||||
var d D
|
||||
cmethodClosure := d.f
|
||||
cmethodClosure()
|
||||
// @calls main.func7 -> bound$(main.D).f
|
||||
// @calls bound$(main.D).f ->(main.D).f
|
||||
// @calls main.func7 -> (main.D).f$bound
|
||||
// @calls (main.D).f$bound ->(main.D).f
|
||||
|
||||
methodExpr := D.f
|
||||
methodExpr(d)
|
||||
// @calls main.func7 -> (main.D).f
|
||||
// @calls main.func7 -> (main.D).f$thunk
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
6
go/pointer/testdata/funcreflect.go
vendored
6
go/pointer/testdata/funcreflect.go
vendored
@ -51,8 +51,8 @@ func reflectValueCallIndirect() {
|
||||
print(res0.(*T)) // @pointsto new@newT2:19
|
||||
}
|
||||
|
||||
// @calls main.reflectValueCallIndirect -> bound$(reflect.Value).Call
|
||||
// @calls bound$(reflect.Value).Call -> main.g
|
||||
// @calls main.reflectValueCallIndirect -> (reflect.Value).Call$bound
|
||||
// @calls (reflect.Value).Call$bound -> main.g
|
||||
|
||||
func reflectTypeInOut() {
|
||||
var f func(float64, bool) (string, int)
|
||||
@ -111,7 +111,7 @@ func reflectTypeMethodByName() {
|
||||
print(reflect.Zero(rThasF)) // @types hasF
|
||||
F2, _ := rThasF.MethodByName("F")
|
||||
print(reflect.Zero(F2.Type)) // @types func()
|
||||
print(F2.Func) // @pointsto (main.hasF).F
|
||||
print(F2.Func) // @pointsto
|
||||
|
||||
}
|
||||
|
||||
|
6
go/pointer/testdata/interfaces.go
vendored
6
go/pointer/testdata/interfaces.go
vendored
@ -135,12 +135,12 @@ func interface5() {
|
||||
|
||||
func interface6() {
|
||||
f := I.f
|
||||
print(f) // @pointsto (main.I).f
|
||||
print(f) // @pointsto (main.I).f$thunk
|
||||
f(new(struct{ D }))
|
||||
}
|
||||
|
||||
// @calls main.interface6 -> (main.I).f
|
||||
// @calls (main.I).f -> (*struct{main.D}).f
|
||||
// @calls main.interface6 -> (main.I).f$thunk
|
||||
// @calls (main.I).f$thunk -> (*struct{main.D}).f
|
||||
|
||||
func main() {
|
||||
interface1()
|
||||
|
@ -431,7 +431,7 @@ func (b *builder) exprInPlace(fn *Function, loc lvalue, e ast.Expr) {
|
||||
}
|
||||
|
||||
if _, ok := loc.(*address); ok {
|
||||
if _, ok := loc.typ().Underlying().(*types.Interface); ok {
|
||||
if isInterface(loc.typ()) {
|
||||
// e.g. var x interface{} = T{...}
|
||||
// Can't in-place initialize an interface value.
|
||||
// Fall back to copying.
|
||||
@ -624,25 +624,25 @@ func (b *builder) expr0(fn *Function, e ast.Expr) Value {
|
||||
|
||||
case types.MethodExpr:
|
||||
// (*T).f or T.f, the method f from the method-set of type T.
|
||||
// For declared methods, a simple conversion will suffice.
|
||||
return emitConv(fn, fn.Prog.Method(sel), fn.Pkg.typeOf(e))
|
||||
// The result is a "thunk".
|
||||
return emitConv(fn, makeThunk(fn.Prog, sel), fn.Pkg.typeOf(e))
|
||||
|
||||
case types.MethodVal:
|
||||
// e.f where e is an expression and f is a method.
|
||||
// The result is a bound method closure.
|
||||
// The result is a "bound".
|
||||
obj := sel.Obj().(*types.Func)
|
||||
rt := recvType(obj)
|
||||
wantAddr := isPointer(rt)
|
||||
escaping := true
|
||||
v := b.receiver(fn, e.X, wantAddr, escaping, sel)
|
||||
if _, ok := rt.Underlying().(*types.Interface); ok {
|
||||
if isInterface(rt) {
|
||||
// If v has interface type I,
|
||||
// we must emit a check that v is non-nil.
|
||||
// We use: typeassert v.(I).
|
||||
emitTypeAssert(fn, v, rt, token.NoPos)
|
||||
}
|
||||
c := &MakeClosure{
|
||||
Fn: boundMethodWrapper(fn.Prog, obj),
|
||||
Fn: makeBound(fn.Prog, obj),
|
||||
Bindings: []Value{v},
|
||||
}
|
||||
c.setPos(e.Sel.Pos())
|
||||
@ -798,7 +798,7 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
|
||||
wantAddr := isPointer(recvType(obj))
|
||||
escaping := true
|
||||
v := b.receiver(fn, selector.X, wantAddr, escaping, sel)
|
||||
if _, ok := deref(v.Type()).Underlying().(*types.Interface); ok {
|
||||
if isInterface(deref(v.Type())) {
|
||||
// Invoke-mode call.
|
||||
c.Value = v
|
||||
c.Method = obj
|
||||
@ -2265,6 +2265,10 @@ func (p *Package) Build() {
|
||||
init.finishBody()
|
||||
|
||||
p.info = nil // We no longer need ASTs or go/types deductions.
|
||||
|
||||
if p.Prog.mode&SanityCheckFunctions != 0 {
|
||||
sanityCheckPackage(p)
|
||||
}
|
||||
}
|
||||
|
||||
// Only valid during p's create and build phases.
|
||||
|
@ -39,12 +39,12 @@ const (
|
||||
//
|
||||
func Create(iprog *loader.Program, mode BuilderMode) *Program {
|
||||
prog := &Program{
|
||||
Fset: iprog.Fset,
|
||||
imported: make(map[string]*Package),
|
||||
packages: make(map[*types.Package]*Package),
|
||||
boundMethodWrappers: make(map[*types.Func]*Function),
|
||||
ifaceMethodWrappers: make(map[*types.Func]*Function),
|
||||
mode: mode,
|
||||
Fset: iprog.Fset,
|
||||
imported: make(map[string]*Package),
|
||||
packages: make(map[*types.Package]*Package),
|
||||
thunks: make(map[selectionKey]*Function),
|
||||
bounds: make(map[*types.Func]*Function),
|
||||
mode: mode,
|
||||
}
|
||||
|
||||
for _, info := range iprog.AllPackages {
|
||||
@ -243,10 +243,6 @@ func (prog *Program) CreatePackage(info *loader.PackageInfo) *Package {
|
||||
}
|
||||
prog.packages[p.Object] = p
|
||||
|
||||
if prog.mode&SanityCheckFunctions != 0 {
|
||||
sanityCheckPackage(p)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ package ssa
|
||||
// Helpers for emitting SSA instructions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
@ -161,11 +162,6 @@ func isValuePreserving(ut_src, ut_dst types.Type) bool {
|
||||
// Conversion between pointers with identical base types?
|
||||
_, ok := ut_src.(*types.Pointer)
|
||||
return ok
|
||||
|
||||
case *types.Signature:
|
||||
// Conversion from (T) func f() method to f(T) function?
|
||||
_, ok := ut_src.(*types.Signature)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -233,10 +229,18 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
|
||||
// e.g. string -> []byte/[]rune.
|
||||
}
|
||||
|
||||
// A representation-changing conversion.
|
||||
c := &Convert{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
// A representation-changing conversion?
|
||||
// At least one of {ut_src,ut_dst} must be *Basic.
|
||||
// (The other may be []byte or []rune.)
|
||||
_, ok1 := ut_src.(*types.Basic)
|
||||
_, ok2 := ut_dst.(*types.Basic)
|
||||
if ok1 || ok2 {
|
||||
c := &Convert{X: val}
|
||||
c.setType(typ)
|
||||
return f.emit(c)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ))
|
||||
}
|
||||
|
||||
// emitStore emits to f an instruction to store value val at location
|
||||
|
@ -454,52 +454,51 @@ func (f *Function) emit(instr Instruction) Value {
|
||||
// Examples:
|
||||
// "math.IsNaN" // a package-level function
|
||||
// "IsNaN" // intra-package reference to same
|
||||
// "(*sync.WaitGroup).Add" // a declared method
|
||||
// "(*Return).Block" // a promotion wrapper method (intra-package ref)
|
||||
// "(Instruction).Block" // an interface method wrapper (intra-package ref)
|
||||
// "(*bytes.Buffer).Bytes" // a declared method or a wrapper
|
||||
// "(*Buffer).Bytes" // intra-package reference to same
|
||||
// "(*Buffer).Bytes$thunk" // thunk (func wrapping method; receiver is param 0)
|
||||
// "(*Buffer).Bytes$bound" // bound (func wrapping method; receiver supplied by closure)
|
||||
// "main$1" // an anonymous function
|
||||
// "init$1" // a declared init function
|
||||
// "init" // the synthesized package initializer
|
||||
// "bound$(*T).f" // a bound method wrapper
|
||||
//
|
||||
// If from==f.Pkg, suppress package qualification.
|
||||
//
|
||||
func (f *Function) RelString(from *types.Package) string {
|
||||
// TODO(adonovan): expose less fragile case discrimination
|
||||
// using f.method.
|
||||
|
||||
// Anonymous?
|
||||
if f.Enclosing != nil {
|
||||
return f.name
|
||||
}
|
||||
|
||||
// Declared method, or promotion/indirection wrapper?
|
||||
// Method (declared or wrapper)?
|
||||
if recv := f.Signature.Recv(); recv != nil {
|
||||
return fmt.Sprintf("(%s).%s", relType(recv.Type(), from), f.name)
|
||||
return f.relMethod(from, recv.Type())
|
||||
}
|
||||
|
||||
// Other synthetic wrapper?
|
||||
if f.Synthetic != "" {
|
||||
// Bound method wrapper?
|
||||
if strings.HasPrefix(f.name, "bound$") {
|
||||
return f.name
|
||||
}
|
||||
|
||||
// Interface method wrapper?
|
||||
if strings.HasPrefix(f.Synthetic, "interface ") {
|
||||
return fmt.Sprintf("(%s).%s", relType(f.Params[0].Type(), from), f.name)
|
||||
}
|
||||
|
||||
// "package initializer" or "loaded from GC object file": fall through.
|
||||
// Thunk?
|
||||
if f.method != nil {
|
||||
return f.relMethod(from, f.method.Recv())
|
||||
}
|
||||
|
||||
// Package-level function.
|
||||
// Bound?
|
||||
if len(f.FreeVars) == 1 && strings.HasSuffix(f.name, "$bound") {
|
||||
return f.relMethod(from, f.FreeVars[0].Type())
|
||||
}
|
||||
|
||||
// Package-level function?
|
||||
// Prefix with package name for cross-package references only.
|
||||
if p := f.pkgobj(); p != from {
|
||||
if p := f.pkgobj(); p != nil && p != from {
|
||||
return fmt.Sprintf("%s.%s", p.Path(), f.name)
|
||||
}
|
||||
|
||||
// Unknown.
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f *Function) relMethod(from *types.Package, recv types.Type) string {
|
||||
return fmt.Sprintf("(%s).%s", relType(recv, from), f.name)
|
||||
}
|
||||
|
||||
// writeSignature writes to buf the signature sig in declaration syntax.
|
||||
func writeSignature(buf *bytes.Buffer, pkg *types.Package, name string, sig *types.Signature, params []*Parameter) {
|
||||
buf.WriteString("func ")
|
||||
|
@ -5,34 +5,51 @@
|
||||
package ssa
|
||||
|
||||
// This file defines utilities for population of method sets and
|
||||
// synthesis of wrapper methods.
|
||||
// synthesis of Functions that delegate to declared methods, which
|
||||
// come in three kinds:
|
||||
//
|
||||
// Wrappers include:
|
||||
// - indirection/promotion wrappers for methods of embedded fields.
|
||||
// - interface method wrappers for expressions I.f.
|
||||
// - bound method wrappers, for uncalled obj.Method closures.
|
||||
|
||||
// TODO(adonovan): split and rename to {methodset,wrappers}.go.
|
||||
// (1) wrappers: methods that wrap declared methods, performing
|
||||
// implicit pointer indirections and embedded field selections.
|
||||
//
|
||||
// (2) thunks: funcs that wrap declared methods. Like wrappers,
|
||||
// thunks perform indirections and field selections. The thunks's
|
||||
// first parameter is used as the receiver for the method call.
|
||||
//
|
||||
// (3) bounds: funcs that wrap declared methods. The bound's sole
|
||||
// free variable, supplied by a closure, is used as the receiver
|
||||
// for the method call. No indirections or field selections are
|
||||
// performed since they can be done before the call.
|
||||
//
|
||||
// TODO(adonovan): split and rename to {methodset,delegate}.go.
|
||||
// TODO(adonovan): use 'sel' not 'meth' for *types.Selection; reserve 'meth' for *Function.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
)
|
||||
|
||||
// Method returns the Function implementing method meth, building
|
||||
// wrapper methods on demand.
|
||||
// wrapper methods on demand. It returns nil if meth denotes an
|
||||
// abstract (interface) method.
|
||||
//
|
||||
// Precondition: meth Kind() == MethodVal.
|
||||
//
|
||||
// TODO(adonovan): rename this to MethodValue because of the
|
||||
// precondition, and for consistency with functions in source.go.
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) Method(meth *types.Selection) *Function {
|
||||
if meth == nil {
|
||||
panic("Method(nil)")
|
||||
if meth.Kind() != types.MethodVal {
|
||||
panic(fmt.Sprintf("Method(%s) kind != MethodVal", meth))
|
||||
}
|
||||
T := meth.Recv()
|
||||
if isInterface(T) {
|
||||
return nil // abstract method
|
||||
}
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("Method %s %v", T, meth)()
|
||||
}
|
||||
@ -44,7 +61,8 @@ func (prog *Program) Method(meth *types.Selection) *Function {
|
||||
}
|
||||
|
||||
// LookupMethod returns the implementation of the method of type T
|
||||
// identified by (pkg, name). It panics if there is no such method.
|
||||
// identified by (pkg, name). It returns nil if the method exists but
|
||||
// is abstract, and panics if T has no such method.
|
||||
//
|
||||
func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function {
|
||||
sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name)
|
||||
@ -54,17 +72,20 @@ func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string)
|
||||
return prog.Method(sel)
|
||||
}
|
||||
|
||||
// makeMethods ensures that all wrappers in the complete method set of
|
||||
// T are generated. It is equivalent to calling prog.Method() on all
|
||||
// makeMethods ensures that all concrete methods of type T are
|
||||
// generated. It is equivalent to calling prog.Method() on all
|
||||
// members of T.methodSet(), but acquires fewer locks.
|
||||
//
|
||||
// It reports whether the type's method set is non-empty.
|
||||
// It reports whether the type's (concrete) method set is non-empty.
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) makeMethods(T types.Type) bool {
|
||||
if isInterface(T) {
|
||||
return false // abstract method
|
||||
}
|
||||
tmset := prog.MethodSets.MethodSet(T)
|
||||
n := tmset.Len()
|
||||
if n == 0 {
|
||||
@ -89,11 +110,13 @@ func (prog *Program) makeMethods(T types.Type) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// methodSet contains the (concrete) methods of a non-interface type.
|
||||
type methodSet struct {
|
||||
mapping map[string]*Function // populated lazily
|
||||
complete bool // mapping contains all methods
|
||||
}
|
||||
|
||||
// Precondition: !isInterface(T).
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
func (prog *Program) createMethodSet(T types.Type) *methodSet {
|
||||
mset, ok := prog.methodSets.At(T).(*methodSet)
|
||||
@ -105,19 +128,33 @@ func (prog *Program) createMethodSet(T types.Type) *methodSet {
|
||||
}
|
||||
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
func (prog *Program) addMethod(mset *methodSet, meth *types.Selection) *Function {
|
||||
id := meth.Obj().Id()
|
||||
func (prog *Program) addMethod(mset *methodSet, sel *types.Selection) *Function {
|
||||
if sel.Kind() == types.MethodExpr {
|
||||
panic(sel)
|
||||
}
|
||||
id := sel.Obj().Id()
|
||||
fn := mset.mapping[id]
|
||||
if fn == nil {
|
||||
fn = findMethod(prog, meth)
|
||||
obj := sel.Obj().(*types.Func)
|
||||
|
||||
needsPromotion := len(sel.Index()) > 1
|
||||
needsIndirection := !isPointer(recvType(obj)) && isPointer(sel.Recv())
|
||||
if needsPromotion || needsIndirection {
|
||||
fn = makeWrapper(prog, sel)
|
||||
} else {
|
||||
fn = prog.declaredFunc(obj)
|
||||
}
|
||||
if fn.Signature.Recv() == nil {
|
||||
panic(fn) // missing receiver
|
||||
}
|
||||
mset.mapping[id] = fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// TypesWithMethodSets returns a new unordered slice containing all
|
||||
// types in the program for which a complete (non-empty) method set is
|
||||
// required at run-time.
|
||||
// concrete types in the program for which a complete (non-empty)
|
||||
// method set is required at run-time.
|
||||
//
|
||||
// It is the union of pkg.TypesWithMethodSets() for all pkg in
|
||||
// prog.AllPackages().
|
||||
@ -139,10 +176,10 @@ func (prog *Program) TypesWithMethodSets() []types.Type {
|
||||
return res
|
||||
}
|
||||
|
||||
// TypesWithMethodSets returns an unordered slice containing the
|
||||
// set of all types referenced within package pkg and not belonging to
|
||||
// some other package, for which a complete (non-empty) method set is
|
||||
// required at run-time.
|
||||
// TypesWithMethodSets returns an unordered slice containing the set
|
||||
// of all concrete types referenced within package pkg and not
|
||||
// belonging to some other package, for which a complete (non-empty)
|
||||
// method set is required at run-time.
|
||||
//
|
||||
// A type belongs to a package if it is a named type or a pointer to a
|
||||
// named type, and the name was defined in that package. All other
|
||||
@ -165,8 +202,6 @@ func (pkg *Package) TypesWithMethodSets() []types.Type {
|
||||
return pkg.methodSets
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// declaredFunc returns the concrete function/method denoted by obj.
|
||||
// Panic ensues if there is none.
|
||||
//
|
||||
@ -177,78 +212,63 @@ func (prog *Program) declaredFunc(obj *types.Func) *Function {
|
||||
panic("no concrete method: " + obj.String())
|
||||
}
|
||||
|
||||
// recvType returns the receiver type of method obj.
|
||||
func recvType(obj *types.Func) types.Type {
|
||||
return obj.Type().(*types.Signature).Recv().Type()
|
||||
}
|
||||
// -- wrappers ---------------------------------------------------------
|
||||
|
||||
// findMethod returns the concrete Function for the method meth,
|
||||
// synthesizing wrappers as needed.
|
||||
// makeWrapper returns a synthetic method that delegates to the
|
||||
// declared method denoted by meth.Obj(), first performing any
|
||||
// necessary pointer indirections or field selections implied by meth.
|
||||
//
|
||||
// The resulting method's receiver type is meth.Recv().
|
||||
//
|
||||
// This function is versatile but quite subtle! Consider the
|
||||
// following axes of variation when making changes:
|
||||
// - optional receiver indirection
|
||||
// - optional implicit field selections
|
||||
// - meth.Obj() may denote a concrete or an interface method
|
||||
// - the result may be a thunk or a wrapper.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
//
|
||||
func findMethod(prog *Program, meth *types.Selection) *Function {
|
||||
needsPromotion := len(meth.Index()) > 1
|
||||
obj := meth.Obj().(*types.Func)
|
||||
needsIndirection := !isPointer(recvType(obj)) && isPointer(meth.Recv())
|
||||
func makeWrapper(prog *Program, meth *types.Selection) *Function {
|
||||
obj := meth.Obj().(*types.Func) // the declared function
|
||||
sig := meth.Type().(*types.Signature) // type of this wrapper
|
||||
|
||||
if needsPromotion || needsIndirection {
|
||||
return makeWrapper(prog, meth.Recv(), meth)
|
||||
var recv *types.Var // wrapper's receiver or thunk's params[0]
|
||||
name := obj.Name()
|
||||
var description string
|
||||
var start int // first regular param
|
||||
if meth.Kind() == types.MethodExpr {
|
||||
name += "$thunk"
|
||||
description = "thunk"
|
||||
recv = sig.Params().At(0)
|
||||
start = 1
|
||||
} else {
|
||||
description = "wrapper"
|
||||
recv = sig.Recv()
|
||||
}
|
||||
|
||||
if _, ok := meth.Recv().Underlying().(*types.Interface); ok {
|
||||
return interfaceMethodWrapper(prog, meth.Recv(), obj)
|
||||
}
|
||||
|
||||
return prog.declaredFunc(obj)
|
||||
}
|
||||
|
||||
// makeWrapper returns a synthetic wrapper Function that optionally
|
||||
// performs receiver indirection, implicit field selections and then a
|
||||
// tailcall of a "promoted" method. For example, given these decls:
|
||||
//
|
||||
// type A struct {B}
|
||||
// type B struct {*C}
|
||||
// type C ...
|
||||
// func (*C) f()
|
||||
//
|
||||
// then makeWrapper(typ=A, obj={Func:(*C).f, Indices=[B,C,f]})
|
||||
// synthesize this wrapper method:
|
||||
//
|
||||
// func (a A) f() { return a.B.C->f() }
|
||||
//
|
||||
// prog is the program to which the synthesized method will belong.
|
||||
// typ is the receiver type of the wrapper method. obj is the
|
||||
// type-checker's object for the promoted method; its Func may be a
|
||||
// concrete or an interface method.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
//
|
||||
func makeWrapper(prog *Program, typ types.Type, meth *types.Selection) *Function {
|
||||
obj := meth.Obj().(*types.Func)
|
||||
oldsig := obj.Type().(*types.Signature)
|
||||
recv := newVar("recv", typ)
|
||||
|
||||
description := fmt.Sprintf("wrapper for %s", obj)
|
||||
description = fmt.Sprintf("%s for %s", description, meth.Obj())
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("make %s to (%s)", description, typ)()
|
||||
defer logStack("make %s to (%s)", description, recv.Type())()
|
||||
}
|
||||
fn := &Function{
|
||||
name: obj.Name(),
|
||||
name: name,
|
||||
method: meth,
|
||||
Signature: changeRecv(oldsig, recv),
|
||||
object: obj,
|
||||
Signature: sig,
|
||||
Synthetic: description,
|
||||
Prog: prog,
|
||||
pos: obj.Pos(),
|
||||
}
|
||||
fn.startBody()
|
||||
fn.addSpilledParam(recv)
|
||||
createParams(fn)
|
||||
createParams(fn, start)
|
||||
|
||||
var v Value = fn.Locals[0] // spilled receiver
|
||||
if isPointer(typ) {
|
||||
// TODO(adonovan): consider emitting a nil-pointer check here
|
||||
// with a nice error message, like gc does.
|
||||
if isPointer(meth.Recv()) {
|
||||
// TODO(adonovan): consider emitting a nil-pointer
|
||||
// check here with a nice error message, like gc does.
|
||||
// We could define a new builtin for the purpose.
|
||||
v = emitLoad(fn, v)
|
||||
}
|
||||
|
||||
@ -268,8 +288,8 @@ func makeWrapper(prog *Program, typ types.Type, meth *types.Selection) *Function
|
||||
// address of implicit C field.
|
||||
|
||||
var c Call
|
||||
if _, ok := oldsig.Recv().Type().Underlying().(*types.Interface); !ok { // concrete method
|
||||
if !isPointer(oldsig.Recv().Type()) {
|
||||
if r := recvType(obj); !isInterface(r) { // concrete method
|
||||
if !isPointer(r) {
|
||||
v = emitLoad(fn, v)
|
||||
}
|
||||
c.Call.Value = prog.declaredFunc(obj)
|
||||
@ -288,11 +308,12 @@ func makeWrapper(prog *Program, typ types.Type, meth *types.Selection) *Function
|
||||
|
||||
// createParams creates parameters for wrapper method fn based on its
|
||||
// Signature.Params, which do not include the receiver.
|
||||
// start is the index of the first regular parameter to use.
|
||||
//
|
||||
func createParams(fn *Function) {
|
||||
func createParams(fn *Function, start int) {
|
||||
var last *Parameter
|
||||
tparams := fn.Signature.Params()
|
||||
for i, n := 0, tparams.Len(); i < n; i++ {
|
||||
for i, n := start, tparams.Len(); i < n; i++ {
|
||||
last = fn.addParamObj(tparams.At(i))
|
||||
}
|
||||
if fn.Signature.Variadic() {
|
||||
@ -300,76 +321,15 @@ func createParams(fn *Function) {
|
||||
}
|
||||
}
|
||||
|
||||
// Wrappers for standalone interface methods ----------------------------------
|
||||
// -- bounds -----------------------------------------------------------
|
||||
|
||||
// interfaceMethodWrapper returns a synthetic wrapper function
|
||||
// permitting an abstract method obj to be called like a standalone
|
||||
// function, e.g.:
|
||||
// makeBound returns a bound method wrapper (or "bound"), a synthetic
|
||||
// function that delegates to a concrete or interface method denoted
|
||||
// by obj. The resulting function has no receiver, but has one free
|
||||
// variable which will be used as the method's receiver in the
|
||||
// tail-call.
|
||||
//
|
||||
// type I interface { f(x int) R }
|
||||
// m := I.f // wrapper
|
||||
// var i I
|
||||
// m(i, 0)
|
||||
//
|
||||
// The wrapper is defined as if by:
|
||||
//
|
||||
// func (i I) f(x int, ...) R {
|
||||
// return i.f(x, ...)
|
||||
// }
|
||||
//
|
||||
// typ is the type of the receiver (I here). It isn't necessarily
|
||||
// equal to the recvType(obj) because one interface may embed another.
|
||||
// TODO(adonovan): more tests.
|
||||
//
|
||||
// TODO(adonovan): opt: currently the stub is created even when used
|
||||
// in call position: I.f(i, 0). Clearly this is suboptimal.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
//
|
||||
func interfaceMethodWrapper(prog *Program, typ types.Type, obj *types.Func) *Function {
|
||||
// If one interface embeds another they'll share the same
|
||||
// wrappers for common methods. This is safe, but it might
|
||||
// confuse some tools because of the implicit interface
|
||||
// conversion applied to the first argument. If this becomes
|
||||
// a problem, we should include 'typ' in the memoization key.
|
||||
fn, ok := prog.ifaceMethodWrappers[obj]
|
||||
if !ok {
|
||||
description := "interface method wrapper"
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("(%s).%s, %s", typ, obj.Name(), description)()
|
||||
}
|
||||
fn = &Function{
|
||||
name: obj.Name(),
|
||||
object: obj,
|
||||
Signature: obj.Type().(*types.Signature),
|
||||
Synthetic: description,
|
||||
pos: obj.Pos(),
|
||||
Prog: prog,
|
||||
}
|
||||
fn.startBody()
|
||||
fn.addParam("recv", typ, token.NoPos)
|
||||
createParams(fn)
|
||||
var c Call
|
||||
|
||||
c.Call.Method = obj
|
||||
c.Call.Value = fn.Params[0]
|
||||
for _, arg := range fn.Params[1:] {
|
||||
c.Call.Args = append(c.Call.Args, arg)
|
||||
}
|
||||
emitTailCall(fn, &c)
|
||||
fn.finishBody()
|
||||
|
||||
prog.ifaceMethodWrappers[obj] = fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// Wrappers for bound methods -------------------------------------------------
|
||||
|
||||
// boundMethodWrapper returns a synthetic wrapper function that
|
||||
// delegates to a concrete or interface method.
|
||||
// The wrapper has one free variable, the method's receiver.
|
||||
// Use MakeClosure with such a wrapper to construct a bound-method
|
||||
// Use MakeClosure with such a wrapper to construct a bound method
|
||||
// closure. e.g.:
|
||||
//
|
||||
// type T int or: type T interface { meth() }
|
||||
@ -382,19 +342,24 @@ func interfaceMethodWrapper(prog *Program, typ types.Type, obj *types.Func) *Fun
|
||||
//
|
||||
// f := func() { return t.meth() }
|
||||
//
|
||||
// Unlike makeWrapper, makeBound need perform no indirection or field
|
||||
// selections because that can be done before the closure is
|
||||
// constructed.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
||||
//
|
||||
func boundMethodWrapper(prog *Program, obj *types.Func) *Function {
|
||||
func makeBound(prog *Program, obj *types.Func) *Function {
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
fn, ok := prog.boundMethodWrappers[obj]
|
||||
fn, ok := prog.bounds[obj]
|
||||
if !ok {
|
||||
description := fmt.Sprintf("bound method wrapper for %s", obj)
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("%s", description)()
|
||||
}
|
||||
fn = &Function{
|
||||
name: "bound$" + obj.FullName(),
|
||||
name: obj.Name() + "$bound",
|
||||
object: obj,
|
||||
Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver
|
||||
Synthetic: description,
|
||||
Prog: prog,
|
||||
@ -404,10 +369,10 @@ func boundMethodWrapper(prog *Program, obj *types.Func) *Function {
|
||||
cap := &Capture{name: "recv", typ: recvType(obj), parent: fn}
|
||||
fn.FreeVars = []*Capture{cap}
|
||||
fn.startBody()
|
||||
createParams(fn)
|
||||
createParams(fn, 0)
|
||||
var c Call
|
||||
|
||||
if _, ok := recvType(obj).Underlying().(*types.Interface); !ok { // concrete
|
||||
if !isInterface(recvType(obj)) { // concrete
|
||||
c.Call.Value = prog.declaredFunc(obj)
|
||||
c.Call.Args = []Value{cap}
|
||||
} else {
|
||||
@ -420,7 +385,60 @@ func boundMethodWrapper(prog *Program, obj *types.Func) *Function {
|
||||
emitTailCall(fn, &c)
|
||||
fn.finishBody()
|
||||
|
||||
prog.boundMethodWrappers[obj] = fn
|
||||
prog.bounds[obj] = fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// -- thunks -----------------------------------------------------------
|
||||
|
||||
// makeThunk returns a thunk, a synthetic function that delegates to a
|
||||
// concrete or interface method denoted by sel.Obj(). The resulting
|
||||
// function has no receiver, but has an additional (first) regular
|
||||
// parameter.
|
||||
//
|
||||
// Precondition: sel.Kind() == types.MethodExpr.
|
||||
//
|
||||
// type T int or: type T interface { meth() }
|
||||
// func (t T) meth()
|
||||
// f := T.meth
|
||||
// var t T
|
||||
// f(t) // calls t.meth()
|
||||
//
|
||||
// f is a synthetic wrapper defined as if by:
|
||||
//
|
||||
// f := func(t T) { return t.meth() }
|
||||
//
|
||||
// TODO(adonovan): opt: currently the stub is created even when used
|
||||
// directly in a function call: C.f(i, 0). This is less efficient
|
||||
// than inlining the stub.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
|
||||
//
|
||||
func makeThunk(prog *Program, sel *types.Selection) *Function {
|
||||
if sel.Kind() != types.MethodExpr {
|
||||
panic(sel)
|
||||
}
|
||||
|
||||
// TODO(adonovan): opt: canonicalize the recv Type to avoid
|
||||
// construct unnecessary duplicate thunks.
|
||||
key := selectionKey{
|
||||
kind: sel.Kind(),
|
||||
recv: sel.Recv(),
|
||||
obj: sel.Obj(),
|
||||
index: fmt.Sprint(sel.Index()),
|
||||
indirect: sel.Indirect(),
|
||||
}
|
||||
|
||||
prog.methodsMu.Lock()
|
||||
defer prog.methodsMu.Unlock()
|
||||
fn, ok := prog.thunks[key]
|
||||
if !ok {
|
||||
fn = makeWrapper(prog, sel)
|
||||
if fn.Signature.Recv() != nil {
|
||||
panic(fn) // unexpected receiver
|
||||
}
|
||||
prog.thunks[key] = fn
|
||||
}
|
||||
return fn
|
||||
}
|
||||
@ -428,3 +446,22 @@ func boundMethodWrapper(prog *Program, obj *types.Func) *Function {
|
||||
func changeRecv(s *types.Signature, recv *types.Var) *types.Signature {
|
||||
return types.NewSignature(nil, recv, s.Params(), s.Results(), s.Variadic())
|
||||
}
|
||||
|
||||
// recvType returns the receiver type of method obj.
|
||||
func recvType(obj *types.Func) types.Type {
|
||||
return obj.Type().(*types.Signature).Recv().Type()
|
||||
}
|
||||
|
||||
func isInterface(T types.Type) bool {
|
||||
_, ok := T.Underlying().(*types.Interface)
|
||||
return ok
|
||||
}
|
||||
|
||||
// selectionKey is like types.Selection but a usable map key.
|
||||
type selectionKey struct {
|
||||
kind types.SelectionKind
|
||||
recv types.Type
|
||||
obj types.Object
|
||||
index string
|
||||
indirect bool
|
||||
}
|
||||
|
@ -389,11 +389,13 @@ func (s *sanity) checkFunction(fn *Function) bool {
|
||||
fn.String() // must not crash
|
||||
fn.RelString(fn.pkgobj()) // must not crash
|
||||
|
||||
// All functions have a package, except wrappers (which are
|
||||
// All functions have a package, except delegates (which are
|
||||
// shared across packages, or duplicated as weak symbols in a
|
||||
// separate-compilation model), and error.Error.
|
||||
if fn.Pkg == nil {
|
||||
if strings.Contains(fn.Synthetic, "wrapper") ||
|
||||
if strings.HasPrefix(fn.Synthetic, "wrapper ") ||
|
||||
strings.HasPrefix(fn.Synthetic, "bound ") ||
|
||||
strings.HasPrefix(fn.Synthetic, "thunk ") ||
|
||||
strings.HasSuffix(fn.name, "Error") {
|
||||
// ok
|
||||
} else {
|
||||
|
@ -193,20 +193,19 @@ func (prog *Program) packageLevelValue(obj types.Object) Value {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FuncValue returns the Function denoted by the source-level named
|
||||
// function obj.
|
||||
// FuncValue returns the concrete Function denoted by the source-level
|
||||
// named function obj, or nil if obj denotes an interface method.
|
||||
//
|
||||
// TODO(adonovan): check the invariant that obj.Type() matches the
|
||||
// result's Signature, both in the params/results and in the receiver.
|
||||
//
|
||||
func (prog *Program) FuncValue(obj *types.Func) *Function {
|
||||
// Package-level function or declared method?
|
||||
if v := prog.packageLevelValue(obj); v != nil {
|
||||
return v.(*Function)
|
||||
}
|
||||
// Interface method wrapper?
|
||||
return prog.LookupMethod(recvType(obj), obj.Pkg(), obj.Name())
|
||||
fn, _ := prog.packageLevelValue(obj).(*Function)
|
||||
return fn
|
||||
}
|
||||
|
||||
// ConstValue returns the SSA Value denoted by the source-level named
|
||||
// constant obj. The result may be a *Const, or nil if not found.
|
||||
// constant obj.
|
||||
//
|
||||
func (prog *Program) ConstValue(obj *types.Const) *Const {
|
||||
// TODO(adonovan): opt: share (don't reallocate)
|
||||
|
@ -110,7 +110,9 @@ func checkFuncValue(t *testing.T, prog *ssa.Program, obj *types.Func) {
|
||||
fn := prog.FuncValue(obj)
|
||||
// fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging
|
||||
if fn == nil {
|
||||
t.Errorf("FuncValue(%s) == nil", obj)
|
||||
if obj.Name() != "interfaceMethod" {
|
||||
t.Errorf("FuncValue(%s) == nil", obj)
|
||||
}
|
||||
return
|
||||
}
|
||||
if fnobj := fn.Object(); fnobj != obj {
|
||||
|
@ -28,10 +28,10 @@ type Program struct {
|
||||
mode BuilderMode // set of mode bits for SSA construction
|
||||
MethodSets types.MethodSetCache // cache of type-checker's method-sets
|
||||
|
||||
methodsMu sync.Mutex // guards the following maps:
|
||||
methodSets typeutil.Map // maps type to its concrete methodSet
|
||||
boundMethodWrappers map[*types.Func]*Function // wrappers for curried x.Method closures
|
||||
ifaceMethodWrappers map[*types.Func]*Function // wrappers for curried I.Method functions
|
||||
methodsMu sync.Mutex // guards the following maps:
|
||||
methodSets typeutil.Map // maps type to its concrete methodSet
|
||||
bounds map[*types.Func]*Function // bounds for curried x.Method closures
|
||||
thunks map[selectionKey]*Function // thunks for T.Method expressions
|
||||
}
|
||||
|
||||
// A Package is a single analyzed Go package containing Members for
|
||||
@ -268,8 +268,8 @@ type Instruction interface {
|
||||
//
|
||||
type Function struct {
|
||||
name string
|
||||
object types.Object // a declared *types.Func; nil for init, wrappers, etc.
|
||||
method *types.Selection // info about provenance of synthetic methods [currently unused]
|
||||
object types.Object // a declared *types.Func or one of its wrappers
|
||||
method *types.Selection // info about provenance of synthetic methods
|
||||
Signature *types.Signature
|
||||
pos token.Pos
|
||||
|
||||
@ -548,7 +548,6 @@ type UnOp struct {
|
||||
// - between a named type and its underlying type.
|
||||
// - between two named types of the same underlying type.
|
||||
// - between (possibly named) pointers to identical base types.
|
||||
// - between f(T) functions and (T) func f() methods.
|
||||
// - from a bidirectional channel to a read- or write-channel,
|
||||
// optionally adding/removing a name.
|
||||
//
|
||||
|
@ -82,6 +82,7 @@ func (s *Selection) Type() Type {
|
||||
// The type of x.f is a function (without receiver)
|
||||
// and an additional first argument with the same type as x.
|
||||
// TODO(gri) Similar code is already in call.go - factor!
|
||||
// TODO(gri) Compute this eagerly to avoid allocations.
|
||||
sig := *s.obj.(*Func).typ.(*Signature)
|
||||
arg0 := *sig.recv
|
||||
sig.recv = nil
|
||||
|
@ -48,7 +48,8 @@ func pointsto(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
}
|
||||
|
||||
// Reject non-pointerlike types (includes all constants).
|
||||
// Reject non-pointerlike types (includes all constants---except nil).
|
||||
// TODO(adonovan): reject nil too.
|
||||
typ := qpos.info.TypeOf(expr)
|
||||
if !pointer.CanPoint(typ) {
|
||||
return nil, fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ)
|
||||
@ -96,7 +97,13 @@ func ssaValueForIdent(prog *ssa.Program, qinfo *loader.PackageInfo, obj types.Ob
|
||||
return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name())
|
||||
|
||||
case *types.Func:
|
||||
return prog.FuncValue(obj), false, nil
|
||||
fn := prog.FuncValue(obj)
|
||||
if fn == nil {
|
||||
return nil, false, fmt.Errorf("%s is an interface method", obj)
|
||||
}
|
||||
// TODO(adonovan): there's no point running PTA on a *Func ident.
|
||||
// Eliminate this feature.
|
||||
return fn, false, nil
|
||||
}
|
||||
panic(obj)
|
||||
}
|
||||
|
6
oracle/testdata/src/main/pointsto.golden
vendored
6
oracle/testdata/src/main/pointsto.golden
vendored
@ -14,17 +14,15 @@ this func() may point to these objects:
|
||||
(pointsto.D).f
|
||||
|
||||
-------- @pointsto func-ref-I.f --------
|
||||
this func() may point to these objects:
|
||||
(pointsto.I).f
|
||||
|
||||
Error: func (pointsto.I).f() is an interface method
|
||||
-------- @pointsto func-ref-d.f --------
|
||||
this func() may point to these objects:
|
||||
(pointsto.D).f
|
||||
|
||||
-------- @pointsto func-ref-i.f --------
|
||||
this func() may point to these objects:
|
||||
(pointsto.I).f
|
||||
|
||||
Error: func (pointsto.I).f() is an interface method
|
||||
-------- @pointsto ref-lexical-d.f --------
|
||||
this func() may point to these objects:
|
||||
(pointsto.D).f
|
||||
|
Loading…
Reference in New Issue
Block a user