From e783d2d666f42e3b4a56ca06337e2154587d270b Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Wed, 10 Jul 2013 18:08:42 -0400 Subject: [PATCH] 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 --- ssa/builder_test.go | 135 ++++++++++++++++++++++++++++++++++++++++++++ ssa/create.go | 13 ++++- ssa/promote.go | 49 ++++++++-------- ssa/ssa.go | 5 +- 4 files changed, 176 insertions(+), 26 deletions(-) create mode 100644 ssa/builder_test.go diff --git a/ssa/builder_test.go b/ssa/builder_test.go new file mode 100644 index 00000000..de451c30 --- /dev/null +++ b/ssa/builder_test.go @@ -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, "", 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) + } +} diff --git a/ssa/create.go b/ssa/create.go index 2f709512..b0933f9f 100644 --- a/ssa/create.go +++ b/ssa/create.go @@ -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) } } diff --git a/ssa/promote.go b/ssa/promote.go index 538265a8..8d5b58a1 100644 --- a/ssa/promote.go +++ b/ssa/promote.go @@ -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) diff --git a/ssa/ssa.go b/ssa/ssa.go index fc841eda..b5705426 100644 --- a/ssa/ssa.go +++ b/ssa/ssa.go @@ -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