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
9import (
10	"bytes"
11	"go/ast"
12	"go/importer"
13	"go/parser"
14	"go/token"
15	"go/types"
16	"os"
17	"reflect"
18	"sort"
19	"strings"
20	"testing"
21
22	"golang.org/x/tools/go/loader"
23	"honnef.co/go/tools/ir"
24	"honnef.co/go/tools/ir/irutil"
25)
26
27func isEmpty(f *ir.Function) bool { return f.Blocks == nil }
28
29// Tests that programs partially loaded from gc object files contain
30// functions with no code for the external portions, but are otherwise ok.
31func TestBuildPackage(t *testing.T) {
32	input := `
33package main
34
35import (
36	"bytes"
37	"io"
38	"testing"
39)
40
41func main() {
42	var t testing.T
43	t.Parallel()    // static call to external declared method
44	t.Fail()        // static call to promoted external declared method
45	testing.Short() // static call to external package-level function
46
47	var w io.Writer = new(bytes.Buffer)
48	w.Write(nil)    // interface invoke of external declared method
49}
50`
51
52	// Parse the file.
53	fset := token.NewFileSet()
54	f, err := parser.ParseFile(fset, "input.go", input, 0)
55	if err != nil {
56		t.Error(err)
57		return
58	}
59
60	// Build an IR program from the parsed file.
61	// Load its dependencies from gc binary export data.
62	mainPkg, _, err := irutil.BuildPackage(&types.Config{Importer: importer.Default()}, fset,
63		types.NewPackage("main", ""), []*ast.File{f}, ir.SanityCheckFunctions)
64	if err != nil {
65		t.Error(err)
66		return
67	}
68
69	// The main package, its direct and indirect dependencies are loaded.
70	deps := []string{
71		// directly imported dependencies:
72		"bytes", "io", "testing",
73		// indirect dependencies mentioned by
74		// the direct imports' export data
75		"sync", "unicode", "time",
76	}
77
78	prog := mainPkg.Prog
79	all := prog.AllPackages()
80	if len(all) <= len(deps) {
81		t.Errorf("unexpected set of loaded packages: %q", all)
82	}
83	for _, path := range deps {
84		pkg := prog.ImportedPackage(path)
85		if pkg == nil {
86			t.Errorf("package not loaded: %q", path)
87			continue
88		}
89
90		// External packages should have no function bodies (except for wrappers).
91		isExt := pkg != mainPkg
92
93		// init()
94		if isExt && !isEmpty(pkg.Func("init")) {
95			t.Errorf("external package %s has non-empty init", pkg)
96		} else if !isExt && isEmpty(pkg.Func("init")) {
97			t.Errorf("main package %s has empty init", pkg)
98		}
99
100		for _, mem := range pkg.Members {
101			switch mem := mem.(type) {
102			case *ir.Function:
103				// Functions at package level.
104				if isExt && !isEmpty(mem) {
105					t.Errorf("external function %s is non-empty", mem)
106				} else if !isExt && isEmpty(mem) {
107					t.Errorf("function %s is empty", mem)
108				}
109
110			case *ir.Type:
111				// Methods of named types T.
112				// (In this test, all exported methods belong to *T not T.)
113				if !isExt {
114					t.Fatalf("unexpected name type in main package: %s", mem)
115				}
116				mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type()))
117				for i, n := 0, mset.Len(); i < n; i++ {
118					m := prog.MethodValue(mset.At(i))
119					// For external types, only synthetic wrappers have code.
120					expExt := !strings.Contains(m.Synthetic, "wrapper")
121					if expExt && !isEmpty(m) {
122						t.Errorf("external method %s is non-empty: %s",
123							m, m.Synthetic)
124					} else if !expExt && isEmpty(m) {
125						t.Errorf("method function %s is empty: %s",
126							m, m.Synthetic)
127					}
128				}
129			}
130		}
131	}
132
133	expectedCallee := []string{
134		"(*testing.T).Parallel",
135		"(*testing.common).Fail",
136		"testing.Short",
137		"N/A",
138	}
139	callNum := 0
140	for _, b := range mainPkg.Func("main").Blocks {
141		for _, instr := range b.Instrs {
142			switch instr := instr.(type) {
143			case ir.CallInstruction:
144				call := instr.Common()
145				if want := expectedCallee[callNum]; want != "N/A" {
146					got := call.StaticCallee().String()
147					if want != got {
148						t.Errorf("call #%d from main.main: got callee %s, want %s",
149							callNum, got, want)
150					}
151				}
152				callNum++
153			}
154		}
155	}
156	if callNum != 4 {
157		t.Errorf("in main.main: got %d calls, want %d", callNum, 4)
158	}
159}
160
161// TestRuntimeTypes tests that (*Program).RuntimeTypes() includes all necessary types.
162func TestRuntimeTypes(t *testing.T) {
163	tests := []struct {
164		input string
165		want  []string
166	}{
167		// An exported package-level type is needed.
168		{`package A; type T struct{}; func (T) f() {}`,
169			[]string{"*p.T", "p.T"},
170		},
171		// An unexported package-level type is not needed.
172		{`package B; type t struct{}; func (t) f() {}`,
173			nil,
174		},
175		// Subcomponents of type of exported package-level var are needed.
176		{`package C; import "bytes"; var V struct {*bytes.Buffer}`,
177			[]string{"*bytes.Buffer", "*struct{*bytes.Buffer}", "struct{*bytes.Buffer}"},
178		},
179		// Subcomponents of type of unexported package-level var are not needed.
180		{`package D; import "bytes"; var v struct {*bytes.Buffer}`,
181			nil,
182		},
183		// Subcomponents of type of exported package-level function are needed.
184		{`package E; import "bytes"; func F(struct {*bytes.Buffer}) {}`,
185			[]string{"*bytes.Buffer", "struct{*bytes.Buffer}"},
186		},
187		// Subcomponents of type of unexported package-level function are not needed.
188		{`package F; import "bytes"; func f(struct {*bytes.Buffer}) {}`,
189			nil,
190		},
191		// Subcomponents of type of exported method of uninstantiated unexported type are not needed.
192		{`package G; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v x`,
193			nil,
194		},
195		// ...unless used by MakeInterface.
196		{`package G2; import "bytes"; type x struct{}; func (x) G(struct {*bytes.Buffer}) {}; var v interface{} = x{}`,
197			[]string{"*bytes.Buffer", "*p.x", "p.x", "struct{*bytes.Buffer}"},
198		},
199		// Subcomponents of type of unexported method are not needed.
200		{`package I; import "bytes"; type X struct{}; func (X) G(struct {*bytes.Buffer}) {}`,
201			[]string{"*bytes.Buffer", "*p.X", "p.X", "struct{*bytes.Buffer}"},
202		},
203		// Local types aren't needed.
204		{`package J; import "bytes"; func f() { type T struct {*bytes.Buffer}; var t T; _ = t }`,
205			nil,
206		},
207		// ...unless used by MakeInterface.
208		{`package K; import "bytes"; func f() { type T struct {*bytes.Buffer}; _ = interface{}(T{}) }`,
209			[]string{"*bytes.Buffer", "*p.T", "p.T"},
210		},
211		// Types used as operand of MakeInterface are needed.
212		{`package L; import "bytes"; func f() { _ = interface{}(struct{*bytes.Buffer}{}) }`,
213			[]string{"*bytes.Buffer", "struct{*bytes.Buffer}"},
214		},
215		// MakeInterface is optimized away when storing to a blank.
216		{`package M; import "bytes"; var _ interface{} = struct{*bytes.Buffer}{}`,
217			nil,
218		},
219	}
220	for _, test := range tests {
221		// Parse the file.
222		fset := token.NewFileSet()
223		f, err := parser.ParseFile(fset, "input.go", test.input, 0)
224		if err != nil {
225			t.Errorf("test %q: %s", test.input[:15], err)
226			continue
227		}
228
229		// Create a single-file main package.
230		// Load dependencies from gc binary export data.
231		irpkg, _, err := irutil.BuildPackage(&types.Config{Importer: importer.Default()}, fset,
232			types.NewPackage("p", ""), []*ast.File{f}, ir.SanityCheckFunctions)
233		if err != nil {
234			t.Errorf("test %q: %s", test.input[:15], err)
235			continue
236		}
237
238		var typstrs []string
239		for _, T := range irpkg.Prog.RuntimeTypes() {
240			typstrs = append(typstrs, T.String())
241		}
242		sort.Strings(typstrs)
243
244		if !reflect.DeepEqual(typstrs, test.want) {
245			t.Errorf("test 'package %s': got %q, want %q",
246				f.Name.Name, typstrs, test.want)
247		}
248	}
249}
250
251// TestInit tests that synthesized init functions are correctly formed.
252func TestInit(t *testing.T) {
253	tests := []struct {
254		mode        ir.BuilderMode
255		input, want string
256	}{
257		{0, `package A; import _ "errors"; var i int = 42`,
258			`# Name: A.init
259# Package: A
260# Synthetic: package initializer
261func init():
262b0: # entry
263	t1 = Const <bool> {true}
264	t2 = Const <int> {42}
265	t3 = Load <bool> init$guard
266	If t3b1 b2
267
268b1: ← b0 b2 # exit
269	Return
270
271b2: ← b0 # init.start
272	Store {bool} init$guard t1
273	t7 = Call <()> errors.init
274	Store {int} i t2
275	Jumpb1
276
277`},
278	}
279	for _, test := range tests {
280		// Create a single-file main package.
281		var conf loader.Config
282		f, err := conf.ParseFile("<input>", test.input)
283		if err != nil {
284			t.Errorf("test %q: %s", test.input[:15], err)
285			continue
286		}
287		conf.CreateFromFiles(f.Name.Name, f)
288
289		lprog, err := conf.Load()
290		if err != nil {
291			t.Errorf("test 'package %s': Load: %s", f.Name.Name, err)
292			continue
293		}
294		prog := irutil.CreateProgram(lprog, test.mode)
295		mainPkg := prog.Package(lprog.Created[0].Pkg)
296		prog.Build()
297		initFunc := mainPkg.Func("init")
298		if initFunc == nil {
299			t.Errorf("test 'package %s': no init function", f.Name.Name)
300			continue
301		}
302
303		var initbuf bytes.Buffer
304		_, err = initFunc.WriteTo(&initbuf)
305		if err != nil {
306			t.Errorf("test 'package %s': WriteTo: %s", f.Name.Name, err)
307			continue
308		}
309
310		if initbuf.String() != test.want {
311			t.Errorf("test 'package %s': got %s, want %s", f.Name.Name, initbuf.String(), test.want)
312		}
313	}
314}
315
316// TestSyntheticFuncs checks that the expected synthetic functions are
317// created, reachable, and not duplicated.
318func TestSyntheticFuncs(t *testing.T) {
319	const input = `package P
320type T int
321func (T) f() int
322func (*T) g() int
323var (
324	// thunks
325	a = T.f
326	b = T.f
327	c = (struct{T}).f
328	d = (struct{T}).f
329	e = (*T).g
330	f = (*T).g
331	g = (struct{*T}).g
332	h = (struct{*T}).g
333
334	// bounds
335	i = T(0).f
336	j = T(0).f
337	k = new(T).g
338	l = new(T).g
339
340	// wrappers
341	m interface{} = struct{T}{}
342	n interface{} = struct{T}{}
343	o interface{} = struct{*T}{}
344	p interface{} = struct{*T}{}
345	q interface{} = new(struct{T})
346	r interface{} = new(struct{T})
347	s interface{} = new(struct{*T})
348	t interface{} = new(struct{*T})
349)
350`
351	// Parse
352	var conf loader.Config
353	f, err := conf.ParseFile("<input>", input)
354	if err != nil {
355		t.Fatalf("parse: %v", err)
356	}
357	conf.CreateFromFiles(f.Name.Name, f)
358
359	// Load
360	lprog, err := conf.Load()
361	if err != nil {
362		t.Fatalf("Load: %v", err)
363	}
364
365	// Create and build IR
366	prog := irutil.CreateProgram(lprog, 0)
367	prog.Build()
368
369	// Enumerate reachable synthetic functions
370	want := map[string]string{
371		"(*P.T).g$bound": "bound method wrapper for func (*P.T).g() int",
372		"(P.T).f$bound":  "bound method wrapper for func (P.T).f() int",
373
374		"(*P.T).g$thunk":         "thunk for func (*P.T).g() int",
375		"(P.T).f$thunk":          "thunk for func (P.T).f() int",
376		"(struct{*P.T}).g$thunk": "thunk for func (*P.T).g() int",
377		"(struct{P.T}).f$thunk":  "thunk for func (P.T).f() int",
378
379		"(*P.T).f":          "wrapper for func (P.T).f() int",
380		"(*struct{*P.T}).f": "wrapper for func (P.T).f() int",
381		"(*struct{*P.T}).g": "wrapper for func (*P.T).g() int",
382		"(*struct{P.T}).f":  "wrapper for func (P.T).f() int",
383		"(*struct{P.T}).g":  "wrapper for func (*P.T).g() int",
384		"(struct{*P.T}).f":  "wrapper for func (P.T).f() int",
385		"(struct{*P.T}).g":  "wrapper for func (*P.T).g() int",
386		"(struct{P.T}).f":   "wrapper for func (P.T).f() int",
387
388		"P.init": "package initializer",
389	}
390	for fn := range irutil.AllFunctions(prog) {
391		if fn.Synthetic == "" {
392			continue
393		}
394		name := fn.String()
395		wantDescr, ok := want[name]
396		if !ok {
397			t.Errorf("got unexpected/duplicate func: %q: %q", name, fn.Synthetic)
398			continue
399		}
400		delete(want, name)
401
402		if wantDescr != fn.Synthetic {
403			t.Errorf("(%s).Synthetic = %q, want %q", name, fn.Synthetic, wantDescr)
404		}
405	}
406	for fn, descr := range want {
407		t.Errorf("want func: %q: %q", fn, descr)
408	}
409}
410
411// TestPhiElimination ensures that dead phis, including those that
412// participate in a cycle, are properly eliminated.
413func TestPhiElimination(t *testing.T) {
414	const input = `
415package p
416
417func f() error
418
419func g(slice []int) {
420	for {
421		for range slice {
422			// e should not be lifted to a dead φ-node.
423			e := f()
424			h(e)
425		}
426	}
427}
428
429func h(error)
430`
431	// The IR code for this function should look something like this:
432	// 0:
433	//         jump 1
434	// 1:
435	//         t0 = len(slice)
436	//         jump 2
437	// 2:
438	//         t1 = phi [1: -1:int, 3: t2]
439	//         t2 = t1 + 1:int
440	//         t3 = t2 < t0
441	//         if t3 goto 3 else 1
442	// 3:
443	//         t4 = f()
444	//         t5 = h(t4)
445	//         jump 2
446	//
447	// But earlier versions of the IR construction algorithm would
448	// additionally generate this cycle of dead phis:
449	//
450	// 1:
451	//         t7 = phi [0: nil:error, 2: t8] #e
452	//         ...
453	// 2:
454	//         t8 = phi [1: t7, 3: t4] #e
455	//         ...
456
457	// Parse
458	var conf loader.Config
459	f, err := conf.ParseFile("<input>", input)
460	if err != nil {
461		t.Fatalf("parse: %v", err)
462	}
463	conf.CreateFromFiles("p", f)
464
465	// Load
466	lprog, err := conf.Load()
467	if err != nil {
468		t.Fatalf("Load: %v", err)
469	}
470
471	// Create and build IR
472	prog := irutil.CreateProgram(lprog, 0)
473	p := prog.Package(lprog.Package("p").Pkg)
474	p.Build()
475	g := p.Func("g")
476
477	phis := 0
478	for _, b := range g.Blocks {
479		for _, instr := range b.Instrs {
480			if _, ok := instr.(*ir.Phi); ok {
481				phis++
482			}
483		}
484	}
485	if expected := 1; phis != expected {
486		g.WriteTo(os.Stderr)
487		t.Errorf("expected %d Phi nodes (for the range index), got %d", expected, phis)
488	}
489}
490