| 1 | // Copyright 2019 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 | // This is a copy of bexport_test.go for iexport.go. |
| 6 | |
| 7 | //go:build go1.11 |
| 8 | // +build go1.11 |
| 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/ioutil" |
| 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/gcimporter" |
| 36 | "golang.org/x/tools/internal/typeparams/genericfeatures" |
| 37 | ) |
| 38 | |
| 39 | func readExportFile(filename string) ([]byte, error) { |
| 40 | f, err := os.Open(filename) |
| 41 | if err != nil { |
| 42 | return nil, err |
| 43 | } |
| 44 | defer f.Close() |
| 45 | |
| 46 | buf := bufio.NewReader(f) |
| 47 | if _, _, err := gcimporter.FindExportData(buf); err != nil { |
| 48 | return nil, err |
| 49 | } |
| 50 | |
| 51 | if ch, err := buf.ReadByte(); err != nil { |
| 52 | return nil, err |
| 53 | } else if ch != 'i' { |
| 54 | return nil, fmt.Errorf("unexpected byte: %v", ch) |
| 55 | } |
| 56 | |
| 57 | return ioutil.ReadAll(buf) |
| 58 | } |
| 59 | |
| 60 | func iexport(fset *token.FileSet, version int, pkg *types.Package) ([]byte, error) { |
| 61 | var buf bytes.Buffer |
| 62 | const bundle, shallow = false, false |
| 63 | if err := gcimporter.IExportCommon(&buf, fset, bundle, shallow, version, []*types.Package{pkg}); err != nil { |
| 64 | return nil, err |
| 65 | } |
| 66 | return buf.Bytes(), nil |
| 67 | } |
| 68 | |
| 69 | // isUnifiedBuilder reports whether we are executing on a go builder that uses |
| 70 | // unified export data. |
| 71 | func isUnifiedBuilder() bool { |
| 72 | return os.Getenv("GO_BUILDER_NAME") == "linux-amd64-unified" |
| 73 | } |
| 74 | |
| 75 | const minStdlibPackages = 248 |
| 76 | |
| 77 | func TestIExportData_stdlib(t *testing.T) { |
| 78 | if runtime.Compiler == "gccgo" { |
| 79 | t.Skip("gccgo standard library is inaccessible") |
| 80 | } |
| 81 | if runtime.GOOS == "android" { |
| 82 | t.Skipf("incomplete std lib on %s", runtime.GOOS) |
| 83 | } |
| 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 | // Load, parse and type-check the program. |
| 92 | ctxt := build.Default // copy |
| 93 | ctxt.GOPATH = "" // disable 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 | // Create a package containing type and value errors to ensure |
| 107 | // they are properly encoded/decoded. |
| 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 | // Temporarily skip packages that use generics on the unified builder, to |
| 126 | // fix TryBots. |
| 127 | // |
| 128 | // TODO(#48595): fix this test with GOEXPERIMENT=unified. |
| 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 { // non-empty directory |
| 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 | for _, pkg := range sorted { |
| 150 | if exportdata, err := iexport(conf.Fset, version, pkg); err != nil { |
| 151 | t.Error(err) |
| 152 | } else { |
| 153 | testPkgData(t, conf.Fset, version, pkg, exportdata) |
| 154 | } |
| 155 | |
| 156 | if pkg.Name() == "main" || pkg.Name() == "haserrors" { |
| 157 | // skip; no export data |
| 158 | } else if bp, err := ctxt.Import(pkg.Path(), "", build.FindOnly); err != nil { |
| 159 | t.Log("warning:", err) |
| 160 | } else if exportdata, err := readExportFile(bp.PkgObj); err != nil { |
| 161 | t.Log("warning:", err) |
| 162 | } else { |
| 163 | testPkgData(t, conf.Fset, version, pkg, exportdata) |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | var bundle bytes.Buffer |
| 168 | if err := gcimporter.IExportBundle(&bundle, conf.Fset, sorted); err != nil { |
| 169 | t.Fatal(err) |
| 170 | } |
| 171 | fset2 := token.NewFileSet() |
| 172 | imports := make(map[string]*types.Package) |
| 173 | pkgs2, err := gcimporter.IImportBundle(fset2, imports, bundle.Bytes()) |
| 174 | if err != nil { |
| 175 | t.Fatal(err) |
| 176 | } |
| 177 | |
| 178 | for i, pkg := range sorted { |
| 179 | testPkg(t, conf.Fset, version, pkg, fset2, pkgs2[i]) |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | func testPkgData(t *testing.T, fset *token.FileSet, version int, pkg *types.Package, exportdata []byte) { |
| 184 | imports := make(map[string]*types.Package) |
| 185 | fset2 := token.NewFileSet() |
| 186 | _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path()) |
| 187 | if err != nil { |
| 188 | t.Errorf("IImportData(%s): %v", pkg.Path(), err) |
| 189 | } |
| 190 | |
| 191 | testPkg(t, fset, version, pkg, fset2, pkg2) |
| 192 | } |
| 193 | |
| 194 | func testPkg(t *testing.T, fset *token.FileSet, version int, pkg *types.Package, fset2 *token.FileSet, pkg2 *types.Package) { |
| 195 | if _, err := iexport(fset2, version, pkg2); err != nil { |
| 196 | t.Errorf("reexport %q: %v", pkg.Path(), err) |
| 197 | } |
| 198 | |
| 199 | // Compare the packages' corresponding members. |
| 200 | for _, name := range pkg.Scope().Names() { |
| 201 | if !token.IsExported(name) { |
| 202 | continue |
| 203 | } |
| 204 | obj1 := pkg.Scope().Lookup(name) |
| 205 | obj2 := pkg2.Scope().Lookup(name) |
| 206 | if obj2 == nil { |
| 207 | t.Errorf("%s.%s not found, want %s", pkg.Path(), name, obj1) |
| 208 | continue |
| 209 | } |
| 210 | |
| 211 | fl1 := fileLine(fset, obj1) |
| 212 | fl2 := fileLine(fset2, obj2) |
| 213 | if fl1 != fl2 { |
| 214 | t.Errorf("%s.%s: got posn %s, want %s", |
| 215 | pkg.Path(), name, fl2, fl1) |
| 216 | } |
| 217 | |
| 218 | if err := cmpObj(obj1, obj2); err != nil { |
| 219 | t.Errorf("%s.%s: %s\ngot: %s\nwant: %s", |
| 220 | pkg.Path(), name, err, obj2, obj1) |
| 221 | } |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | // TestVeryLongFile tests the position of an import object declared in |
| 226 | // a very long input file. Line numbers greater than maxlines are |
| 227 | // reported as line 1, not garbage or token.NoPos. |
| 228 | func TestIExportData_long(t *testing.T) { |
| 229 | // parse and typecheck |
| 230 | longFile := "package foo" + strings.Repeat("\n", 123456) + "var X int" |
| 231 | fset1 := token.NewFileSet() |
| 232 | f, err := parser.ParseFile(fset1, "foo.go", longFile, 0) |
| 233 | if err != nil { |
| 234 | t.Fatal(err) |
| 235 | } |
| 236 | var conf types.Config |
| 237 | pkg, err := conf.Check("foo", fset1, []*ast.File{f}, nil) |
| 238 | if err != nil { |
| 239 | t.Fatal(err) |
| 240 | } |
| 241 | |
| 242 | // export |
| 243 | exportdata, err := iexport(fset1, gcimporter.IExportVersion, pkg) |
| 244 | if err != nil { |
| 245 | t.Fatal(err) |
| 246 | } |
| 247 | |
| 248 | // import |
| 249 | imports := make(map[string]*types.Package) |
| 250 | fset2 := token.NewFileSet() |
| 251 | _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path()) |
| 252 | if err != nil { |
| 253 | t.Fatalf("IImportData(%s): %v", pkg.Path(), err) |
| 254 | } |
| 255 | |
| 256 | // compare |
| 257 | posn1 := fset1.Position(pkg.Scope().Lookup("X").Pos()) |
| 258 | posn2 := fset2.Position(pkg2.Scope().Lookup("X").Pos()) |
| 259 | if want := "foo.go:1:1"; posn2.String() != want { |
| 260 | t.Errorf("X position = %s, want %s (orig was %s)", |
| 261 | posn2, want, posn1) |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | func TestIExportData_typealiases(t *testing.T) { |
| 266 | // parse and typecheck |
| 267 | fset1 := token.NewFileSet() |
| 268 | f, err := parser.ParseFile(fset1, "p.go", src, 0) |
| 269 | if err != nil { |
| 270 | t.Fatal(err) |
| 271 | } |
| 272 | var conf types.Config |
| 273 | pkg1, err := conf.Check("p", fset1, []*ast.File{f}, nil) |
| 274 | if err == nil { |
| 275 | // foo in undeclared in src; we should see an error |
| 276 | t.Fatal("invalid source type-checked without error") |
| 277 | } |
| 278 | if pkg1 == nil { |
| 279 | // despite incorrect src we should see a (partially) type-checked package |
| 280 | t.Fatal("nil package returned") |
| 281 | } |
| 282 | checkPkg(t, pkg1, "export") |
| 283 | |
| 284 | // export |
| 285 | // use a nil fileset here to confirm that it doesn't panic |
| 286 | exportdata, err := iexport(nil, gcimporter.IExportVersion, pkg1) |
| 287 | if err != nil { |
| 288 | t.Fatal(err) |
| 289 | } |
| 290 | |
| 291 | // import |
| 292 | imports := make(map[string]*types.Package) |
| 293 | fset2 := token.NewFileSet() |
| 294 | _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg1.Path()) |
| 295 | if err != nil { |
| 296 | t.Fatalf("IImportData(%s): %v", pkg1.Path(), err) |
| 297 | } |
| 298 | checkPkg(t, pkg2, "import") |
| 299 | } |
| 300 | |
| 301 | // cmpObj reports how x and y differ. They are assumed to belong to different |
| 302 | // universes so cannot be compared directly. It is an adapted version of |
| 303 | // equalObj in bexport_test.go. |
| 304 | func cmpObj(x, y types.Object) error { |
| 305 | if reflect.TypeOf(x) != reflect.TypeOf(y) { |
| 306 | return fmt.Errorf("%T vs %T", x, y) |
| 307 | } |
| 308 | xt := x.Type() |
| 309 | yt := y.Type() |
| 310 | switch x := x.(type) { |
| 311 | case *types.Var, *types.Func: |
| 312 | // ok |
| 313 | case *types.Const: |
| 314 | xval := x.Val() |
| 315 | yval := y.(*types.Const).Val() |
| 316 | equal := constant.Compare(xval, token.EQL, yval) |
| 317 | if !equal { |
| 318 | // try approx. comparison |
| 319 | xkind := xval.Kind() |
| 320 | ykind := yval.Kind() |
| 321 | if xkind == constant.Complex || ykind == constant.Complex { |
| 322 | equal = same(constant.Real(xval), constant.Real(yval)) && |
| 323 | same(constant.Imag(xval), constant.Imag(yval)) |
| 324 | } else if xkind == constant.Float || ykind == constant.Float { |
| 325 | equal = same(xval, yval) |
| 326 | } else if xkind == constant.Unknown && ykind == constant.Unknown { |
| 327 | equal = true |
| 328 | } |
| 329 | } |
| 330 | if !equal { |
| 331 | return fmt.Errorf("unequal constants %s vs %s", xval, yval) |
| 332 | } |
| 333 | case *types.TypeName: |
| 334 | if xalias, yalias := x.IsAlias(), y.(*types.TypeName).IsAlias(); xalias != yalias { |
| 335 | return fmt.Errorf("mismatching IsAlias(): %s vs %s", x, y) |
| 336 | } |
| 337 | // equalType does not recurse into the underlying types of named types, so |
| 338 | // we must pass the underlying type explicitly here. However, in doing this |
| 339 | // we may skip checking the features of the named types themselves, in |
| 340 | // situations where the type name is not referenced by the underlying or |
| 341 | // any other top-level declarations. Therefore, we must explicitly compare |
| 342 | // named types here, before passing their underlying types into equalType. |
| 343 | xn, _ := xt.(*types.Named) |
| 344 | yn, _ := yt.(*types.Named) |
| 345 | if (xn == nil) != (yn == nil) { |
| 346 | return fmt.Errorf("mismatching types: %T vs %T", xt, yt) |
| 347 | } |
| 348 | if xn != nil { |
| 349 | if err := cmpNamed(xn, yn); err != nil { |
| 350 | return err |
| 351 | } |
| 352 | } |
| 353 | xt = xt.Underlying() |
| 354 | yt = yt.Underlying() |
| 355 | default: |
| 356 | return fmt.Errorf("unexpected %T", x) |
| 357 | } |
| 358 | return equalType(xt, yt) |
| 359 | } |
| 360 | |
| 361 | // Use the same floating-point precision (512) as cmd/compile |
| 362 | // (see Mpprec in cmd/compile/internal/gc/mpfloat.go). |
| 363 | const mpprec = 512 |
| 364 | |
| 365 | // same compares non-complex numeric values and reports if they are approximately equal. |
| 366 | func same(x, y constant.Value) bool { |
| 367 | xf := constantToFloat(x) |
| 368 | yf := constantToFloat(y) |
| 369 | d := new(big.Float).Sub(xf, yf) |
| 370 | d.Abs(d) |
| 371 | eps := big.NewFloat(1.0 / (1 << (mpprec - 1))) // allow for 1 bit of error |
| 372 | return d.Cmp(eps) < 0 |
| 373 | } |
| 374 | |
| 375 | // copy of the function with the same name in iexport.go. |
| 376 | func constantToFloat(x constant.Value) *big.Float { |
| 377 | var f big.Float |
| 378 | f.SetPrec(mpprec) |
| 379 | if v, exact := constant.Float64Val(x); exact { |
| 380 | // float64 |
| 381 | f.SetFloat64(v) |
| 382 | } else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int { |
| 383 | // TODO(gri): add big.Rat accessor to constant.Value. |
| 384 | n := valueToRat(num) |
| 385 | d := valueToRat(denom) |
| 386 | f.SetRat(n.Quo(n, d)) |
| 387 | } else { |
| 388 | // Value too large to represent as a fraction => inaccessible. |
| 389 | // TODO(gri): add big.Float accessor to constant.Value. |
| 390 | _, ok := f.SetString(x.ExactString()) |
| 391 | if !ok { |
| 392 | panic("should not reach here") |
| 393 | } |
| 394 | } |
| 395 | return &f |
| 396 | } |
| 397 | |
| 398 | // copy of the function with the same name in iexport.go. |
| 399 | func valueToRat(x constant.Value) *big.Rat { |
| 400 | // Convert little-endian to big-endian. |
| 401 | // I can't believe this is necessary. |
| 402 | bytes := constant.Bytes(x) |
| 403 | for i := 0; i < len(bytes)/2; i++ { |
| 404 | bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i] |
| 405 | } |
| 406 | return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) |
| 407 | } |
| 408 | |
| 409 | // This is a regression test for a bug in iexport of types.Struct: |
| 410 | // unexported fields were losing their implicit package qualifier. |
| 411 | func TestUnexportedStructFields(t *testing.T) { |
| 412 | fset := token.NewFileSet() |
| 413 | export := make(map[string][]byte) |
| 414 | |
| 415 | // process parses and type-checks a single-file |
| 416 | // package and saves its export data. |
| 417 | process := func(path, content string) { |
| 418 | syntax, err := parser.ParseFile(fset, path+"/x.go", content, 0) |
| 419 | if err != nil { |
| 420 | t.Fatal(err) |
| 421 | } |
| 422 | packages := make(map[string]*types.Package) // keys are package paths |
| 423 | cfg := &types.Config{ |
| 424 | Importer: importerFunc(func(path string) (*types.Package, error) { |
| 425 | data, ok := export[path] |
| 426 | if !ok { |
| 427 | return nil, fmt.Errorf("missing export data for %s", path) |
| 428 | } |
| 429 | return gcexportdata.Read(bytes.NewReader(data), fset, packages, path) |
| 430 | }), |
| 431 | } |
| 432 | pkg := types.NewPackage(path, syntax.Name.Name) |
| 433 | check := types.NewChecker(cfg, fset, pkg, nil) |
| 434 | if err := check.Files([]*ast.File{syntax}); err != nil { |
| 435 | t.Fatal(err) |
| 436 | } |
| 437 | var out bytes.Buffer |
| 438 | if err := gcexportdata.Write(&out, fset, pkg); err != nil { |
| 439 | t.Fatal(err) |
| 440 | } |
| 441 | export[path] = out.Bytes() |
| 442 | } |
| 443 | |
| 444 | // Historically this led to a spurious error: |
| 445 | // "cannot convert a.M (variable of type a.MyTime) to type time.Time" |
| 446 | // because the private fields of Time and MyTime were not identical. |
| 447 | process("time", `package time; type Time struct { x, y int }`) |
| 448 | process("a", `package a; import "time"; type MyTime time.Time; var M MyTime`) |
| 449 | process("b", `package b; import ("a"; "time"); var _ = time.Time(a.M)`) |
| 450 | } |
| 451 | |
| 452 | type importerFunc func(path string) (*types.Package, error) |
| 453 | |
| 454 | func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } |
| 455 |
Members