// 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/types" "golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/internal/aliases" ) // MethodValue returns the Function implementing method sel, building // wrapper methods on demand. It returns nil if sel denotes an // interface or generic method. // // 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)) } T := sel.Recv() if types.IsInterface(T) { return nil // interface method or type parameter } if prog.isParameterized(T) { return nil // generic method } if prog.mode&LogSource != 0 { defer logStack("MethodValue %s %v", T, sel)() } var b builder m := func() *Function { 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[string]*Function)} prog.methodSets.Set(T, mset) } // Get or create SSA method. id := sel.Obj().Id() fn, ok := mset.mapping[id] if !ok { obj := sel.Obj().(*types.Func) needsPromotion := len(sel.Index()) > 1 needsIndirection := !isPointer(recvType(obj)) && isPointer(T) if needsPromotion || needsIndirection { fn = createWrapper(prog, toSelection(sel)) fn.buildshared = b.shared() b.enqueue(fn) } else { fn = prog.objectMethod(obj, &b) } if fn.Signature.Recv() == nil { panic(fn) } mset.mapping[id] = fn } else { b.waitForSharedFunction(fn) } return fn }() b.iterate() return m } // 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, b *builder) *Function { sig := obj.Type().(*types.Signature) if sig.Recv() == nil { panic("not a method: " + obj.String()) } // Belongs to a created package? if fn := prog.FuncValue(obj); fn != nil { return fn } // Instantiation of generic? if originObj := obj.Origin(); originObj != obj { origin := prog.objectMethod(originObj, b) assert(origin.typeparams.Len() > 0, "origin is not generic") targs := receiverTypeArgs(obj) return origin.instance(targs, b) } // 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 { 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) } // methodSet contains the (concrete) methods of a concrete type (non-interface, non-parameterized). type methodSet struct { mapping map[string]*Function // populated lazily } // 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.runtimeTypesMu. func (prog *Program) RuntimeTypes() []types.Type { prog.runtimeTypesMu.Lock() defer prog.runtimeTypesMu.Unlock() return prog.runtimeTypes.Keys() } // forEachReachable calls f for type T and each type reachable from // its type through reflection. // // The function f must use memoization to break cycles and // return false when the type has already been visited. // // TODO(adonovan): publish in typeutil and share with go/callgraph/rta. func forEachReachable(msets *typeutil.MethodSetCache, T types.Type, f func(types.Type) bool) { var visit func(T types.Type, skip bool) visit = func(T types.Type, skip bool) { if !skip { if !f(T) { return } } // Recursion over signatures of each method. tmset := msets.MethodSet(T) for i := 0; i < tmset.Len(); i++ { sig := tmset.At(i).Type().(*types.Signature) // It is tempting to call visit(sig, false) // but, as noted in golang.org/cl/65450043, // the Signature.Recv field is ignored by // types.Identical and typeutil.Map, which // is confusing at best. // // More importantly, the true signature rtype // reachable from a method using reflection // has no receiver but an extra ordinary parameter. // For the Read method of io.Reader we want: // func(Reader, []byte) (int, error) // but here sig is: // func([]byte) (int, error) // with .Recv = Reader (though it is hard to // notice because it doesn't affect Signature.String // or types.Identical). // // TODO(adonovan): construct and visit the correct // non-method signature with an extra parameter // (though since unnamed func types have no methods // there is essentially no actual demand for this). // // TODO(adonovan): document whether or not it is // safe to skip non-exported methods (as RTA does). visit(sig.Params(), true) // skip the Tuple visit(sig.Results(), true) // skip the Tuple } switch T := T.(type) { case *aliases.Alias: visit(aliases.Unalias(T), skip) // emulates the pre-Alias behavior case *types.Basic: // nop case *types.Interface: // nop---handled by recursion over method set. case *types.Pointer: visit(T.Elem(), false) case *types.Slice: visit(T.Elem(), false) case *types.Chan: visit(T.Elem(), false) case *types.Map: visit(T.Key(), false) visit(T.Elem(), false) case *types.Signature: if T.Recv() != nil { panic(fmt.Sprintf("Signature %s has Recv %s", T, T.Recv())) } visit(T.Params(), true) // skip the Tuple visit(T.Results(), true) // skip the Tuple case *types.Named: // A pointer-to-named type can be derived from a named // type via reflection. It may have methods too. visit(types.NewPointer(T), false) // Consider 'type T struct{S}' where S has methods. // Reflection provides no way to get from T to struct{S}, // only to S, so the method set of struct{S} is unwanted, // so set 'skip' flag during recursion. visit(T.Underlying(), true) // skip the unnamed type case *types.Array: visit(T.Elem(), false) case *types.Struct: for i, n := 0, T.NumFields(); i < n; i++ { // TODO(adonovan): document whether or not // it is safe to skip non-exported fields. visit(T.Field(i).Type(), false) } case *types.Tuple: for i, n := 0, T.Len(); i < n; i++ { visit(T.At(i).Type(), false) } case *types.TypeParam, *types.Union: // forEachReachable must not be called on parameterized types. panic(T) default: panic(T) } } visit(T, false) }