summaryrefslogtreecommitdiff
path: root/vendor/golang.org/x/tools/go/ssa/source_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/tools/go/ssa/source_test.go')
-rw-r--r--vendor/golang.org/x/tools/go/ssa/source_test.go397
1 files changed, 397 insertions, 0 deletions
diff --git a/vendor/golang.org/x/tools/go/ssa/source_test.go b/vendor/golang.org/x/tools/go/ssa/source_test.go
new file mode 100644
index 0000000..43051f8
--- /dev/null
+++ b/vendor/golang.org/x/tools/go/ssa/source_test.go
@@ -0,0 +1,397 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssa_test
+
+// This file defines tests of source-level debugging utilities.
+
+import (
+ "fmt"
+ "go/ast"
+ exact "go/constant"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "os"
+ "regexp"
+ "runtime"
+ "strings"
+ "testing"
+
+ "golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/go/loader"
+ "golang.org/x/tools/go/ssa"
+ "golang.org/x/tools/go/ssa/ssautil"
+)
+
+func TestObjValueLookup(t *testing.T) {
+ if runtime.GOOS == "android" {
+ t.Skipf("no testdata directory on %s", runtime.GOOS)
+ }
+
+ conf := loader.Config{ParserMode: parser.ParseComments}
+ f, err := conf.ParseFile("testdata/objlookup.go", nil)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ conf.CreateFromFiles("main", f)
+
+ // Maps each var Ident (represented "name:linenum") to the
+ // kind of ssa.Value we expect (represented "Constant", "&Alloc").
+ expectations := make(map[string]string)
+
+ // Find all annotations of form x::BinOp, &y::Alloc, etc.
+ re := regexp.MustCompile(`(\b|&)?(\w*)::(\w*)\b`)
+ for _, c := range f.Comments {
+ text := c.Text()
+ pos := conf.Fset.Position(c.Pos())
+ for _, m := range re.FindAllStringSubmatch(text, -1) {
+ key := fmt.Sprintf("%s:%d", m[2], pos.Line)
+ value := m[1] + m[3]
+ expectations[key] = value
+ }
+ }
+
+ iprog, err := conf.Load()
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ prog := ssautil.CreateProgram(iprog, 0 /*|ssa.PrintFunctions*/)
+ mainInfo := iprog.Created[0]
+ mainPkg := prog.Package(mainInfo.Pkg)
+ mainPkg.SetDebugMode(true)
+ mainPkg.Build()
+
+ var varIds []*ast.Ident
+ var varObjs []*types.Var
+ for id, obj := range mainInfo.Defs {
+ // Check invariants for func and const objects.
+ switch obj := obj.(type) {
+ case *types.Func:
+ checkFuncValue(t, prog, obj)
+
+ case *types.Const:
+ checkConstValue(t, prog, obj)
+
+ case *types.Var:
+ if id.Name == "_" {
+ continue
+ }
+ varIds = append(varIds, id)
+ varObjs = append(varObjs, obj)
+ }
+ }
+ for id, obj := range mainInfo.Uses {
+ if obj, ok := obj.(*types.Var); ok {
+ varIds = append(varIds, id)
+ varObjs = append(varObjs, obj)
+ }
+ }
+
+ // Check invariants for var objects.
+ // The result varies based on the specific Ident.
+ for i, id := range varIds {
+ obj := varObjs[i]
+ ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
+ pos := prog.Fset.Position(id.Pos())
+ exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
+ if exp == "" {
+ t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
+ continue
+ }
+ wantAddr := false
+ if exp[0] == '&' {
+ wantAddr = true
+ exp = exp[1:]
+ }
+ checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr)
+ }
+}
+
+func checkFuncValue(t *testing.T, prog *ssa.Program, obj *types.Func) {
+ fn := prog.FuncValue(obj)
+ // fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging
+ if fn == nil {
+ if obj.Name() != "interfaceMethod" {
+ t.Errorf("FuncValue(%s) == nil", obj)
+ }
+ return
+ }
+ if fnobj := fn.Object(); fnobj != obj {
+ t.Errorf("FuncValue(%s).Object() == %s; value was %s",
+ obj, fnobj, fn.Name())
+ return
+ }
+ if !types.Identical(fn.Type(), obj.Type()) {
+ t.Errorf("FuncValue(%s).Type() == %s", obj, fn.Type())
+ return
+ }
+}
+
+func checkConstValue(t *testing.T, prog *ssa.Program, obj *types.Const) {
+ c := prog.ConstValue(obj)
+ // fmt.Printf("ConstValue(%s) = %s\n", obj, c) // debugging
+ if c == nil {
+ t.Errorf("ConstValue(%s) == nil", obj)
+ return
+ }
+ if !types.Identical(c.Type(), obj.Type()) {
+ t.Errorf("ConstValue(%s).Type() == %s", obj, c.Type())
+ return
+ }
+ if obj.Name() != "nil" {
+ if !exact.Compare(c.Value, token.EQL, obj.Val()) {
+ t.Errorf("ConstValue(%s).Value (%s) != %s",
+ obj, c.Value, obj.Val())
+ return
+ }
+ }
+}
+
+func checkVarValue(t *testing.T, prog *ssa.Program, pkg *ssa.Package, ref []ast.Node, obj *types.Var, expKind string, wantAddr bool) {
+ // The prefix of all assertions messages.
+ prefix := fmt.Sprintf("VarValue(%s @ L%d)",
+ obj, prog.Fset.Position(ref[0].Pos()).Line)
+
+ v, gotAddr := prog.VarValue(obj, pkg, ref)
+
+ // Kind is the concrete type of the ssa Value.
+ gotKind := "nil"
+ if v != nil {
+ gotKind = fmt.Sprintf("%T", v)[len("*ssa."):]
+ }
+
+ // fmt.Printf("%s = %v (kind %q; expect %q) wantAddr=%t gotAddr=%t\n", prefix, v, gotKind, expKind, wantAddr, gotAddr) // debugging
+
+ // Check the kinds match.
+ // "nil" indicates expected failure (e.g. optimized away).
+ if expKind != gotKind {
+ t.Errorf("%s concrete type == %s, want %s", prefix, gotKind, expKind)
+ }
+
+ // Check the types match.
+ // If wantAddr, the expected type is the object's address.
+ if v != nil {
+ expType := obj.Type()
+ if wantAddr {
+ expType = types.NewPointer(expType)
+ if !gotAddr {
+ t.Errorf("%s: got value, want address", prefix)
+ }
+ } else if gotAddr {
+ t.Errorf("%s: got address, want value", prefix)
+ }
+ if !types.Identical(v.Type(), expType) {
+ t.Errorf("%s.Type() == %s, want %s", prefix, v.Type(), expType)
+ }
+ }
+}
+
+// Ensure that, in debug mode, we can determine the ssa.Value
+// corresponding to every ast.Expr.
+func TestValueForExpr(t *testing.T) {
+ testValueForExpr(t, "testdata/valueforexpr.go")
+}
+
+func testValueForExpr(t *testing.T, testfile string) {
+ if runtime.GOOS == "android" {
+ t.Skipf("no testdata dir on %s", runtime.GOOS)
+ }
+
+ conf := loader.Config{ParserMode: parser.ParseComments}
+ f, err := conf.ParseFile(testfile, nil)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ conf.CreateFromFiles("main", f)
+
+ iprog, err := conf.Load()
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ mainInfo := iprog.Created[0]
+
+ prog := ssautil.CreateProgram(iprog, 0)
+ mainPkg := prog.Package(mainInfo.Pkg)
+ mainPkg.SetDebugMode(true)
+ mainPkg.Build()
+
+ if false {
+ // debugging
+ for _, mem := range mainPkg.Members {
+ if fn, ok := mem.(*ssa.Function); ok {
+ fn.WriteTo(os.Stderr)
+ }
+ }
+ }
+
+ // Find the actual AST node for each canonical position.
+ parenExprByPos := make(map[token.Pos]*ast.ParenExpr)
+ ast.Inspect(f, func(n ast.Node) bool {
+ if n != nil {
+ if e, ok := n.(*ast.ParenExpr); ok {
+ parenExprByPos[e.Pos()] = e
+ }
+ }
+ return true
+ })
+
+ // Find all annotations of form /*@kind*/.
+ for _, c := range f.Comments {
+ text := strings.TrimSpace(c.Text())
+ if text == "" || text[0] != '@' {
+ continue
+ }
+ text = text[1:]
+ pos := c.End() + 1
+ position := prog.Fset.Position(pos)
+ var e ast.Expr
+ if target := parenExprByPos[pos]; target == nil {
+ t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text)
+ continue
+ } else {
+ e = target.X
+ }
+
+ path, _ := astutil.PathEnclosingInterval(f, pos, pos)
+ if path == nil {
+ t.Errorf("%s: can't find AST path from root to comment: %s", position, text)
+ continue
+ }
+
+ fn := ssa.EnclosingFunction(mainPkg, path)
+ if fn == nil {
+ t.Errorf("%s: can't find enclosing function", position)
+ continue
+ }
+
+ v, gotAddr := fn.ValueForExpr(e) // (may be nil)
+ got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa.")
+ if want := text; got != want {
+ t.Errorf("%s: got value %q, want %q", position, got, want)
+ }
+ if v != nil {
+ T := v.Type()
+ if gotAddr {
+ T = T.Underlying().(*types.Pointer).Elem() // deref
+ }
+ if !types.Identical(T, mainInfo.TypeOf(e)) {
+ t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T)
+ }
+ }
+ }
+}
+
+// findInterval parses input and returns the [start, end) positions of
+// the first occurrence of substr in input. f==nil indicates failure;
+// an error has already been reported in that case.
+//
+func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) {
+ f, err := parser.ParseFile(fset, "<input>", input, 0)
+ if err != nil {
+ t.Errorf("parse error: %s", err)
+ return
+ }
+
+ i := strings.Index(input, substr)
+ if i < 0 {
+ t.Errorf("%q is not a substring of input", substr)
+ f = nil
+ return
+ }
+
+ filePos := fset.File(f.Package)
+ return f, filePos.Pos(i), filePos.Pos(i + len(substr))
+}
+
+func TestEnclosingFunction(t *testing.T) {
+ tests := []struct {
+ input string // the input file
+ substr string // first occurrence of this string denotes interval
+ fn string // name of expected containing function
+ }{
+ // We use distinctive numbers as syntactic landmarks.
+
+ // Ordinary function:
+ {`package main
+ func f() { println(1003) }`,
+ "100", "main.f"},
+ // Methods:
+ {`package main
+ type T int
+ func (t T) f() { println(200) }`,
+ "200", "(main.T).f"},
+ // Function literal:
+ {`package main
+ func f() { println(func() { print(300) }) }`,
+ "300", "main.f$1"},
+ // Doubly nested
+ {`package main
+ func f() { println(func() { print(func() { print(350) })})}`,
+ "350", "main.f$1$1"},
+ // Implicit init for package-level var initializer.
+ {"package main; var a = 400", "400", "main.init"},
+ // No code for constants:
+ {"package main; const a = 500", "500", "(none)"},
+ // Explicit init()
+ {"package main; func init() { println(600) }", "600", "main.init#1"},
+ // Multiple explicit init functions:
+ {`package main
+ func init() { println("foo") }
+ func init() { println(800) }`,
+ "800", "main.init#2"},
+ // init() containing FuncLit.
+ {`package main
+ func init() { println(func(){print(900)}) }`,
+ "900", "main.init#1$1"},
+ }
+ for _, test := range tests {
+ conf := loader.Config{Fset: token.NewFileSet()}
+ f, start, end := findInterval(t, conf.Fset, test.input, test.substr)
+ if f == nil {
+ continue
+ }
+ path, exact := astutil.PathEnclosingInterval(f, start, end)
+ if !exact {
+ t.Errorf("EnclosingFunction(%q) not exact", test.substr)
+ continue
+ }
+
+ conf.CreateFromFiles("main", f)
+
+ iprog, err := conf.Load()
+ if err != nil {
+ t.Error(err)
+ continue
+ }
+ prog := ssautil.CreateProgram(iprog, 0)
+ pkg := prog.Package(iprog.Created[0].Pkg)
+ pkg.Build()
+
+ name := "(none)"
+ fn := ssa.EnclosingFunction(pkg, path)
+ if fn != nil {
+ name = fn.String()
+ }
+
+ if name != test.fn {
+ t.Errorf("EnclosingFunction(%q in %q) got %s, want %s",
+ test.substr, test.input, name, test.fn)
+ continue
+ }
+
+ // While we're here: test HasEnclosingFunction.
+ if has := ssa.HasEnclosingFunction(pkg, path); has != (fn != nil) {
+ t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v",
+ test.substr, test.input, has, fn != nil)
+ continue
+ }
+ }
+}