mirror of
https://github.com/golang/go
synced 2024-11-19 01:14:39 -07:00
c28bf6e069
CanonicalPos was inadequate since many pairs of instruction share the same pos (e.g. Allocs and Phis). Instead, we generalize the DebugRef instruction to associate not just Idents but Exprs with ssa.Values. We no longer store any DebugRefs for constant expressions, to save space. (The type and value of such expressions can be obtained by other means, at a cost in complexity.) Function.ValueForExpr queries the DebugRef info to return the ssa.Value of a given Expr. Added tests. Also: - the DebugInfo flag is now per package, not global. It must be set between Create and Build phases if desired. - {Value,Instruction}.Pos() documentation updated: we still maintain this information in the instruction stream even in non-debug mode, but we make fewer claims about its invariants. - Go and Defer instructions can now use their respective go/defer token positions (not the call's lparen), so they do. - SelectState: Posn token.Pos indicates the <- position DebugNode ast.Expr is the send stmt or receive expr. - In building SelectStmt, we introduce extra temporaries in debug mode to hold the result of the receive in 'case <-ch' even though this value isn't ordinarily needed. - Use *SelectState (indirectly) since the struct is getting bigger. - Document some missing instructions in doc.go. R=gri CC=golang-dev https://golang.org/cl/12147043
145 lines
3.8 KiB
Go
145 lines
3.8 KiB
Go
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 promoted external declared method
|
|
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.Config)) // no Loader; uses GC importer
|
|
|
|
f, err := parser.ParseFile(imp.Fset, "<input>", test, parser.DeclarationErrors)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
info := imp.CreateSourcePackage("main", []*ast.File{f})
|
|
if info.Err != nil {
|
|
t.Error(info.Err.Error())
|
|
return
|
|
}
|
|
|
|
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
|
for _, info := range imp.Packages {
|
|
prog.CreatePackage(info)
|
|
}
|
|
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.Func("init")) {
|
|
t.Errorf("external package %s has non-empty init", pkg)
|
|
} else if !isExt && isEmpty(pkg.Func("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)
|
|
}
|
|
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++ {
|
|
m := prog.Method(mset.At(i))
|
|
// 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.common).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("call #%d 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)
|
|
}
|
|
}
|