1// Copyright 2016 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
5package gcimporter_test
6
7import (
8	"fmt"
9	"go/ast"
10	"go/build"
11	"go/constant"
12	"go/parser"
13	"go/token"
14	"go/types"
15	"path/filepath"
16	"reflect"
17	"runtime"
18	"strings"
19	"testing"
20
21	"golang.org/x/tools/go/buildutil"
22	"golang.org/x/tools/go/internal/gcimporter"
23	"golang.org/x/tools/go/loader"
24)
25
26var isRace = false
27
28func TestBExportData_stdlib(t *testing.T) {
29	if runtime.Compiler == "gccgo" {
30		t.Skip("gccgo standard library is inaccessible")
31	}
32	if runtime.GOOS == "android" {
33		t.Skipf("incomplete std lib on %s", runtime.GOOS)
34	}
35	if isRace {
36		t.Skipf("stdlib tests take too long in race mode and flake on builders")
37	}
38
39	// Load, parse and type-check the program.
40	ctxt := build.Default // copy
41	ctxt.GOPATH = ""      // disable GOPATH
42	conf := loader.Config{
43		Build:       &ctxt,
44		AllowErrors: true,
45	}
46	for _, path := range buildutil.AllPackages(conf.Build) {
47		conf.Import(path)
48	}
49
50	// Create a package containing type and value errors to ensure
51	// they are properly encoded/decoded.
52	f, err := conf.ParseFile("haserrors/haserrors.go", `package haserrors
53const UnknownValue = "" + 0
54type UnknownType undefined
55`)
56	if err != nil {
57		t.Fatal(err)
58	}
59	conf.CreateFromFiles("haserrors", f)
60
61	prog, err := conf.Load()
62	if err != nil {
63		t.Fatalf("Load failed: %v", err)
64	}
65
66	numPkgs := len(prog.AllPackages)
67	if want := 248; numPkgs < want {
68		t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
69	}
70
71	for pkg, info := range prog.AllPackages {
72		if info.Files == nil {
73			continue // empty directory
74		}
75		exportdata, err := gcimporter.BExportData(conf.Fset, pkg)
76		if err != nil {
77			t.Fatal(err)
78		}
79
80		imports := make(map[string]*types.Package)
81		fset2 := token.NewFileSet()
82		n, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg.Path())
83		if err != nil {
84			t.Errorf("BImportData(%s): %v", pkg.Path(), err)
85			continue
86		}
87		if n != len(exportdata) {
88			t.Errorf("BImportData(%s) decoded %d bytes, want %d",
89				pkg.Path(), n, len(exportdata))
90		}
91
92		// Compare the packages' corresponding members.
93		for _, name := range pkg.Scope().Names() {
94			if !ast.IsExported(name) {
95				continue
96			}
97			obj1 := pkg.Scope().Lookup(name)
98			obj2 := pkg2.Scope().Lookup(name)
99			if obj2 == nil {
100				t.Errorf("%s.%s not found, want %s", pkg.Path(), name, obj1)
101				continue
102			}
103
104			fl1 := fileLine(conf.Fset, obj1)
105			fl2 := fileLine(fset2, obj2)
106			if fl1 != fl2 {
107				t.Errorf("%s.%s: got posn %s, want %s",
108					pkg.Path(), name, fl2, fl1)
109			}
110
111			if err := equalObj(obj1, obj2); err != nil {
112				t.Errorf("%s.%s: %s\ngot:  %s\nwant: %s",
113					pkg.Path(), name, err, obj2, obj1)
114			}
115		}
116	}
117}
118
119func fileLine(fset *token.FileSet, obj types.Object) string {
120	posn := fset.Position(obj.Pos())
121	filename := filepath.Clean(strings.ReplaceAll(posn.Filename, "$GOROOT", runtime.GOROOT()))
122	return fmt.Sprintf("%s:%d", filename, posn.Line)
123}
124
125// equalObj reports how x and y differ.  They are assumed to belong to
126// different universes so cannot be compared directly.
127func equalObj(x, y types.Object) error {
128	if reflect.TypeOf(x) != reflect.TypeOf(y) {
129		return fmt.Errorf("%T vs %T", x, y)
130	}
131	xt := x.Type()
132	yt := y.Type()
133	switch x.(type) {
134	case *types.Var, *types.Func:
135		// ok
136	case *types.Const:
137		xval := x.(*types.Const).Val()
138		yval := y.(*types.Const).Val()
139		// Use string comparison for floating-point values since rounding is permitted.
140		if constant.Compare(xval, token.NEQ, yval) &&
141			!(xval.Kind() == constant.Float && xval.String() == yval.String()) {
142			return fmt.Errorf("unequal constants %s vs %s", xval, yval)
143		}
144	case *types.TypeName:
145		xt = xt.Underlying()
146		yt = yt.Underlying()
147	default:
148		return fmt.Errorf("unexpected %T", x)
149	}
150	return equalType(xt, yt)
151}
152
153func equalType(x, y types.Type) error {
154	if reflect.TypeOf(x) != reflect.TypeOf(y) {
155		return fmt.Errorf("unequal kinds: %T vs %T", x, y)
156	}
157	switch x := x.(type) {
158	case *types.Interface:
159		y := y.(*types.Interface)
160		// TODO(gri): enable separate emission of Embedded interfaces
161		// and ExplicitMethods then use this logic.
162		// if x.NumEmbeddeds() != y.NumEmbeddeds() {
163		// 	return fmt.Errorf("unequal number of embedded interfaces: %d vs %d",
164		// 		x.NumEmbeddeds(), y.NumEmbeddeds())
165		// }
166		// for i := 0; i < x.NumEmbeddeds(); i++ {
167		// 	xi := x.Embedded(i)
168		// 	yi := y.Embedded(i)
169		// 	if xi.String() != yi.String() {
170		// 		return fmt.Errorf("mismatched %th embedded interface: %s vs %s",
171		// 			i, xi, yi)
172		// 	}
173		// }
174		// if x.NumExplicitMethods() != y.NumExplicitMethods() {
175		// 	return fmt.Errorf("unequal methods: %d vs %d",
176		// 		x.NumExplicitMethods(), y.NumExplicitMethods())
177		// }
178		// for i := 0; i < x.NumExplicitMethods(); i++ {
179		// 	xm := x.ExplicitMethod(i)
180		// 	ym := y.ExplicitMethod(i)
181		// 	if xm.Name() != ym.Name() {
182		// 		return fmt.Errorf("mismatched %th method: %s vs %s", i, xm, ym)
183		// 	}
184		// 	if err := equalType(xm.Type(), ym.Type()); err != nil {
185		// 		return fmt.Errorf("mismatched %s method: %s", xm.Name(), err)
186		// 	}
187		// }
188		if x.NumMethods() != y.NumMethods() {
189			return fmt.Errorf("unequal methods: %d vs %d",
190				x.NumMethods(), y.NumMethods())
191		}
192		for i := 0; i < x.NumMethods(); i++ {
193			xm := x.Method(i)
194			ym := y.Method(i)
195			if xm.Name() != ym.Name() {
196				return fmt.Errorf("mismatched %dth method: %s vs %s", i, xm, ym)
197			}
198			if err := equalType(xm.Type(), ym.Type()); err != nil {
199				return fmt.Errorf("mismatched %s method: %s", xm.Name(), err)
200			}
201		}
202	case *types.Array:
203		y := y.(*types.Array)
204		if x.Len() != y.Len() {
205			return fmt.Errorf("unequal array lengths: %d vs %d", x.Len(), y.Len())
206		}
207		if err := equalType(x.Elem(), y.Elem()); err != nil {
208			return fmt.Errorf("array elements: %s", err)
209		}
210	case *types.Basic:
211		y := y.(*types.Basic)
212		if x.Kind() != y.Kind() {
213			return fmt.Errorf("unequal basic types: %s vs %s", x, y)
214		}
215	case *types.Chan:
216		y := y.(*types.Chan)
217		if x.Dir() != y.Dir() {
218			return fmt.Errorf("unequal channel directions: %d vs %d", x.Dir(), y.Dir())
219		}
220		if err := equalType(x.Elem(), y.Elem()); err != nil {
221			return fmt.Errorf("channel elements: %s", err)
222		}
223	case *types.Map:
224		y := y.(*types.Map)
225		if err := equalType(x.Key(), y.Key()); err != nil {
226			return fmt.Errorf("map keys: %s", err)
227		}
228		if err := equalType(x.Elem(), y.Elem()); err != nil {
229			return fmt.Errorf("map values: %s", err)
230		}
231	case *types.Named:
232		y := y.(*types.Named)
233		if x.String() != y.String() {
234			return fmt.Errorf("unequal named types: %s vs %s", x, y)
235		}
236	case *types.Pointer:
237		y := y.(*types.Pointer)
238		if err := equalType(x.Elem(), y.Elem()); err != nil {
239			return fmt.Errorf("pointer elements: %s", err)
240		}
241	case *types.Signature:
242		y := y.(*types.Signature)
243		if err := equalType(x.Params(), y.Params()); err != nil {
244			return fmt.Errorf("parameters: %s", err)
245		}
246		if err := equalType(x.Results(), y.Results()); err != nil {
247			return fmt.Errorf("results: %s", err)
248		}
249		if x.Variadic() != y.Variadic() {
250			return fmt.Errorf("unequal variadicity: %t vs %t",
251				x.Variadic(), y.Variadic())
252		}
253		if (x.Recv() != nil) != (y.Recv() != nil) {
254			return fmt.Errorf("unequal receivers: %s vs %s", x.Recv(), y.Recv())
255		}
256		if x.Recv() != nil {
257			// TODO(adonovan): fix: this assertion fires for interface methods.
258			// The type of the receiver of an interface method is a named type
259			// if the Package was loaded from export data, or an unnamed (interface)
260			// type if the Package was produced by type-checking ASTs.
261			// if err := equalType(x.Recv().Type(), y.Recv().Type()); err != nil {
262			// 	return fmt.Errorf("receiver: %s", err)
263			// }
264		}
265	case *types.Slice:
266		y := y.(*types.Slice)
267		if err := equalType(x.Elem(), y.Elem()); err != nil {
268			return fmt.Errorf("slice elements: %s", err)
269		}
270	case *types.Struct:
271		y := y.(*types.Struct)
272		if x.NumFields() != y.NumFields() {
273			return fmt.Errorf("unequal struct fields: %d vs %d",
274				x.NumFields(), y.NumFields())
275		}
276		for i := 0; i < x.NumFields(); i++ {
277			xf := x.Field(i)
278			yf := y.Field(i)
279			if xf.Name() != yf.Name() {
280				return fmt.Errorf("mismatched fields: %s vs %s", xf, yf)
281			}
282			if err := equalType(xf.Type(), yf.Type()); err != nil {
283				return fmt.Errorf("struct field %s: %s", xf.Name(), err)
284			}
285			if x.Tag(i) != y.Tag(i) {
286				return fmt.Errorf("struct field %s has unequal tags: %q vs %q",
287					xf.Name(), x.Tag(i), y.Tag(i))
288			}
289		}
290	case *types.Tuple:
291		y := y.(*types.Tuple)
292		if x.Len() != y.Len() {
293			return fmt.Errorf("unequal tuple lengths: %d vs %d", x.Len(), y.Len())
294		}
295		for i := 0; i < x.Len(); i++ {
296			if err := equalType(x.At(i).Type(), y.At(i).Type()); err != nil {
297				return fmt.Errorf("tuple element %d: %s", i, err)
298			}
299		}
300	}
301	return nil
302}
303
304// TestVeryLongFile tests the position of an import object declared in
305// a very long input file.  Line numbers greater than maxlines are
306// reported as line 1, not garbage or token.NoPos.
307func TestVeryLongFile(t *testing.T) {
308	// parse and typecheck
309	longFile := "package foo" + strings.Repeat("\n", 123456) + "var X int"
310	fset1 := token.NewFileSet()
311	f, err := parser.ParseFile(fset1, "foo.go", longFile, 0)
312	if err != nil {
313		t.Fatal(err)
314	}
315	var conf types.Config
316	pkg, err := conf.Check("foo", fset1, []*ast.File{f}, nil)
317	if err != nil {
318		t.Fatal(err)
319	}
320
321	// export
322	exportdata, err := gcimporter.BExportData(fset1, pkg)
323	if err != nil {
324		t.Fatal(err)
325	}
326
327	// import
328	imports := make(map[string]*types.Package)
329	fset2 := token.NewFileSet()
330	_, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg.Path())
331	if err != nil {
332		t.Fatalf("BImportData(%s): %v", pkg.Path(), err)
333	}
334
335	// compare
336	posn1 := fset1.Position(pkg.Scope().Lookup("X").Pos())
337	posn2 := fset2.Position(pkg2.Scope().Lookup("X").Pos())
338	if want := "foo.go:1:1"; posn2.String() != want {
339		t.Errorf("X position = %s, want %s (orig was %s)",
340			posn2, want, posn1)
341	}
342}
343
344const src = `
345package p
346
347type (
348	T0 = int32
349	T1 = struct{}
350	T2 = struct{ T1 }
351	Invalid = foo // foo is undeclared
352)
353`
354
355func checkPkg(t *testing.T, pkg *types.Package, label string) {
356	T1 := types.NewStruct(nil, nil)
357	T2 := types.NewStruct([]*types.Var{types.NewField(0, pkg, "T1", T1, true)}, nil)
358
359	for _, test := range []struct {
360		name string
361		typ  types.Type
362	}{
363		{"T0", types.Typ[types.Int32]},
364		{"T1", T1},
365		{"T2", T2},
366		{"Invalid", types.Typ[types.Invalid]},
367	} {
368		obj := pkg.Scope().Lookup(test.name)
369		if obj == nil {
370			t.Errorf("%s: %s not found", label, test.name)
371			continue
372		}
373		tname, _ := obj.(*types.TypeName)
374		if tname == nil {
375			t.Errorf("%s: %v not a type name", label, obj)
376			continue
377		}
378		if !tname.IsAlias() {
379			t.Errorf("%s: %v: not marked as alias", label, tname)
380			continue
381		}
382		if got := tname.Type(); !types.Identical(got, test.typ) {
383			t.Errorf("%s: %v: got %v; want %v", label, tname, got, test.typ)
384		}
385	}
386}
387
388func TestTypeAliases(t *testing.T) {
389	// parse and typecheck
390	fset1 := token.NewFileSet()
391	f, err := parser.ParseFile(fset1, "p.go", src, 0)
392	if err != nil {
393		t.Fatal(err)
394	}
395	var conf types.Config
396	pkg1, err := conf.Check("p", fset1, []*ast.File{f}, nil)
397	if err == nil {
398		// foo in undeclared in src; we should see an error
399		t.Fatal("invalid source type-checked without error")
400	}
401	if pkg1 == nil {
402		// despite incorrect src we should see a (partially) type-checked package
403		t.Fatal("nil package returned")
404	}
405	checkPkg(t, pkg1, "export")
406
407	// export
408	exportdata, err := gcimporter.BExportData(fset1, pkg1)
409	if err != nil {
410		t.Fatal(err)
411	}
412
413	// import
414	imports := make(map[string]*types.Package)
415	fset2 := token.NewFileSet()
416	_, pkg2, err := gcimporter.BImportData(fset2, imports, exportdata, pkg1.Path())
417	if err != nil {
418		t.Fatalf("BImportData(%s): %v", pkg1.Path(), err)
419	}
420	checkPkg(t, pkg2, "import")
421}
422