Skip to content

Commit 5ab57de

Browse files
adonovanfindleyr
authored andcommitted
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 <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 02048e6 commit 5ab57de

File tree

3 files changed

+75
-37
lines changed

3 files changed

+75
-37
lines changed

go/packages/packages.go

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,21 @@ type driverResponse struct {
258258
// proceeding with further analysis. The PrintErrors function is
259259
// provided for convenient display of all errors.
260260
func Load(cfg *Config, patterns ...string) ([]*Package, error) {
261-
l := newLoader(cfg)
262-
response, err := defaultDriver(&l.Config, patterns...)
261+
ld := newLoader(cfg)
262+
response, err := defaultDriver(&ld.Config, patterns...)
263263
if err != nil {
264264
return nil, err
265265
}
266-
l.sizes = types.SizesFor(response.Compiler, response.Arch)
267-
return l.refine(response)
266+
267+
// If type size information is needed but unavailable.
268+
// reject the whole Load since the error is the same for every package.
269+
ld.sizes = types.SizesFor(response.Compiler, response.Arch)
270+
if ld.sizes == nil && ld.Config.Mode&(NeedTypes|NeedTypesSizes|NeedTypesInfo) != 0 {
271+
return nil, fmt.Errorf("can't determine type sizes for compiler %q on GOARCH %q",
272+
response.Compiler, response.Arch)
273+
}
274+
275+
return ld.refine(response)
268276
}
269277

270278
// defaultDriver is a driver that implements go/packages' fallback behavior.
@@ -373,7 +381,6 @@ type Package struct {
373381
TypesInfo *types.Info
374382

375383
// TypesSizes provides the effective size function for types in TypesInfo.
376-
// It may be nil if, for example, the compiler/architecture pair is not known.
377384
TypesSizes types.Sizes
378385

379386
// forTest is the package under test, if any.
@@ -554,7 +561,7 @@ type loaderPackage struct {
554561
type loader struct {
555562
pkgs map[string]*loaderPackage
556563
Config
557-
sizes types.Sizes // nil => unknown
564+
sizes types.Sizes // non-nil if needed by mode
558565
parseCache map[string]*parseValue
559566
parseCacheMu sync.Mutex
560567
exportMu sync.Mutex // enforces mutual exclusion of exportdata operations
@@ -679,7 +686,7 @@ func (ld *loader) refine(response *driverResponse) ([]*Package, error) {
679686
}
680687
}
681688

682-
// Materialize the import graph.
689+
// Materialize the import graph (if NeedImports).
683690

684691
const (
685692
white = 0 // new
@@ -697,9 +704,8 @@ func (ld *loader) refine(response *driverResponse) ([]*Package, error) {
697704
// visit returns whether the package needs src or has a transitive
698705
// dependency on a package that does. These are the only packages
699706
// for which we load source code.
700-
var stack []*loaderPackage
707+
var stack, srcPkgs []*loaderPackage
701708
var visit func(lpkg *loaderPackage) bool
702-
var srcPkgs []*loaderPackage
703709
visit = func(lpkg *loaderPackage) bool {
704710
switch lpkg.color {
705711
case black:
@@ -710,35 +716,34 @@ func (ld *loader) refine(response *driverResponse) ([]*Package, error) {
710716
lpkg.color = grey
711717
stack = append(stack, lpkg) // push
712718
stubs := lpkg.Imports // the structure form has only stubs with the ID in the Imports
713-
// If NeedImports isn't set, the imports fields will all be zeroed out.
714-
if ld.Mode&NeedImports != 0 {
715-
lpkg.Imports = make(map[string]*Package, len(stubs))
716-
for importPath, ipkg := range stubs {
717-
var importErr error
718-
imp := ld.pkgs[ipkg.ID]
719-
if imp == nil {
720-
// (includes package "C" when DisableCgo)
721-
importErr = fmt.Errorf("missing package: %q", ipkg.ID)
722-
} else if imp.color == grey {
723-
importErr = fmt.Errorf("import cycle: %s", stack)
724-
}
725-
if importErr != nil {
726-
if lpkg.importErrors == nil {
727-
lpkg.importErrors = make(map[string]error)
728-
}
729-
lpkg.importErrors[importPath] = importErr
730-
continue
719+
lpkg.Imports = make(map[string]*Package, len(stubs))
720+
for importPath, ipkg := range stubs {
721+
var importErr error
722+
imp := ld.pkgs[ipkg.ID]
723+
if imp == nil {
724+
// (includes package "C" when DisableCgo)
725+
importErr = fmt.Errorf("missing package: %q", ipkg.ID)
726+
} else if imp.color == grey {
727+
importErr = fmt.Errorf("import cycle: %s", stack)
728+
}
729+
if importErr != nil {
730+
if lpkg.importErrors == nil {
731+
lpkg.importErrors = make(map[string]error)
731732
}
733+
lpkg.importErrors[importPath] = importErr
734+
continue
735+
}
732736

733-
if visit(imp) {
734-
lpkg.needsrc = true
735-
}
736-
lpkg.Imports[importPath] = imp.Package
737+
if visit(imp) {
738+
lpkg.needsrc = true
737739
}
740+
lpkg.Imports[importPath] = imp.Package
738741
}
739742
if lpkg.needsrc {
740743
srcPkgs = append(srcPkgs, lpkg)
741744
}
745+
// NeedTypeSizes causes TypeSizes to be set even
746+
// on packages for which types aren't needed.
742747
if ld.Mode&NeedTypesSizes != 0 {
743748
lpkg.TypesSizes = ld.sizes
744749
}
@@ -758,17 +763,18 @@ func (ld *loader) refine(response *driverResponse) ([]*Package, error) {
758763
for _, lpkg := range initial {
759764
visit(lpkg)
760765
}
761-
}
762-
if ld.Mode&NeedImports != 0 && ld.Mode&NeedTypes != 0 {
763-
for _, lpkg := range srcPkgs {
766+
767+
if ld.Mode&NeedTypes != 0 {
764768
// Complete type information is required for the
765769
// immediate dependencies of each source package.
766-
for _, ipkg := range lpkg.Imports {
767-
imp := ld.pkgs[ipkg.ID]
768-
imp.needtypes = true
770+
for _, lpkg := range srcPkgs {
771+
for _, ipkg := range lpkg.Imports {
772+
ld.pkgs[ipkg.ID].needtypes = true
773+
}
769774
}
770775
}
771776
}
777+
772778
// Load type data and syntax if needed, starting at
773779
// the initial packages (roots of the import DAG).
774780
if ld.Mode&NeedTypes != 0 || ld.Mode&NeedSyntax != 0 {

go/packages/packages_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,34 @@ func testSizes(t *testing.T, exporter packagestest.Exporter) {
12171217
}
12181218
}
12191219

1220+
// This is a regression test for the root cause of
1221+
// github.com/golang/vscode-go/issues/3021.
1222+
// If types are needed (any of NeedTypes{,Info,Sizes}
1223+
// and the types.Sizes cannot be obtained (e.g. due to a bad GOARCH)
1224+
// then the Load operation must fail. It must not return a nil
1225+
// TypesSizes, or use the default (wrong) size.
1226+
//
1227+
// We use a file=... query because it suppresses the bad-GOARCH check
1228+
// that the go command would otherwise perform eagerly.
1229+
// (Gopls relies on this as a fallback.)
1230+
func TestNeedTypeSizesWithBadGOARCH(t *testing.T) {
1231+
testAllOrModulesParallel(t, func(t *testing.T, exporter packagestest.Exporter) {
1232+
exported := packagestest.Export(t, exporter, []packagestest.Module{{
1233+
Name: "testdata",
1234+
Files: map[string]interface{}{"a/a.go": `package a`}}})
1235+
defer exported.Cleanup()
1236+
1237+
exported.Config.Mode = packages.NeedTypesSizes // or {,Info,Sizes}
1238+
exported.Config.Env = append(exported.Config.Env, "GOARCH=286")
1239+
_, err := packages.Load(exported.Config, "file=./a/a.go")
1240+
got := fmt.Sprint(err)
1241+
want := "can't determine type sizes"
1242+
if !strings.Contains(got, want) {
1243+
t.Errorf("Load error %q does not contain substring %q", got, want)
1244+
}
1245+
})
1246+
}
1247+
12201248
// TestContainsFallbackSticks ensures that when there are both contains and non-contains queries
12211249
// the decision whether to fallback to the pre-1.11 go list sticks across both sets of calls to
12221250
// go list.

gopls/internal/lsp/cache/load.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,10 @@ func buildMetadata(updates map[PackageID]*source.Metadata, pkg *packages.Package
413413
return
414414
}
415415

416+
if pkg.TypesSizes == nil {
417+
panic(id + ".TypeSizes is nil")
418+
}
419+
416420
// Recreate the metadata rather than reusing it to avoid locking.
417421
m := &source.Metadata{
418422
ID: id,

0 commit comments

Comments
 (0)