diff --git a/go/pointer/gen.go b/go/pointer/gen.go index c218d58e..f0ac1aeb 100644 --- a/go/pointer/gen.go +++ b/go/pointer/gen.go @@ -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. } diff --git a/go/pointer/pointer_test.go b/go/pointer/pointer_test.go index 4f276e70..777281ef 100644 --- a/go/pointer/pointer_test.go +++ b/go/pointer/pointer_test.go @@ -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. diff --git a/go/pointer/reflect.go b/go/pointer/reflect.go index c9b3e24a..56364c42 100644 --- a/go/pointer/reflect.go +++ b/go/pointer/reflect.go @@ -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) + } } } } diff --git a/go/pointer/testdata/finalizer.go b/go/pointer/testdata/finalizer.go index 28afacfb..d80f85ac 100644 --- a/go/pointer/testdata/finalizer.go +++ b/go/pointer/testdata/finalizer.go @@ -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 diff --git a/go/pointer/testdata/func.go b/go/pointer/testdata/func.go index a2cbd71c..8ffb8c74 100644 --- a/go/pointer/testdata/func.go +++ b/go/pointer/testdata/func.go @@ -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() { diff --git a/go/pointer/testdata/funcreflect.go b/go/pointer/testdata/funcreflect.go index 448740ff..a0a9a5fa 100644 --- a/go/pointer/testdata/funcreflect.go +++ b/go/pointer/testdata/funcreflect.go @@ -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 } diff --git a/go/pointer/testdata/interfaces.go b/go/pointer/testdata/interfaces.go index 60c238aa..91c0fa9a 100644 --- a/go/pointer/testdata/interfaces.go +++ b/go/pointer/testdata/interfaces.go @@ -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() diff --git a/go/ssa/builder.go b/go/ssa/builder.go index 8e5c2c29..9d7d9f37 100644 --- a/go/ssa/builder.go +++ b/go/ssa/builder.go @@ -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. diff --git a/go/ssa/create.go b/go/ssa/create.go index 3317013a..7e3b1f8f 100644 --- a/go/ssa/create.go +++ b/go/ssa/create.go @@ -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 } diff --git a/go/ssa/emit.go b/go/ssa/emit.go index cb68043a..5cb110f2 100644 --- a/go/ssa/emit.go +++ b/go/ssa/emit.go @@ -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 diff --git a/go/ssa/func.go b/go/ssa/func.go index bd637625..5f8d216f 100644 --- a/go/ssa/func.go +++ b/go/ssa/func.go @@ -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 ") diff --git a/go/ssa/promote.go b/go/ssa/promote.go index ea5b6afb..bf545007 100644 --- a/go/ssa/promote.go +++ b/go/ssa/promote.go @@ -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 +} diff --git a/go/ssa/sanity.go b/go/ssa/sanity.go index 4b0fb7e3..dd80b5dd 100644 --- a/go/ssa/sanity.go +++ b/go/ssa/sanity.go @@ -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 { diff --git a/go/ssa/source.go b/go/ssa/source.go index e4a69a29..0acfe8d5 100644 --- a/go/ssa/source.go +++ b/go/ssa/source.go @@ -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) diff --git a/go/ssa/source_test.go b/go/ssa/source_test.go index 0ad98202..85631c2a 100644 --- a/go/ssa/source_test.go +++ b/go/ssa/source_test.go @@ -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 { diff --git a/go/ssa/ssa.go b/go/ssa/ssa.go index 113ba855..9151c97b 100644 --- a/go/ssa/ssa.go +++ b/go/ssa/ssa.go @@ -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. // diff --git a/go/types/selection.go b/go/types/selection.go index 6a873d5a..a85f0d1f 100644 --- a/go/types/selection.go +++ b/go/types/selection.go @@ -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 diff --git a/oracle/pointsto.go b/oracle/pointsto.go index 76eb2aaf..b6ff413d 100644 --- a/oracle/pointsto.go +++ b/oracle/pointsto.go @@ -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) } diff --git a/oracle/testdata/src/main/pointsto.golden b/oracle/testdata/src/main/pointsto.golden index a4f9a5c2..b7f0e905 100644 --- a/oracle/testdata/src/main/pointsto.golden +++ b/oracle/testdata/src/main/pointsto.golden @@ -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