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.
4
5//lint:file-ignore SA1019 go/ssa's test suite is built around the deprecated go/loader. We'll leave fixing that to upstream.
6
7package ir_test
8
9// This file defines tests of source-level debugging utilities.
10
11import (
12	"fmt"
13	"go/ast"
14	"go/constant"
15	"go/parser"
16	"go/token"
17	"go/types"
18	"io/ioutil"
19	"os"
20	"runtime"
21	"strings"
22	"testing"
23
24	"golang.org/x/tools/go/ast/astutil"
25	"golang.org/x/tools/go/expect"
26	"golang.org/x/tools/go/loader"
27	"honnef.co/go/tools/ir"
28	"honnef.co/go/tools/ir/irutil"
29)
30
31func TestObjValueLookup(t *testing.T) {
32	if runtime.GOOS == "android" {
33		t.Skipf("no testdata directory on %s", runtime.GOOS)
34	}
35
36	conf := loader.Config{ParserMode: parser.ParseComments}
37	src, err := ioutil.ReadFile("testdata/objlookup.go")
38	if err != nil {
39		t.Fatal(err)
40	}
41	readFile := func(filename string) ([]byte, error) { return src, nil }
42	f, err := conf.ParseFile("testdata/objlookup.go", src)
43	if err != nil {
44		t.Fatal(err)
45	}
46	conf.CreateFromFiles("main", f)
47
48	// Maps each var Ident (represented "name:linenum") to the
49	// kind of ir.Value we expect (represented "Constant", "&Alloc").
50	expectations := make(map[string]string)
51
52	// Each note of the form @ir(x, "BinOp") in testdata/objlookup.go
53	// specifies an expectation that an object named x declared on the
54	// same line is associated with an an ir.Value of type *ir.BinOp.
55	notes, err := expect.Extract(conf.Fset, f)
56	if err != nil {
57		t.Fatal(err)
58	}
59	for _, n := range notes {
60		if n.Name != "ir" {
61			t.Errorf("%v: unexpected note type %q, want \"ir\"", conf.Fset.Position(n.Pos), n.Name)
62			continue
63		}
64		if len(n.Args) != 2 {
65			t.Errorf("%v: ir has %d args, want 2", conf.Fset.Position(n.Pos), len(n.Args))
66			continue
67		}
68		ident, ok := n.Args[0].(expect.Identifier)
69		if !ok {
70			t.Errorf("%v: got %v for arg 1, want identifier", conf.Fset.Position(n.Pos), n.Args[0])
71			continue
72		}
73		exp, ok := n.Args[1].(string)
74		if !ok {
75			t.Errorf("%v: got %v for arg 2, want string", conf.Fset.Position(n.Pos), n.Args[1])
76			continue
77		}
78		p, _, err := expect.MatchBefore(conf.Fset, readFile, n.Pos, string(ident))
79		if err != nil {
80			t.Error(err)
81			continue
82		}
83		pos := conf.Fset.Position(p)
84		key := fmt.Sprintf("%s:%d", ident, pos.Line)
85		expectations[key] = exp
86	}
87
88	iprog, err := conf.Load()
89	if err != nil {
90		t.Error(err)
91		return
92	}
93
94	prog := irutil.CreateProgram(iprog, 0 /*|ir.PrintFunctions*/)
95	mainInfo := iprog.Created[0]
96	mainPkg := prog.Package(mainInfo.Pkg)
97	mainPkg.SetDebugMode(true)
98	mainPkg.Build()
99
100	var varIds []*ast.Ident
101	var varObjs []*types.Var
102	for id, obj := range mainInfo.Defs {
103		// Check invariants for func and const objects.
104		switch obj := obj.(type) {
105		case *types.Func:
106			checkFuncValue(t, prog, obj)
107
108		case *types.Const:
109			checkConstValue(t, prog, obj)
110
111		case *types.Var:
112			if id.Name == "_" {
113				continue
114			}
115			varIds = append(varIds, id)
116			varObjs = append(varObjs, obj)
117		}
118	}
119	for id, obj := range mainInfo.Uses {
120		if obj, ok := obj.(*types.Var); ok {
121			varIds = append(varIds, id)
122			varObjs = append(varObjs, obj)
123		}
124	}
125
126	// Check invariants for var objects.
127	// The result varies based on the specific Ident.
128	for i, id := range varIds {
129		obj := varObjs[i]
130		ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
131		pos := prog.Fset.Position(id.Pos())
132		exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
133		if exp == "" {
134			t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
135			continue
136		}
137		wantAddr := false
138		if exp[0] == '&' {
139			wantAddr = true
140			exp = exp[1:]
141		}
142		checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr)
143	}
144}
145
146func checkFuncValue(t *testing.T, prog *ir.Program, obj *types.Func) {
147	fn := prog.FuncValue(obj)
148	// fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging
149	if fn == nil {
150		if obj.Name() != "interfaceMethod" {
151			t.Errorf("FuncValue(%s) == nil", obj)
152		}
153		return
154	}
155	if fnobj := fn.Object(); fnobj != obj {
156		t.Errorf("FuncValue(%s).Object() == %s; value was %s",
157			obj, fnobj, fn.Name())
158		return
159	}
160	if !types.Identical(fn.Type(), obj.Type()) {
161		t.Errorf("FuncValue(%s).Type() == %s", obj, fn.Type())
162		return
163	}
164}
165
166func checkConstValue(t *testing.T, prog *ir.Program, obj *types.Const) {
167	c := prog.ConstValue(obj)
168	// fmt.Printf("ConstValue(%s) = %s\n", obj, c) // debugging
169	if c == nil {
170		t.Errorf("ConstValue(%s) == nil", obj)
171		return
172	}
173	if !types.Identical(c.Type(), obj.Type()) {
174		t.Errorf("ConstValue(%s).Type() == %s", obj, c.Type())
175		return
176	}
177	if obj.Name() != "nil" {
178		if !constant.Compare(c.Value, token.EQL, obj.Val()) {
179			t.Errorf("ConstValue(%s).Value (%s) != %s",
180				obj, c.Value, obj.Val())
181			return
182		}
183	}
184}
185
186func checkVarValue(t *testing.T, prog *ir.Program, pkg *ir.Package, ref []ast.Node, obj *types.Var, expKind string, wantAddr bool) {
187	// The prefix of all assertions messages.
188	prefix := fmt.Sprintf("VarValue(%s @ L%d)",
189		obj, prog.Fset.Position(ref[0].Pos()).Line)
190
191	v, gotAddr := prog.VarValue(obj, pkg, ref)
192
193	// Kind is the concrete type of the ir Value.
194	gotKind := "nil"
195	if v != nil {
196		gotKind = fmt.Sprintf("%T", v)[len("*ir."):]
197	}
198
199	// fmt.Printf("%s = %v (kind %q; expect %q) wantAddr=%t gotAddr=%t\n", prefix, v, gotKind, expKind, wantAddr, gotAddr) // debugging
200
201	// Check the kinds match.
202	// "nil" indicates expected failure (e.g. optimized away).
203	if expKind != gotKind {
204		t.Errorf("%s concrete type == %s, want %s", prefix, gotKind, expKind)
205	}
206
207	// Check the types match.
208	// If wantAddr, the expected type is the object's address.
209	if v != nil {
210		expType := obj.Type()
211		if wantAddr {
212			expType = types.NewPointer(expType)
213			if !gotAddr {
214				t.Errorf("%s: got value, want address", prefix)
215			}
216		} else if gotAddr {
217			t.Errorf("%s: got address, want value", prefix)
218		}
219		if !types.Identical(v.Type(), expType) {
220			t.Errorf("%s.Type() == %s, want %s", prefix, v.Type(), expType)
221		}
222	}
223}
224
225// Ensure that, in debug mode, we can determine the ir.Value
226// corresponding to every ast.Expr.
227func TestValueForExpr(t *testing.T) {
228	testValueForExpr(t, "testdata/valueforexpr.go")
229}
230
231func testValueForExpr(t *testing.T, testfile string) {
232	if runtime.GOOS == "android" {
233		t.Skipf("no testdata dir on %s", runtime.GOOS)
234	}
235
236	conf := loader.Config{ParserMode: parser.ParseComments}
237	f, err := conf.ParseFile(testfile, nil)
238	if err != nil {
239		t.Error(err)
240		return
241	}
242	conf.CreateFromFiles("main", f)
243
244	iprog, err := conf.Load()
245	if err != nil {
246		t.Error(err)
247		return
248	}
249
250	mainInfo := iprog.Created[0]
251
252	prog := irutil.CreateProgram(iprog, 0)
253	mainPkg := prog.Package(mainInfo.Pkg)
254	mainPkg.SetDebugMode(true)
255	mainPkg.Build()
256
257	if false {
258		// debugging
259		for _, mem := range mainPkg.Members {
260			if fn, ok := mem.(*ir.Function); ok {
261				fn.WriteTo(os.Stderr)
262			}
263		}
264	}
265
266	var parenExprs []*ast.ParenExpr
267	ast.Inspect(f, func(n ast.Node) bool {
268		if n != nil {
269			if e, ok := n.(*ast.ParenExpr); ok {
270				parenExprs = append(parenExprs, e)
271			}
272		}
273		return true
274	})
275
276	notes, err := expect.Extract(prog.Fset, f)
277	if err != nil {
278		t.Fatal(err)
279	}
280	for _, n := range notes {
281		want := n.Name
282		if want == "nil" {
283			want = "<nil>"
284		}
285		position := prog.Fset.Position(n.Pos)
286		var e ast.Expr
287		for _, paren := range parenExprs {
288			if paren.Pos() > n.Pos {
289				e = paren.X
290				break
291			}
292		}
293		if e == nil {
294			t.Errorf("%s: note doesn't precede ParenExpr: %q", position, want)
295			continue
296		}
297
298		path, _ := astutil.PathEnclosingInterval(f, n.Pos, n.Pos)
299		if path == nil {
300			t.Errorf("%s: can't find AST path from root to comment: %s", position, want)
301			continue
302		}
303
304		fn := ir.EnclosingFunction(mainPkg, path)
305		if fn == nil {
306			t.Errorf("%s: can't find enclosing function", position)
307			continue
308		}
309
310		v, gotAddr := fn.ValueForExpr(e) // (may be nil)
311		got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ir.")
312		if got != want {
313			t.Errorf("%s: got value %q, want %q", position, got, want)
314		}
315		if v != nil {
316			T := v.Type()
317			if gotAddr {
318				T = T.Underlying().(*types.Pointer).Elem() // deref
319			}
320			if !types.Identical(T, mainInfo.TypeOf(e)) {
321				t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T)
322			}
323		}
324	}
325}
326
327// findInterval parses input and returns the [start, end) positions of
328// the first occurrence of substr in input.  f==nil indicates failure;
329// an error has already been reported in that case.
330//
331func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) {
332	f, err := parser.ParseFile(fset, "<input>", input, 0)
333	if err != nil {
334		t.Errorf("parse error: %s", err)
335		return
336	}
337
338	i := strings.Index(input, substr)
339	if i < 0 {
340		t.Errorf("%q is not a substring of input", substr)
341		f = nil
342		return
343	}
344
345	filePos := fset.File(f.Package)
346	return f, filePos.Pos(i), filePos.Pos(i + len(substr))
347}
348
349func TestEnclosingFunction(t *testing.T) {
350	tests := []struct {
351		input  string // the input file
352		substr string // first occurrence of this string denotes interval
353		fn     string // name of expected containing function
354	}{
355		// We use distinctive numbers as syntactic landmarks.
356
357		// Ordinary function:
358		{`package main
359		  func f() { println(1003) }`,
360			"100", "main.f"},
361		// Methods:
362		{`package main
363                  type T int
364		  func (t T) f() { println(200) }`,
365			"200", "(main.T).f"},
366		// Function literal:
367		{`package main
368		  func f() { println(func() { print(300) }) }`,
369			"300", "main.f$1"},
370		// Doubly nested
371		{`package main
372		  func f() { println(func() { print(func() { print(350) })})}`,
373			"350", "main.f$1$1"},
374		// Implicit init for package-level var initializer.
375		{"package main; var a = 400", "400", "main.init"},
376		// No code for constants:
377		{"package main; const a = 500", "500", "(none)"},
378		// Explicit init()
379		{"package main; func init() { println(600) }", "600", "main.init#1"},
380		// Multiple explicit init functions:
381		{`package main
382		  func init() { println("foo") }
383		  func init() { println(800) }`,
384			"800", "main.init#2"},
385		// init() containing FuncLit.
386		{`package main
387		  func init() { println(func(){print(900)}) }`,
388			"900", "main.init#1$1"},
389	}
390	for _, test := range tests {
391		conf := loader.Config{Fset: token.NewFileSet()}
392		f, start, end := findInterval(t, conf.Fset, test.input, test.substr)
393		if f == nil {
394			continue
395		}
396		path, exact := astutil.PathEnclosingInterval(f, start, end)
397		if !exact {
398			t.Errorf("EnclosingFunction(%q) not exact", test.substr)
399			continue
400		}
401
402		conf.CreateFromFiles("main", f)
403
404		iprog, err := conf.Load()
405		if err != nil {
406			t.Error(err)
407			continue
408		}
409		prog := irutil.CreateProgram(iprog, 0)
410		pkg := prog.Package(iprog.Created[0].Pkg)
411		pkg.Build()
412
413		name := "(none)"
414		fn := ir.EnclosingFunction(pkg, path)
415		if fn != nil {
416			name = fn.String()
417		}
418
419		if name != test.fn {
420			t.Errorf("EnclosingFunction(%q in %q) got %s, want %s",
421				test.substr, test.input, name, test.fn)
422			continue
423		}
424
425		// While we're here: test HasEnclosingFunction.
426		if has := ir.HasEnclosingFunction(pkg, path); has != (fn != nil) {
427			t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v",
428				test.substr, test.input, has, fn != nil)
429			continue
430		}
431	}
432}
433