mirror of
https://github.com/golang/go
synced 2024-11-18 22:14:56 -07:00
3f2f9a7e70
Motivation: pointer analysis tools (like the oracle) want the user to specify a set of initial packages, like 'go test'. This change enables the user to specify a set of packages on the command line using importer.LoadInitialPackages(args). Each argument is interpreted as either: - a comma-separated list of *.go source files together comprising one non-importable ad-hoc package. e.g. "src/pkg/net/http/triv.go" gives us [main]. - an import path, denoting both the imported package and its non-importable external test package, if any. e.g. "fmt" gives us [fmt, fmt_test]. Current type-checker limitations mean that only the first import path may contribute tests: multiple packages augmented by *_test.go files could create import cycles, which 'go test' avoids by building a separate executable for each one. That approach is less attractive for static analysis. Details: (many files touched, but importer.go is the crux) importer: - PackageInfo.Importable boolean indicates whether package is importable. - un-expose Importer.Packages; expose AllPackages() instead. - CreatePackageFromArgs has become LoadInitialPackages. - imports() moved to util.go, renamed importsOf(). - InitialPackagesUsage usage message exported to clients. - the package name for ad-hoc packages now comes from the 'package' decl, not "main". ssa.Program: - added CreatePackages() method - PackagesByPath un-exposed, renamed 'imported'. - expose AllPackages and ImportedPackage accessors. oracle: - describe: explain and workaround a go/types bug. Misc: - Removed various unnecessary error.Error() calls in Printf args. R=crawshaw CC=golang-dev https://golang.org/cl/13579043
254 lines
6.5 KiB
Go
254 lines
6.5 KiB
Go
// 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"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"code.google.com/p/go.tools/go/exact"
|
|
"code.google.com/p/go.tools/go/types"
|
|
"code.google.com/p/go.tools/importer"
|
|
"code.google.com/p/go.tools/ssa"
|
|
)
|
|
|
|
func TestObjValueLookup(t *testing.T) {
|
|
imp := importer.New(new(importer.Config)) // (uses GCImporter)
|
|
f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.DeclarationErrors|parser.ParseComments)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
// 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 := imp.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
|
|
}
|
|
}
|
|
|
|
mainInfo := imp.LoadMainPackage(f)
|
|
|
|
prog := ssa.NewProgram(imp.Fset, 0 /*|ssa.LogFunctions*/)
|
|
if err := prog.CreatePackages(imp); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
mainPkg := prog.Package(mainInfo.Pkg)
|
|
mainPkg.SetDebugMode(true)
|
|
mainPkg.Build()
|
|
|
|
// Gather all idents and objects in file.
|
|
objs := make(map[types.Object]bool)
|
|
var ids []*ast.Ident
|
|
ast.Inspect(f, func(n ast.Node) bool {
|
|
if id, ok := n.(*ast.Ident); ok {
|
|
ids = append(ids, id)
|
|
if obj := mainInfo.ObjectOf(id); obj != nil {
|
|
objs[obj] = true
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
|
|
// Check invariants for func and const objects.
|
|
for obj := range objs {
|
|
switch obj := obj.(type) {
|
|
case *types.Func:
|
|
checkFuncValue(t, prog, obj)
|
|
|
|
case *types.Const:
|
|
checkConstValue(t, prog, obj)
|
|
}
|
|
}
|
|
|
|
// Check invariants for var objects.
|
|
// The result varies based on the specific Ident.
|
|
for _, id := range ids {
|
|
if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok {
|
|
ref, _ := importer.PathEnclosingInterval(f, id.Pos(), id.Pos())
|
|
pos := imp.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) {
|
|
v := prog.FuncValue(obj)
|
|
// fmt.Printf("FuncValue(%s) = %s\n", obj, v) // debugging
|
|
if v == nil {
|
|
t.Errorf("FuncValue(%s) == nil", obj)
|
|
return
|
|
}
|
|
// v must be an *ssa.Function or *ssa.Builtin.
|
|
v2, _ := v.(interface {
|
|
Object() types.Object
|
|
})
|
|
if v2 == nil {
|
|
t.Errorf("FuncValue(%s) = %s %T; has no Object() method",
|
|
obj, v.Name(), v)
|
|
return
|
|
}
|
|
if vobj := v2.Object(); vobj != obj {
|
|
t.Errorf("FuncValue(%s).Object() == %s; value was %s",
|
|
obj, vobj, v.Name())
|
|
return
|
|
}
|
|
if !types.IsIdentical(v.Type(), obj.Type()) {
|
|
t.Errorf("FuncValue(%s).Type() == %s", obj, v.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.IsIdentical(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 := 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) addr=%t\n", prefix, v, gotKind, expKind, wantAddr) // 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 !types.IsIdentical(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) {
|
|
imp := importer.New(new(importer.Config)) // (uses GCImporter)
|
|
f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil,
|
|
parser.DeclarationErrors|parser.ParseComments)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
mainInfo := imp.LoadMainPackage(f)
|
|
|
|
prog := ssa.NewProgram(imp.Fset, 0)
|
|
if err := prog.CreatePackages(imp); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
mainPkg := prog.Package(mainInfo.Pkg)
|
|
mainPkg.SetDebugMode(true)
|
|
mainPkg.Build()
|
|
|
|
fn := mainPkg.Func("f")
|
|
|
|
if false {
|
|
fn.DumpTo(os.Stderr) // debugging
|
|
}
|
|
|
|
// 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 := imp.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
|
|
}
|
|
|
|
v := 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 {
|
|
if !types.IsIdentical(v.Type(), mainInfo.TypeOf(e)) {
|
|
t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), v.Type())
|
|
}
|
|
}
|
|
}
|
|
}
|