2013-07-10 16:08:42 -06:00
|
|
|
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
|
2013-07-26 09:22:34 -06:00
|
|
|
t.Fail() // static call to promoted external declared method
|
2013-07-10 16:08:42 -06:00
|
|
|
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
|
|
|
|
}
|
|
|
|
`
|
2013-07-19 09:02:27 -06:00
|
|
|
imp := importer.New(new(importer.Config)) // no Loader; uses GC importer
|
2013-07-10 16:08:42 -06:00
|
|
|
|
|
|
|
f, err := parser.ParseFile(imp.Fset, "<input>", test, parser.DeclarationErrors)
|
|
|
|
if err != nil {
|
2013-07-31 11:13:05 -06:00
|
|
|
t.Error(err)
|
2013-07-10 16:08:42 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2013-07-18 14:59:06 -06:00
|
|
|
info := imp.CreateSourcePackage("main", []*ast.File{f})
|
|
|
|
if info.Err != nil {
|
|
|
|
t.Error(info.Err.Error())
|
2013-07-10 16:08:42 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
2013-07-18 14:59:06 -06:00
|
|
|
for _, info := range imp.Packages {
|
|
|
|
prog.CreatePackage(info)
|
|
|
|
}
|
2013-07-10 16:08:42 -06:00
|
|
|
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()
|
2013-07-10 23:14:40 -06:00
|
|
|
if isExt && !isEmpty(pkg.Func("init")) {
|
2013-07-10 16:08:42 -06:00
|
|
|
t.Errorf("external package %s has non-empty init", pkg)
|
2013-07-10 23:14:40 -06:00
|
|
|
} else if !isExt && isEmpty(pkg.Func("init")) {
|
2013-07-10 16:08:42 -06:00
|
|
|
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)
|
|
|
|
}
|
2013-07-30 12:28:14 -06:00
|
|
|
if _, ok := mem.Type().Underlying().(*types.Interface); ok {
|
|
|
|
// TODO(adonovan): workaround bug in types.MethodSet
|
|
|
|
// whereby mset(*I) is nonempty if I is an interface.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
mset := types.NewPointer(mem.Type()).MethodSet()
|
|
|
|
for i, n := 0, mset.Len(); i < n; i++ {
|
2013-07-30 14:36:58 -06:00
|
|
|
m := prog.Method(mset.At(i))
|
2013-07-10 16:08:42 -06:00
|
|
|
// 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",
|
2013-07-26 09:22:34 -06:00
|
|
|
"(*testing.common).Fail",
|
2013-07-10 16:08:42 -06:00
|
|
|
"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 {
|
2013-07-26 09:22:34 -06:00
|
|
|
t.Errorf("call #%d from main.main: got callee %s, want %s",
|
2013-07-10 16:08:42 -06:00
|
|
|
callNum, got, want)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
callNum++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if callNum != 4 {
|
|
|
|
t.Errorf("in main.main: got %d calls, want %d", callNum, 4)
|
|
|
|
}
|
|
|
|
}
|