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:
Alan Donovan 2013-07-10 18:08:42 -04:00
parent 4df74776da
commit e783d2d666
4 changed files with 176 additions and 26 deletions

135
ssa/builder_test.go Normal file
View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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