Skip to content

Commit caf5940

Browse files
committed
gopls/internal/cache: prune broken edges to command-line-arguments pkgs
Fix two bugs discovered during the investigation of golang/go#66109, which revealed the strange and broken intermediate test variant form "path/to/command/package [command-line-arguments.test]", referenced from the equally broken "command-line-arguments [command-line-arguments.test]". This latter package was *also* detected as an ITV, which is why we never tried to type check it in [email protected]. - Snapshot.orphanedFileDiagnostics was not pruning intermediate test variants, causing it to be the one place where we were now type checking ITVs. - Fix the latent bug that caused gopls to record a dangling edge between the two ITVs. There is a third bug in go/packages, filed as golang/go#66126. Fixes golang/go#66109 Change-Id: Ie5795b6d5a4831bf2f73217c8eb22c6ba18e59cd Reviewed-on: https://go-review.googlesource.com/c/tools/+/569035 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent ff00c7b commit caf5940

File tree

3 files changed

+45
-7
lines changed

3 files changed

+45
-7
lines changed

gopls/internal/cache/load.go

+18-7
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,11 @@ func (m *moduleErrorMap) Error() string {
336336
// buildMetadata populates the updates map with metadata updates to
337337
// apply, based on the given pkg. It recurs through pkg.Imports to ensure that
338338
// metadata exists for all dependencies.
339-
func buildMetadata(updates map[PackageID]*metadata.Package, pkg *packages.Package, loadDir string, standalone bool) {
339+
//
340+
// Returns the metadata.Package that was built (or which was already present in
341+
// updates), or nil if the package could not be built. Notably, the resulting
342+
// metadata.Package may have an ID that differs from pkg.ID.
343+
func buildMetadata(updates map[PackageID]*metadata.Package, pkg *packages.Package, loadDir string, standalone bool) *metadata.Package {
340344
// Allow for multiple ad-hoc packages in the workspace (see #47584).
341345
pkgPath := PackagePath(pkg.PkgPath)
342346
id := PackageID(pkg.ID)
@@ -351,27 +355,27 @@ func buildMetadata(updates map[PackageID]*metadata.Package, pkg *packages.Packag
351355
// (Can this happen? #64557)
352356
if len(pkg.CompiledGoFiles) > 1 {
353357
bug.Reportf("unexpected files in command-line-arguments package: %v", pkg.CompiledGoFiles)
354-
return
358+
return nil
355359
}
356360
} else if len(pkg.IgnoredFiles) > 0 {
357361
// A file=empty.go query results in IgnoredFiles=[empty.go].
358362
f = pkg.IgnoredFiles[0]
359363
} else {
360364
bug.Reportf("command-line-arguments package has neither CompiledGoFiles nor IgnoredFiles: %#v", "") //*pkg.Metadata)
361-
return
365+
return nil
362366
}
363367
id = PackageID(pkg.ID + f)
364368
pkgPath = PackagePath(pkg.PkgPath + f)
365369
}
366370

367371
// Duplicate?
368-
if _, ok := updates[id]; ok {
372+
if existing, ok := updates[id]; ok {
369373
// A package was encountered twice due to shared
370374
// subgraphs (common) or cycles (rare). Although "go
371375
// list" usually breaks cycles, we don't rely on it.
372376
// breakImportCycles in metadataGraph.Clone takes care
373377
// of it later.
374-
return
378+
return existing
375379
}
376380

377381
if pkg.TypesSizes == nil {
@@ -492,15 +496,21 @@ func buildMetadata(updates map[PackageID]*metadata.Package, pkg *packages.Packag
492496
continue
493497
}
494498

495-
buildMetadata(updates, imported, loadDir, false) // only top level packages can be standalone
499+
dep := buildMetadata(updates, imported, loadDir, false) // only top level packages can be standalone
496500

497501
// Don't record edges to packages with no name, as they cause trouble for
498502
// the importer (golang/go#60952).
499503
//
504+
// Also don't record edges to packages whose ID was modified (i.e.
505+
// command-line-arguments packages), as encountered in golang/go#66109. In
506+
// this case, we could theoretically keep the edge through dep.ID, but
507+
// since this import doesn't make any sense in the first place, we instead
508+
// choose to consider it invalid.
509+
//
500510
// However, we do want to insert these packages into the update map
501511
// (buildMetadata above), so that we get type-checking diagnostics for the
502512
// invalid packages.
503-
if imported.Name == "" {
513+
if dep == nil || dep.ID != PackageID(imported.ID) || imported.Name == "" {
504514
depsByImpPath[importPath] = "" // missing
505515
continue
506516
}
@@ -510,6 +520,7 @@ func buildMetadata(updates map[PackageID]*metadata.Package, pkg *packages.Packag
510520
}
511521
mp.DepsByImpPath = depsByImpPath
512522
mp.DepsByPkgPath = depsByPkgPath
523+
return mp
513524

514525
// m.Diagnostics is set later in the loading pass, using
515526
// computeLoadDiagnostics.

gopls/internal/cache/snapshot.go

+2
Original file line numberDiff line numberDiff line change
@@ -1441,6 +1441,8 @@ searchOverlays:
14411441
continue searchOverlays
14421442
}
14431443
}
1444+
metadata.RemoveIntermediateTestVariants(&mps)
1445+
14441446
// With zero-config gopls (golang/go#57979), orphaned file diagnostics
14451447
// include diagnostics for orphaned files -- not just diagnostics relating
14461448
// to the reason the files are opened.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
This test exercises the crash in golang/go#66109: a dangling reference due to
2+
test variants of a command-line-arguments package.
3+
4+
-- flags --
5+
-min_go=go1.22
6+
7+
-- go.mod --
8+
module example.com/tools
9+
10+
go 1.22
11+
12+
-- tools_test.go --
13+
//go:build tools
14+
15+
package tools //@diag("tools", re"No packages found")
16+
17+
import (
18+
_ "example.com/tools/tool"
19+
)
20+
21+
-- tool/tool.go --
22+
package main
23+
24+
func main() {
25+
}

0 commit comments

Comments
 (0)