1
2
3
4
5
6
7
8
9
10 package gcimporter_test
11
12 import (
13 "bufio"
14 "bytes"
15 "fmt"
16 "go/ast"
17 "go/build"
18 "go/constant"
19 "go/parser"
20 "go/token"
21 "go/types"
22 "io"
23 "math/big"
24 "os"
25 "reflect"
26 "runtime"
27 "sort"
28 "strings"
29 "testing"
30
31 "golang.org/x/tools/go/ast/inspector"
32 "golang.org/x/tools/go/buildutil"
33 "golang.org/x/tools/go/gcexportdata"
34 "golang.org/x/tools/go/loader"
35 "golang.org/x/tools/internal/aliases"
36 "golang.org/x/tools/internal/gcimporter"
37 "golang.org/x/tools/internal/testenv"
38 "golang.org/x/tools/internal/typeparams/genericfeatures"
39 )
40
41 func readExportFile(filename string) ([]byte, error) {
42 f, err := os.Open(filename)
43 if err != nil {
44 return nil, err
45 }
46 defer f.Close()
47
48 buf := bufio.NewReader(f)
49 if _, _, err := gcimporter.FindExportData(buf); err != nil {
50 return nil, err
51 }
52
53 if ch, err := buf.ReadByte(); err != nil {
54 return nil, err
55 } else if ch != 'i' {
56 return nil, fmt.Errorf("unexpected byte: %v", ch)
57 }
58
59 return io.ReadAll(buf)
60 }
61
62 func iexport(fset *token.FileSet, version int, pkg *types.Package) ([]byte, error) {
63 var buf bytes.Buffer
64 const bundle, shallow = false, false
65 if err := gcimporter.IExportCommon(&buf, fset, bundle, shallow, version, []*types.Package{pkg}); err != nil {
66 return nil, err
67 }
68 return buf.Bytes(), nil
69 }
70
71
72
73 func isUnifiedBuilder() bool {
74 return os.Getenv("GO_BUILDER_NAME") == "linux-amd64-unified"
75 }
76
77 const minStdlibPackages = 248
78
79 func TestIExportData_stdlib(t *testing.T) {
80 if runtime.Compiler == "gccgo" {
81 t.Skip("gccgo standard library is inaccessible")
82 }
83 testenv.NeedsGoBuild(t)
84 if isRace {
85 t.Skipf("stdlib tests take too long in race mode and flake on builders")
86 }
87 if testing.Short() {
88 t.Skip("skipping RAM hungry test in -short mode")
89 }
90
91
92 ctxt := build.Default
93 ctxt.GOPATH = ""
94 conf := loader.Config{
95 Build: &ctxt,
96 AllowErrors: true,
97 TypeChecker: types.Config{
98 Sizes: types.SizesFor(ctxt.Compiler, ctxt.GOARCH),
99 Error: func(err error) { t.Log(err) },
100 },
101 }
102 for _, path := range buildutil.AllPackages(conf.Build) {
103 conf.Import(path)
104 }
105
106
107
108 f, err := conf.ParseFile("haserrors/haserrors.go", `package haserrors
109 const UnknownValue = "" + 0
110 type UnknownType undefined
111 `)
112 if err != nil {
113 t.Fatal(err)
114 }
115 conf.CreateFromFiles("haserrors", f)
116
117 prog, err := conf.Load()
118 if err != nil {
119 t.Fatalf("Load failed: %v", err)
120 }
121
122 var sorted []*types.Package
123 isUnified := isUnifiedBuilder()
124 for pkg, info := range prog.AllPackages {
125
126
127
128
129 inspect := inspector.New(info.Files)
130 features := genericfeatures.ForPackage(inspect, &info.Info)
131 if isUnified && features != 0 {
132 t.Logf("skipping package %q which uses generics", pkg.Path())
133 continue
134 }
135 if info.Files != nil {
136 sorted = append(sorted, pkg)
137 }
138 }
139 sort.Slice(sorted, func(i, j int) bool {
140 return sorted[i].Path() < sorted[j].Path()
141 })
142
143 version := gcimporter.IExportVersion
144 numPkgs := len(sorted)
145 if want := minStdlibPackages; numPkgs < want {
146 t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
147 }
148
149
150 for _, pkg := range sorted {
151 if exportdata, err := iexport(conf.Fset, version, pkg); err != nil {
152 t.Error(err)
153 } else {
154 testPkgData(t, conf.Fset, version, pkg, exportdata)
155 }
156
157 if pkg.Name() == "main" || pkg.Name() == "haserrors" {
158
159 } else if bp, err := ctxt.Import(pkg.Path(), "", build.FindOnly); err != nil {
160 t.Log("warning:", err)
161 } else if exportdata, err := readExportFile(bp.PkgObj); err != nil {
162 t.Log("warning:", err)
163 } else {
164 testPkgData(t, conf.Fset, version, pkg, exportdata)
165 }
166 }
167
168 var bundle bytes.Buffer
169 if err := gcimporter.IExportBundle(&bundle, conf.Fset, sorted); err != nil {
170 t.Fatal(err)
171 }
172 fset2 := token.NewFileSet()
173 imports := make(map[string]*types.Package)
174 pkgs2, err := gcimporter.IImportBundle(fset2, imports, bundle.Bytes())
175 if err != nil {
176 t.Fatal(err)
177 }
178
179 for i, pkg := range sorted {
180 testPkg(t, conf.Fset, version, pkg, fset2, pkgs2[i])
181 }
182 }
183
184 func testPkgData(t *testing.T, fset *token.FileSet, version int, pkg *types.Package, exportdata []byte) {
185 imports := make(map[string]*types.Package)
186 fset2 := token.NewFileSet()
187 _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path())
188 if err != nil {
189 t.Errorf("IImportData(%s): %v", pkg.Path(), err)
190 }
191
192 testPkg(t, fset, version, pkg, fset2, pkg2)
193 }
194
195 func testPkg(t *testing.T, fset *token.FileSet, version int, pkg *types.Package, fset2 *token.FileSet, pkg2 *types.Package) {
196 if _, err := iexport(fset2, version, pkg2); err != nil {
197 t.Errorf("reexport %q: %v", pkg.Path(), err)
198 }
199
200
201 for _, name := range pkg.Scope().Names() {
202 if !token.IsExported(name) {
203 continue
204 }
205 obj1 := pkg.Scope().Lookup(name)
206 obj2 := pkg2.Scope().Lookup(name)
207 if obj2 == nil {
208 t.Errorf("%s.%s not found, want %s", pkg.Path(), name, obj1)
209 continue
210 }
211
212 fl1 := fileLine(fset, obj1)
213 fl2 := fileLine(fset2, obj2)
214 if fl1 != fl2 {
215 t.Errorf("%s.%s: got posn %s, want %s",
216 pkg.Path(), name, fl2, fl1)
217 }
218
219 if err := cmpObj(obj1, obj2); err != nil {
220 t.Errorf("%s.%s: %s\ngot: %s\nwant: %s",
221 pkg.Path(), name, err, obj2, obj1)
222 }
223 }
224 }
225
226
227
228
229 func TestIExportData_long(t *testing.T) {
230
231 longFile := "package foo" + strings.Repeat("\n", 123456) + "var X int"
232 fset1 := token.NewFileSet()
233 f, err := parser.ParseFile(fset1, "foo.go", longFile, 0)
234 if err != nil {
235 t.Fatal(err)
236 }
237 var conf types.Config
238 pkg, err := conf.Check("foo", fset1, []*ast.File{f}, nil)
239 if err != nil {
240 t.Fatal(err)
241 }
242
243
244 exportdata, err := iexport(fset1, gcimporter.IExportVersion, pkg)
245 if err != nil {
246 t.Fatal(err)
247 }
248
249
250 imports := make(map[string]*types.Package)
251 fset2 := token.NewFileSet()
252 _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path())
253 if err != nil {
254 t.Fatalf("IImportData(%s): %v", pkg.Path(), err)
255 }
256
257
258 posn1 := fset1.Position(pkg.Scope().Lookup("X").Pos())
259 posn2 := fset2.Position(pkg2.Scope().Lookup("X").Pos())
260 if want := "foo.go:1:1"; posn2.String() != want {
261 t.Errorf("X position = %s, want %s (orig was %s)",
262 posn2, want, posn1)
263 }
264 }
265
266 func TestIExportData_typealiases(t *testing.T) {
267
268 fset1 := token.NewFileSet()
269 f, err := parser.ParseFile(fset1, "p.go", src, 0)
270 if err != nil {
271 t.Fatal(err)
272 }
273 var conf types.Config
274 pkg1, err := conf.Check("p", fset1, []*ast.File{f}, nil)
275 if err == nil {
276
277 t.Fatal("invalid source type-checked without error")
278 }
279 if pkg1 == nil {
280
281 t.Fatal("nil package returned")
282 }
283 checkPkg(t, pkg1, "export")
284
285
286
287 exportdata, err := iexport(nil, gcimporter.IExportVersion, pkg1)
288 if err != nil {
289 t.Fatal(err)
290 }
291
292
293 imports := make(map[string]*types.Package)
294 fset2 := token.NewFileSet()
295 _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg1.Path())
296 if err != nil {
297 t.Fatalf("IImportData(%s): %v", pkg1.Path(), err)
298 }
299 checkPkg(t, pkg2, "import")
300 }
301
302
303
304
305 func cmpObj(x, y types.Object) error {
306 if reflect.TypeOf(x) != reflect.TypeOf(y) {
307 return fmt.Errorf("%T vs %T", x, y)
308 }
309 xt := x.Type()
310 yt := y.Type()
311 switch x := x.(type) {
312 case *types.Var, *types.Func:
313
314 case *types.Const:
315 xval := x.Val()
316 yval := y.(*types.Const).Val()
317 equal := constant.Compare(xval, token.EQL, yval)
318 if !equal {
319
320 xkind := xval.Kind()
321 ykind := yval.Kind()
322 if xkind == constant.Complex || ykind == constant.Complex {
323 equal = same(constant.Real(xval), constant.Real(yval)) &&
324 same(constant.Imag(xval), constant.Imag(yval))
325 } else if xkind == constant.Float || ykind == constant.Float {
326 equal = same(xval, yval)
327 } else if xkind == constant.Unknown && ykind == constant.Unknown {
328 equal = true
329 }
330 }
331 if !equal {
332 return fmt.Errorf("unequal constants %s vs %s", xval, yval)
333 }
334 case *types.TypeName:
335 if xalias, yalias := x.IsAlias(), y.(*types.TypeName).IsAlias(); xalias != yalias {
336 return fmt.Errorf("mismatching IsAlias(): %s vs %s", x, y)
337 }
338
339
340
341
342
343
344
345 xn, _ := aliases.Unalias(xt).(*types.Named)
346 yn, _ := aliases.Unalias(yt).(*types.Named)
347 if (xn == nil) != (yn == nil) {
348 return fmt.Errorf("mismatching types: %T vs %T", xt, yt)
349 }
350 if xn != nil {
351 if err := cmpNamed(xn, yn); err != nil {
352 return err
353 }
354 }
355 xt = xt.Underlying()
356 yt = yt.Underlying()
357 default:
358 return fmt.Errorf("unexpected %T", x)
359 }
360 return equalType(xt, yt)
361 }
362
363
364
365 const mpprec = 512
366
367
368 func same(x, y constant.Value) bool {
369 xf := constantToFloat(x)
370 yf := constantToFloat(y)
371 d := new(big.Float).Sub(xf, yf)
372 d.Abs(d)
373 eps := big.NewFloat(1.0 / (1 << (mpprec - 1)))
374 return d.Cmp(eps) < 0
375 }
376
377
378 func constantToFloat(x constant.Value) *big.Float {
379 var f big.Float
380 f.SetPrec(mpprec)
381 if v, exact := constant.Float64Val(x); exact {
382
383 f.SetFloat64(v)
384 } else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
385
386 n := valueToRat(num)
387 d := valueToRat(denom)
388 f.SetRat(n.Quo(n, d))
389 } else {
390
391
392 _, ok := f.SetString(x.ExactString())
393 if !ok {
394 panic("should not reach here")
395 }
396 }
397 return &f
398 }
399
400
401 func valueToRat(x constant.Value) *big.Rat {
402
403
404 bytes := constant.Bytes(x)
405 for i := 0; i < len(bytes)/2; i++ {
406 bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i]
407 }
408 return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes))
409 }
410
411
412
413 func TestUnexportedStructFields(t *testing.T) {
414 fset := token.NewFileSet()
415 export := make(map[string][]byte)
416
417
418
419 process := func(path, content string) {
420 syntax, err := parser.ParseFile(fset, path+"/x.go", content, 0)
421 if err != nil {
422 t.Fatal(err)
423 }
424 packages := make(map[string]*types.Package)
425 cfg := &types.Config{
426 Importer: importerFunc(func(path string) (*types.Package, error) {
427 data, ok := export[path]
428 if !ok {
429 return nil, fmt.Errorf("missing export data for %s", path)
430 }
431 return gcexportdata.Read(bytes.NewReader(data), fset, packages, path)
432 }),
433 }
434 pkg := types.NewPackage(path, syntax.Name.Name)
435 check := types.NewChecker(cfg, fset, pkg, nil)
436 if err := check.Files([]*ast.File{syntax}); err != nil {
437 t.Fatal(err)
438 }
439 var out bytes.Buffer
440 if err := gcexportdata.Write(&out, fset, pkg); err != nil {
441 t.Fatal(err)
442 }
443 export[path] = out.Bytes()
444 }
445
446
447
448
449 process("time", `package time; type Time struct { x, y int }`)
450 process("a", `package a; import "time"; type MyTime time.Time; var M MyTime`)
451 process("b", `package b; import ("a"; "time"); var _ = time.Time(a.M)`)
452 }
453
454 type importerFunc func(path string) (*types.Package, error)
455
456 func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
457
View as plain text