golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/internal/gcimporter/iexport_test.go (about)

     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"
    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  // isUnifiedBuilder reports whether we are executing on a go builder that uses
    72  // unified export data.
    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  	// 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  	// TODO(adonovan): opt: parallelize this slow loop.
   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  			// skip; no export data
   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  	// Compare the packages' corresponding members.
   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  // TestIExportData_long tests the position of an import object declared in
   227  // a very long input file.  Line numbers greater than maxlines are
   228  // reported as line 1, not garbage or token.NoPos.
   229  func TestIExportData_long(t *testing.T) {
   230  	// parse and typecheck
   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  	// export
   244  	exportdata, err := iexport(fset1, gcimporter.IExportVersion, pkg)
   245  	if err != nil {
   246  		t.Fatal(err)
   247  	}
   248  
   249  	// import
   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  	// compare
   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  	// parse and typecheck
   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  		// foo in undeclared in src; we should see an error
   277  		t.Fatal("invalid source type-checked without error")
   278  	}
   279  	if pkg1 == nil {
   280  		// despite incorrect src we should see a (partially) type-checked package
   281  		t.Fatal("nil package returned")
   282  	}
   283  	checkPkg(t, pkg1, "export")
   284  
   285  	// export
   286  	// use a nil fileset here to confirm that it doesn't panic
   287  	exportdata, err := iexport(nil, gcimporter.IExportVersion, pkg1)
   288  	if err != nil {
   289  		t.Fatal(err)
   290  	}
   291  
   292  	// import
   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  // cmpObj reports how x and y differ. They are assumed to belong to different
   303  // universes so cannot be compared directly. It is an adapted version of
   304  // equalObj in bexport_test.go.
   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  		// ok
   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  			// try approx. comparison
   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  		// equalType does not recurse into the underlying types of named types, so
   340  		// we must pass the underlying type explicitly here. However, in doing this
   341  		// we may skip checking the features of the named types themselves, in
   342  		// situations where the type name is not referenced by the underlying or
   343  		// any other top-level declarations. Therefore, we must explicitly compare
   344  		// named types here, before passing their underlying types into equalType.
   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  // Use the same floating-point precision (512) as cmd/compile
   364  // (see Mpprec in cmd/compile/internal/gc/mpfloat.go).
   365  const mpprec = 512
   366  
   367  // same compares non-complex numeric values and reports if they are approximately equal.
   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))) // allow for 1 bit of error
   374  	return d.Cmp(eps) < 0
   375  }
   376  
   377  // copy of the function with the same name in iexport.go.
   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  		// float64
   383  		f.SetFloat64(v)
   384  	} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
   385  		// TODO(gri): add big.Rat accessor to constant.Value.
   386  		n := valueToRat(num)
   387  		d := valueToRat(denom)
   388  		f.SetRat(n.Quo(n, d))
   389  	} else {
   390  		// Value too large to represent as a fraction => inaccessible.
   391  		// TODO(gri): add big.Float accessor to constant.Value.
   392  		_, ok := f.SetString(x.ExactString())
   393  		if !ok {
   394  			panic("should not reach here")
   395  		}
   396  	}
   397  	return &f
   398  }
   399  
   400  // copy of the function with the same name in iexport.go.
   401  func valueToRat(x constant.Value) *big.Rat {
   402  	// Convert little-endian to big-endian.
   403  	// I can't believe this is necessary.
   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  // This is a regression test for a bug in iexport of types.Struct:
   412  // unexported fields were losing their implicit package qualifier.
   413  func TestUnexportedStructFields(t *testing.T) {
   414  	fset := token.NewFileSet()
   415  	export := make(map[string][]byte)
   416  
   417  	// process parses and type-checks a single-file
   418  	// package and saves its export data.
   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) // keys are package paths
   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  	// Historically this led to a spurious error:
   447  	// "cannot convert a.M (variable of type a.MyTime) to type time.Time"
   448  	// because the private fields of Time and MyTime were not identical.
   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) }