go.tools/ssa: added test of loading of partial programs (via gcimporter).
This exposed a bug: we weren't creating Functions for methods of imported named types. Also: - simplification: 'candidate' no longer contains 'concrete *Function'. We look this up on demand. NB: this CL contains a copy of CL 10935047 (use of typemap); will submit/sync/resolve soon. R=gri CC=golang-dev https://golang.org/cl/11051043
This commit is contained in:
parent
4df74776da
commit
e783d2d666
|
|
@ -0,0 +1,135 @@
|
|||
package ssa_test
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
"code.google.com/p/go.tools/importer"
|
||||
"code.google.com/p/go.tools/ssa"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func isEmpty(f *ssa.Function) bool { return f.Blocks == nil }
|
||||
|
||||
// Tests that programs partially loaded from gc object files contain
|
||||
// functions with no code for the external portions, but are otherwise ok.
|
||||
func TestExternalPackages(t *testing.T) {
|
||||
test := `
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var t testing.T
|
||||
t.Parallel() // static call to external declared method
|
||||
t.Fail() // static call to synthetic promotion wrapper
|
||||
testing.Short() // static call to external package-level function
|
||||
|
||||
var w io.Writer = new(bytes.Buffer)
|
||||
w.Write(nil) // interface invoke of external declared method
|
||||
}
|
||||
`
|
||||
imp := importer.New(new(importer.Context)) // no Loader; uses GC importer
|
||||
|
||||
f, err := parser.ParseFile(imp.Fset, "<input>", test, parser.DeclarationErrors)
|
||||
if err != nil {
|
||||
t.Errorf("parse error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
info, err := imp.CreateSourcePackage("main", []*ast.File{f})
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
||||
prog.CreatePackages(imp)
|
||||
mainPkg := prog.Package(info.Pkg)
|
||||
mainPkg.Build()
|
||||
|
||||
// Only the main package and its immediate dependencies are loaded.
|
||||
deps := []string{"bytes", "io", "testing"}
|
||||
if len(prog.PackagesByPath) != 1+len(deps) {
|
||||
t.Errorf("unexpected set of loaded packages: %q", prog.PackagesByPath)
|
||||
}
|
||||
for _, path := range deps {
|
||||
pkg, _ := prog.PackagesByPath[path]
|
||||
if pkg == nil {
|
||||
t.Errorf("package not loaded: %q", path)
|
||||
continue
|
||||
}
|
||||
|
||||
// External packages should have no function bodies (except for wrappers).
|
||||
isExt := pkg != mainPkg
|
||||
|
||||
// init()
|
||||
if isExt && !isEmpty(pkg.Init) {
|
||||
t.Errorf("external package %s has non-empty init", pkg)
|
||||
} else if !isExt && isEmpty(pkg.Init) {
|
||||
t.Errorf("main package %s has empty init", pkg)
|
||||
}
|
||||
|
||||
for _, mem := range pkg.Members {
|
||||
switch mem := mem.(type) {
|
||||
case *ssa.Function:
|
||||
// Functions at package level.
|
||||
if isExt && !isEmpty(mem) {
|
||||
t.Errorf("external function %s is non-empty", mem)
|
||||
} else if !isExt && isEmpty(mem) {
|
||||
t.Errorf("function %s is empty", mem)
|
||||
}
|
||||
|
||||
case *ssa.Type:
|
||||
// Methods of named types T.
|
||||
// (In this test, all exported methods belong to *T not T.)
|
||||
if !isExt {
|
||||
t.Fatalf("unexpected name type in main package: %s", mem)
|
||||
}
|
||||
for _, m := range prog.MethodSet(types.NewPointer(mem.Type())) {
|
||||
// For external types, only synthetic wrappers have code.
|
||||
expExt := !strings.Contains(m.Synthetic, "wrapper")
|
||||
if expExt && !isEmpty(m) {
|
||||
t.Errorf("external method %s is non-empty: %s",
|
||||
m, m.Synthetic)
|
||||
} else if !expExt && isEmpty(m) {
|
||||
t.Errorf("method function %s is empty: %s",
|
||||
m, m.Synthetic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expectedCallee := []string{
|
||||
"(*testing.T).Parallel",
|
||||
"(*testing.T).Fail",
|
||||
"testing.Short",
|
||||
"N/A",
|
||||
}
|
||||
callNum := 0
|
||||
for _, b := range mainPkg.Func("main").Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case ssa.CallInstruction:
|
||||
call := instr.Common()
|
||||
if want := expectedCallee[callNum]; want != "N/A" {
|
||||
got := call.StaticCallee().String()
|
||||
if want != got {
|
||||
t.Errorf("%dth call from main.main: got callee %s, want %s",
|
||||
callNum, got, want)
|
||||
}
|
||||
}
|
||||
callNum++
|
||||
}
|
||||
}
|
||||
}
|
||||
if callNum != 4 {
|
||||
t.Errorf("in main.main: got %d calls, want %d", callNum, 4)
|
||||
}
|
||||
}
|
||||
|
|
@ -234,7 +234,18 @@ func createPackage(prog *Program, importPath string, info *importer.PackageInfo)
|
|||
// No position information.
|
||||
scope := p.Object.Scope()
|
||||
for i, n := 0, scope.NumEntries(); i < n; i++ {
|
||||
memberFromObject(p, scope.At(i), nil)
|
||||
obj := scope.At(i)
|
||||
if obj, ok := obj.(*types.TypeName); ok {
|
||||
mset := types.NewMethodSet(obj.Type())
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
memberFromObject(p, mset.At(i), nil)
|
||||
}
|
||||
mset = types.NewMethodSet(types.NewPointer(obj.Type()))
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
memberFromObject(p, mset.At(i), nil)
|
||||
}
|
||||
}
|
||||
memberFromObject(p, obj, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,9 +76,8 @@ func (p *anonFieldPath) isIndirect() bool {
|
|||
// type's method-set.
|
||||
//
|
||||
type candidate struct {
|
||||
method *types.Func // method object of abstract or concrete type
|
||||
concrete *Function // actual method (iff concrete)
|
||||
path *anonFieldPath // desugared selector path
|
||||
method *types.Func // method object of abstract or concrete type
|
||||
path *anonFieldPath // desugared selector path
|
||||
}
|
||||
|
||||
func (c candidate) String() string {
|
||||
|
|
@ -90,9 +89,16 @@ func (c candidate) String() string {
|
|||
return s + "." + c.method.Name()
|
||||
}
|
||||
|
||||
// ptrRecv returns true if this candidate has a pointer receiver.
|
||||
func (c candidate) isConcrete() bool {
|
||||
return c.method.Type().(*types.Signature).Recv() != nil
|
||||
}
|
||||
|
||||
// ptrRecv returns true if this candidate is a concrete method with a
|
||||
// pointer receiver.
|
||||
//
|
||||
func (c candidate) ptrRecv() bool {
|
||||
return c.concrete != nil && isPointer(c.concrete.Signature.Recv().Type())
|
||||
recv := c.method.Type().(*types.Signature).Recv()
|
||||
return recv != nil && isPointer(recv.Type())
|
||||
}
|
||||
|
||||
// MethodSet returns the method set for type typ, building wrapper
|
||||
|
|
@ -101,6 +107,7 @@ func (c candidate) ptrRecv() bool {
|
|||
// A nil result indicates an empty set.
|
||||
//
|
||||
// Thread-safe.
|
||||
//
|
||||
func (p *Program) MethodSet(typ types.Type) MethodSet {
|
||||
if !canHaveConcreteMethods(typ, true) {
|
||||
return nil
|
||||
|
|
@ -120,9 +127,11 @@ func (p *Program) MethodSet(typ types.Type) MethodSet {
|
|||
// buildMethodSet computes the concrete method set for type typ.
|
||||
// It is the implementation of Program.MethodSet.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(meth.Prog.methodsMu)
|
||||
//
|
||||
func buildMethodSet(prog *Program, typ types.Type) MethodSet {
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("buildMethodSet %s %T", typ, typ)()
|
||||
defer logStack("buildMethodSet %s", typ)()
|
||||
}
|
||||
|
||||
// cands maps ids (field and method names) encountered at any
|
||||
|
|
@ -151,12 +160,7 @@ func buildMethodSet(prog *Program, typ types.Type) MethodSet {
|
|||
|
||||
if nt, ok := t.(*types.Named); ok {
|
||||
for i, n := 0, nt.NumMethods(); i < n; i++ {
|
||||
m := nt.Method(i)
|
||||
concrete := prog.concreteMethods[m]
|
||||
if concrete == nil {
|
||||
panic(fmt.Sprintf("no ssa.Function for methodset(%s)[%s]", t, m.Name()))
|
||||
}
|
||||
addCandidate(nextcands, MakeId(m.Name(), m.Pkg()), m, concrete, node)
|
||||
addCandidate(nextcands, nt.Method(i), node)
|
||||
}
|
||||
t = nt.Underlying()
|
||||
}
|
||||
|
|
@ -164,8 +168,7 @@ func buildMethodSet(prog *Program, typ types.Type) MethodSet {
|
|||
switch t := t.(type) {
|
||||
case *types.Interface:
|
||||
for i, n := 0, t.NumMethods(); i < n; i++ {
|
||||
m := t.Method(i)
|
||||
addCandidate(nextcands, MakeId(m.Name(), m.Pkg()), m, nil, node)
|
||||
addCandidate(nextcands, t.Method(i), node)
|
||||
}
|
||||
|
||||
case *types.Struct:
|
||||
|
|
@ -218,7 +221,7 @@ func buildMethodSet(prog *Program, typ types.Type) MethodSet {
|
|||
var method *Function
|
||||
if cand.path == nil {
|
||||
// Trivial member of method-set; no promotion needed.
|
||||
method = cand.concrete
|
||||
method = prog.concreteMethods[cand.method]
|
||||
|
||||
if !cand.ptrRecv() && isPointer(typ) {
|
||||
// Call to method on T from receiver of type *T.
|
||||
|
|
@ -235,11 +238,11 @@ func buildMethodSet(prog *Program, typ types.Type) MethodSet {
|
|||
return mset
|
||||
}
|
||||
|
||||
// addCandidate adds the promotion candidate (method, node) to m[id].
|
||||
// If m[id] already exists (whether nil or not), m[id] is set to nil.
|
||||
// If method denotes a concrete method, concrete is its implementation.
|
||||
// addCandidate adds the promotion candidate (method, node) to m[(name, package)].
|
||||
// If a map entry already exists (whether nil or not), its value is set to nil.
|
||||
//
|
||||
func addCandidate(m map[Id]*candidate, id Id, method *types.Func, concrete *Function, node *anonFieldPath) {
|
||||
func addCandidate(m map[Id]*candidate, method *types.Func, node *anonFieldPath) {
|
||||
id := MakeId(method.Name(), method.Pkg())
|
||||
prev, found := m[id]
|
||||
switch {
|
||||
case prev != nil:
|
||||
|
|
@ -249,7 +252,7 @@ func addCandidate(m map[Id]*candidate, id Id, method *types.Func, concrete *Func
|
|||
// Already blocked.
|
||||
default:
|
||||
// A viable candidate.
|
||||
m[id] = &candidate{method, concrete, node}
|
||||
m[id] = &candidate{method, node}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +274,8 @@ func addCandidate(m map[Id]*candidate, id Id, method *types.Func, concrete *Func
|
|||
// candidate method to be promoted; it may be concrete or an interface
|
||||
// method.
|
||||
//
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(meth.Prog.methodsMu)
|
||||
//
|
||||
func promotionWrapper(prog *Program, typ types.Type, cand *candidate) *Function {
|
||||
old := cand.method.Type().(*types.Signature)
|
||||
sig := types.NewSignature(types.NewVar(token.NoPos, nil, "recv", typ), old.Params(), old.Results(), old.IsVariadic())
|
||||
|
|
@ -321,8 +326,8 @@ func promotionWrapper(prog *Program, typ types.Type, cand *candidate) *Function
|
|||
}
|
||||
|
||||
var c Call
|
||||
if cand.concrete != nil {
|
||||
c.Call.Func = cand.concrete
|
||||
if cand.isConcrete() {
|
||||
c.Call.Func = prog.concreteMethods[cand.method]
|
||||
c.Call.Args = append(c.Call.Args, v)
|
||||
} else {
|
||||
iface := v.Type().Underlying().(*types.Interface)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ type Package struct {
|
|||
Object *types.Package // the type checker's package object for this package
|
||||
Members map[string]Member // all package members keyed by name
|
||||
values map[types.Object]Value // package-level vars and funcs, keyed by object
|
||||
Init *Function // the package's (concatenated) init function
|
||||
Init *Function // the package's (concatenated) init function [TODO use Func("init")]
|
||||
|
||||
// The following fields are set transiently, then cleared
|
||||
// after building.
|
||||
|
|
@ -379,8 +379,7 @@ type Parameter struct {
|
|||
// constants.
|
||||
//
|
||||
// Pos() returns the canonical position (see CanonicalPos) of the
|
||||
// originating constant expression (ast.Ident or ast.BasicLit, or
|
||||
// ast.{Call,Selector,Unary,Binary}Expr), if explicit in the source.
|
||||
// originating constant expression, if explicit in the source.
|
||||
//
|
||||
// Example printed form:
|
||||
// 42:int
|
||||
|
|
|
|||
Loading…
Reference in New Issue