go/packages: ensure that types.Sizes is correct
This change ensures that types.Sizes information can
be computed from compiler+GOARCH if it is needed;
if not, it causes the Load to fail.
This fixes a crash in gopls whereby a bad GOARCH
causes the types.Sizes to be silently nil, in violation
of the Packages.TypesSizes contract. Gopls would
then dereference this nil.
The problem only manifests with a file=foo.go
query, as this suppresses go list's usual eager check for
valid GOOS/GOARCH during its build tag computation.
gopls relies on the file=... mode as a fall back.
Note that this change reverts my earlier doc
change today that allowed TypesSizes to be nil
if unknown. This was the wrong fix, as it
creates both a nil-dereference hazard (the
original bug), plus, if the nil Sizes is fed to
go/types.Config, it would trigger the default
(wrong) size computation.
(This is less significant to gopls because
in file=... mode, size errors are the least of
your type-error worries, but still...)
Plus, a test.
Also: simplify two trivially true conditions
in the packages.Load control flow.
Fixes golang/go#63701
Fixes golang/vscode-go#3021
Change-Id: I8d519d0d8a8c7bce6b3206a8116a150d37e74e45
Reviewed-on: https://go-review.googlesource.com/c/tools/+/537118
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/go/packages/packages.go b/go/packages/packages.go
index 3b6e58a..40af0ad 100644
--- a/go/packages/packages.go
+++ b/go/packages/packages.go
@@ -258,13 +258,21 @@
// proceeding with further analysis. The PrintErrors function is
// provided for convenient display of all errors.
func Load(cfg *Config, patterns ...string) ([]*Package, error) {
- l := newLoader(cfg)
- response, err := defaultDriver(&l.Config, patterns...)
+ ld := newLoader(cfg)
+ response, err := defaultDriver(&ld.Config, patterns...)
if err != nil {
return nil, err
}
- l.sizes = types.SizesFor(response.Compiler, response.Arch)
- return l.refine(response)
+
+ // If type size information is needed but unavailable.
+ // reject the whole Load since the error is the same for every package.
+ ld.sizes = types.SizesFor(response.Compiler, response.Arch)
+ if ld.sizes == nil && ld.Config.Mode&(NeedTypes|NeedTypesSizes|NeedTypesInfo) != 0 {
+ return nil, fmt.Errorf("can't determine type sizes for compiler %q on GOARCH %q",
+ response.Compiler, response.Arch)
+ }
+
+ return ld.refine(response)
}
// defaultDriver is a driver that implements go/packages' fallback behavior.
@@ -373,7 +381,6 @@
TypesInfo *types.Info
// TypesSizes provides the effective size function for types in TypesInfo.
- // It may be nil if, for example, the compiler/architecture pair is not known.
TypesSizes types.Sizes
// forTest is the package under test, if any.
@@ -554,7 +561,7 @@
type loader struct {
pkgs map[string]*loaderPackage
Config
- sizes types.Sizes // nil => unknown
+ sizes types.Sizes // non-nil if needed by mode
parseCache map[string]*parseValue
parseCacheMu sync.Mutex
exportMu sync.Mutex // enforces mutual exclusion of exportdata operations
@@ -679,7 +686,7 @@
}
}
- // Materialize the import graph.
+ // Materialize the import graph (if NeedImports).
const (
white = 0 // new
@@ -697,9 +704,8 @@
// visit returns whether the package needs src or has a transitive
// dependency on a package that does. These are the only packages
// for which we load source code.
- var stack []*loaderPackage
+ var stack, srcPkgs []*loaderPackage
var visit func(lpkg *loaderPackage) bool
- var srcPkgs []*loaderPackage
visit = func(lpkg *loaderPackage) bool {
switch lpkg.color {
case black:
@@ -710,35 +716,34 @@
lpkg.color = grey
stack = append(stack, lpkg) // push
stubs := lpkg.Imports // the structure form has only stubs with the ID in the Imports
- // If NeedImports isn't set, the imports fields will all be zeroed out.
- if ld.Mode&NeedImports != 0 {
- lpkg.Imports = make(map[string]*Package, len(stubs))
- for importPath, ipkg := range stubs {
- var importErr error
- imp := ld.pkgs[ipkg.ID]
- if imp == nil {
- // (includes package "C" when DisableCgo)
- importErr = fmt.Errorf("missing package: %q", ipkg.ID)
- } else if imp.color == grey {
- importErr = fmt.Errorf("import cycle: %s", stack)
- }
- if importErr != nil {
- if lpkg.importErrors == nil {
- lpkg.importErrors = make(map[string]error)
- }
- lpkg.importErrors[importPath] = importErr
- continue
- }
-
- if visit(imp) {
- lpkg.needsrc = true
- }
- lpkg.Imports[importPath] = imp.Package
+ lpkg.Imports = make(map[string]*Package, len(stubs))
+ for importPath, ipkg := range stubs {
+ var importErr error
+ imp := ld.pkgs[ipkg.ID]
+ if imp == nil {
+ // (includes package "C" when DisableCgo)
+ importErr = fmt.Errorf("missing package: %q", ipkg.ID)
+ } else if imp.color == grey {
+ importErr = fmt.Errorf("import cycle: %s", stack)
}
+ if importErr != nil {
+ if lpkg.importErrors == nil {
+ lpkg.importErrors = make(map[string]error)
+ }
+ lpkg.importErrors[importPath] = importErr
+ continue
+ }
+
+ if visit(imp) {
+ lpkg.needsrc = true
+ }
+ lpkg.Imports[importPath] = imp.Package
}
if lpkg.needsrc {
srcPkgs = append(srcPkgs, lpkg)
}
+ // NeedTypeSizes causes TypeSizes to be set even
+ // on packages for which types aren't needed.
if ld.Mode&NeedTypesSizes != 0 {
lpkg.TypesSizes = ld.sizes
}
@@ -758,17 +763,18 @@
for _, lpkg := range initial {
visit(lpkg)
}
- }
- if ld.Mode&NeedImports != 0 && ld.Mode&NeedTypes != 0 {
- for _, lpkg := range srcPkgs {
+
+ if ld.Mode&NeedTypes != 0 {
// Complete type information is required for the
// immediate dependencies of each source package.
- for _, ipkg := range lpkg.Imports {
- imp := ld.pkgs[ipkg.ID]
- imp.needtypes = true
+ for _, lpkg := range srcPkgs {
+ for _, ipkg := range lpkg.Imports {
+ ld.pkgs[ipkg.ID].needtypes = true
+ }
}
}
}
+
// Load type data and syntax if needed, starting at
// the initial packages (roots of the import DAG).
if ld.Mode&NeedTypes != 0 || ld.Mode&NeedSyntax != 0 {
diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go
index 60fdf9f..4fb7f0b 100644
--- a/go/packages/packages_test.go
+++ b/go/packages/packages_test.go
@@ -1217,6 +1217,34 @@
}
}
+// This is a regression test for the root cause of
+// github.com/golang/vscode-go/issues/3021.
+// If types are needed (any of NeedTypes{,Info,Sizes}
+// and the types.Sizes cannot be obtained (e.g. due to a bad GOARCH)
+// then the Load operation must fail. It must not return a nil
+// TypesSizes, or use the default (wrong) size.
+//
+// We use a file=... query because it suppresses the bad-GOARCH check
+// that the go command would otherwise perform eagerly.
+// (Gopls relies on this as a fallback.)
+func TestNeedTypeSizesWithBadGOARCH(t *testing.T) {
+ testAllOrModulesParallel(t, func(t *testing.T, exporter packagestest.Exporter) {
+ exported := packagestest.Export(t, exporter, []packagestest.Module{{
+ Name: "testdata",
+ Files: map[string]interface{}{"a/a.go": `package a`}}})
+ defer exported.Cleanup()
+
+ exported.Config.Mode = packages.NeedTypesSizes // or {,Info,Sizes}
+ exported.Config.Env = append(exported.Config.Env, "GOARCH=286")
+ _, err := packages.Load(exported.Config, "file=./a/a.go")
+ got := fmt.Sprint(err)
+ want := "can't determine type sizes"
+ if !strings.Contains(got, want) {
+ t.Errorf("Load error %q does not contain substring %q", got, want)
+ }
+ })
+}
+
// TestContainsFallbackSticks ensures that when there are both contains and non-contains queries
// the decision whether to fallback to the pre-1.11 go list sticks across both sets of calls to
// go list.
diff --git a/gopls/internal/lsp/cache/load.go b/gopls/internal/lsp/cache/load.go
index 331e0e7..0ac4e5d 100644
--- a/gopls/internal/lsp/cache/load.go
+++ b/gopls/internal/lsp/cache/load.go
@@ -413,6 +413,10 @@
return
}
+ if pkg.TypesSizes == nil {
+ panic(id + ".TypeSizes is nil")
+ }
+
// Recreate the metadata rather than reusing it to avoid locking.
m := &source.Metadata{
ID: id,