1 // Copyright 2013 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 //lint:file-ignore SA1019 go/ssa's test suite is built around the deprecated go/loader. We'll leave fixing that to upstream.
9 // This file defines tests of source-level debugging utilities.
24 "honnef.co/go/tools/go/ir"
25 "honnef.co/go/tools/go/ir/irutil"
27 "golang.org/x/tools/go/ast/astutil"
28 "golang.org/x/tools/go/expect"
29 "golang.org/x/tools/go/loader"
32 func TestObjValueLookup(t *testing.T) {
33 if runtime.GOOS == "android" {
34 t.Skipf("no testdata directory on %s", runtime.GOOS)
37 conf := loader.Config{ParserMode: parser.ParseComments}
38 src, err := ioutil.ReadFile("testdata/objlookup.go")
42 readFile := func(filename string) ([]byte, error) { return src, nil }
43 f, err := conf.ParseFile("testdata/objlookup.go", src)
47 conf.CreateFromFiles("main", f)
49 // Maps each var Ident (represented "name:linenum") to the
50 // kind of ir.Value we expect (represented "Constant", "&Alloc").
51 expectations := make(map[string]string)
53 // Each note of the form @ir(x, "BinOp") in testdata/objlookup.go
54 // specifies an expectation that an object named x declared on the
55 // same line is associated with an an ir.Value of type *ir.BinOp.
56 notes, err := expect.ExtractGo(conf.Fset, f)
60 for _, n := range notes {
62 t.Errorf("%v: unexpected note type %q, want \"ir\"", conf.Fset.Position(n.Pos), n.Name)
66 t.Errorf("%v: ir has %d args, want 2", conf.Fset.Position(n.Pos), len(n.Args))
69 ident, ok := n.Args[0].(expect.Identifier)
71 t.Errorf("%v: got %v for arg 1, want identifier", conf.Fset.Position(n.Pos), n.Args[0])
74 exp, ok := n.Args[1].(string)
76 t.Errorf("%v: got %v for arg 2, want string", conf.Fset.Position(n.Pos), n.Args[1])
79 p, _, err := expect.MatchBefore(conf.Fset, readFile, n.Pos, string(ident))
84 pos := conf.Fset.Position(p)
85 key := fmt.Sprintf("%s:%d", ident, pos.Line)
86 expectations[key] = exp
89 iprog, err := conf.Load()
95 prog := irutil.CreateProgram(iprog, 0 /*|ir.PrintFunctions*/)
96 mainInfo := iprog.Created[0]
97 mainPkg := prog.Package(mainInfo.Pkg)
98 mainPkg.SetDebugMode(true)
101 var varIds []*ast.Ident
102 var varObjs []*types.Var
103 for id, obj := range mainInfo.Defs {
104 // Check invariants for func and const objects.
105 switch obj := obj.(type) {
107 checkFuncValue(t, prog, obj)
110 checkConstValue(t, prog, obj)
116 varIds = append(varIds, id)
117 varObjs = append(varObjs, obj)
120 for id, obj := range mainInfo.Uses {
121 if obj, ok := obj.(*types.Var); ok {
122 varIds = append(varIds, id)
123 varObjs = append(varObjs, obj)
127 // Check invariants for var objects.
128 // The result varies based on the specific Ident.
129 for i, id := range varIds {
131 ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
132 pos := prog.Fset.Position(id.Pos())
133 exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
135 t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
143 checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr)
147 func checkFuncValue(t *testing.T, prog *ir.Program, obj *types.Func) {
148 fn := prog.FuncValue(obj)
149 // fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging
151 if obj.Name() != "interfaceMethod" {
152 t.Errorf("FuncValue(%s) == nil", obj)
156 if fnobj := fn.Object(); fnobj != obj {
157 t.Errorf("FuncValue(%s).Object() == %s; value was %s",
158 obj, fnobj, fn.Name())
161 if !types.Identical(fn.Type(), obj.Type()) {
162 t.Errorf("FuncValue(%s).Type() == %s", obj, fn.Type())
167 func checkConstValue(t *testing.T, prog *ir.Program, obj *types.Const) {
168 c := prog.ConstValue(obj)
169 // fmt.Printf("ConstValue(%s) = %s\n", obj, c) // debugging
171 t.Errorf("ConstValue(%s) == nil", obj)
174 if !types.Identical(c.Type(), obj.Type()) {
175 t.Errorf("ConstValue(%s).Type() == %s", obj, c.Type())
178 if obj.Name() != "nil" {
179 if !constant.Compare(c.Value, token.EQL, obj.Val()) {
180 t.Errorf("ConstValue(%s).Value (%s) != %s",
181 obj, c.Value, obj.Val())
187 func checkVarValue(t *testing.T, prog *ir.Program, pkg *ir.Package, ref []ast.Node, obj *types.Var, expKind string, wantAddr bool) {
188 // The prefix of all assertions messages.
189 prefix := fmt.Sprintf("VarValue(%s @ L%d)",
190 obj, prog.Fset.Position(ref[0].Pos()).Line)
192 v, gotAddr := prog.VarValue(obj, pkg, ref)
194 // Kind is the concrete type of the ir Value.
197 gotKind = fmt.Sprintf("%T", v)[len("*ir."):]
200 // fmt.Printf("%s = %v (kind %q; expect %q) wantAddr=%t gotAddr=%t\n", prefix, v, gotKind, expKind, wantAddr, gotAddr) // debugging
202 // Check the kinds match.
203 // "nil" indicates expected failure (e.g. optimized away).
204 if expKind != gotKind {
205 t.Errorf("%s concrete type == %s, want %s", prefix, gotKind, expKind)
208 // Check the types match.
209 // If wantAddr, the expected type is the object's address.
211 expType := obj.Type()
213 expType = types.NewPointer(expType)
215 t.Errorf("%s: got value, want address", prefix)
218 t.Errorf("%s: got address, want value", prefix)
220 if !types.Identical(v.Type(), expType) {
221 t.Errorf("%s.Type() == %s, want %s", prefix, v.Type(), expType)
226 // Ensure that, in debug mode, we can determine the ir.Value
227 // corresponding to every ast.Expr.
228 func TestValueForExpr(t *testing.T) {
229 testValueForExpr(t, "testdata/valueforexpr.go")
232 func testValueForExpr(t *testing.T, testfile string) {
233 if runtime.GOOS == "android" {
234 t.Skipf("no testdata dir on %s", runtime.GOOS)
237 conf := loader.Config{ParserMode: parser.ParseComments}
238 f, err := conf.ParseFile(testfile, nil)
243 conf.CreateFromFiles("main", f)
245 iprog, err := conf.Load()
251 mainInfo := iprog.Created[0]
253 prog := irutil.CreateProgram(iprog, 0)
254 mainPkg := prog.Package(mainInfo.Pkg)
255 mainPkg.SetDebugMode(true)
260 for _, mem := range mainPkg.Members {
261 if fn, ok := mem.(*ir.Function); ok {
262 fn.WriteTo(os.Stderr)
267 var parenExprs []*ast.ParenExpr
268 ast.Inspect(f, func(n ast.Node) bool {
270 if e, ok := n.(*ast.ParenExpr); ok {
271 parenExprs = append(parenExprs, e)
277 notes, err := expect.ExtractGo(prog.Fset, f)
281 for _, n := range notes {
286 position := prog.Fset.Position(n.Pos)
288 for _, paren := range parenExprs {
289 if paren.Pos() > n.Pos {
295 t.Errorf("%s: note doesn't precede ParenExpr: %q", position, want)
299 path, _ := astutil.PathEnclosingInterval(f, n.Pos, n.Pos)
301 t.Errorf("%s: can't find AST path from root to comment: %s", position, want)
305 fn := ir.EnclosingFunction(mainPkg, path)
307 t.Errorf("%s: can't find enclosing function", position)
311 v, gotAddr := fn.ValueForExpr(e) // (may be nil)
312 got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ir.")
314 t.Errorf("%s: got value %q, want %q", position, got, want)
319 T = T.Underlying().(*types.Pointer).Elem() // deref
321 if !types.Identical(T, mainInfo.TypeOf(e)) {
322 t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T)
328 // findInterval parses input and returns the [start, end) positions of
329 // the first occurrence of substr in input. f==nil indicates failure;
330 // an error has already been reported in that case.
332 func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) {
333 f, err := parser.ParseFile(fset, "<input>", input, 0)
335 t.Errorf("parse error: %s", err)
339 i := strings.Index(input, substr)
341 t.Errorf("%q is not a substring of input", substr)
346 filePos := fset.File(f.Package)
347 return f, filePos.Pos(i), filePos.Pos(i + len(substr))
350 func TestEnclosingFunction(t *testing.T) {
352 input string // the input file
353 substr string // first occurrence of this string denotes interval
354 fn string // name of expected containing function
356 // We use distinctive numbers as syntactic landmarks.
358 // Ordinary function:
360 func f() { println(1003) }`,
365 func (t T) f() { println(200) }`,
366 "200", "(main.T).f"},
369 func f() { println(func() { print(300) }) }`,
373 func f() { println(func() { print(func() { print(350) })})}`,
374 "350", "main.f$1$1"},
375 // Implicit init for package-level var initializer.
376 {"package main; var a = 400", "400", "main.init"},
377 // No code for constants:
378 {"package main; const a = 500", "500", "(none)"},
380 {"package main; func init() { println(600) }", "600", "main.init#1"},
381 // Multiple explicit init functions:
383 func init() { println("foo") }
384 func init() { println(800) }`,
385 "800", "main.init#2"},
386 // init() containing FuncLit.
388 func init() { println(func(){print(900)}) }`,
389 "900", "main.init#1$1"},
391 for _, test := range tests {
392 conf := loader.Config{Fset: token.NewFileSet()}
393 f, start, end := findInterval(t, conf.Fset, test.input, test.substr)
397 path, exact := astutil.PathEnclosingInterval(f, start, end)
399 t.Errorf("EnclosingFunction(%q) not exact", test.substr)
403 conf.CreateFromFiles("main", f)
405 iprog, err := conf.Load()
410 prog := irutil.CreateProgram(iprog, 0)
411 pkg := prog.Package(iprog.Created[0].Pkg)
415 fn := ir.EnclosingFunction(pkg, path)
421 t.Errorf("EnclosingFunction(%q in %q) got %s, want %s",
422 test.substr, test.input, name, test.fn)
426 // While we're here: test HasEnclosingFunction.
427 if has := ir.HasEnclosingFunction(pkg, path); has != (fn != nil) {
428 t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v",
429 test.substr, test.input, has, fn != nil)