gopls/internal/lsp/cache: add assertions

This change documents and asserts a few invariants that should
be maintained by the code. The crash in golang/go#60551 shows
that they are not, but I still can't see the mistake in my proof.

Updates golang/go#60551

Change-Id: I833f7575f1d7372837ab5d7ba5988c94650ce07f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/500055
Auto-Submit: Alan Donovan <adonovan@google.com>
Run-TryBot: Alan Donovan <adonovan@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/internal/lsp/cache/analysis.go b/gopls/internal/lsp/cache/analysis.go
index 4679041..04e2651 100644
--- a/gopls/internal/lsp/cache/analysis.go
+++ b/gopls/internal/lsp/cache/analysis.go
@@ -23,7 +23,6 @@
 	"sort"
 	"strings"
 	"sync"
-	"time"
 
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/tools/go/analysis"
@@ -32,6 +31,7 @@
 	"golang.org/x/tools/gopls/internal/lsp/protocol"
 	"golang.org/x/tools/gopls/internal/lsp/source"
 	"golang.org/x/tools/internal/event"
+	"golang.org/x/tools/internal/event/tag"
 	"golang.org/x/tools/internal/facts"
 	"golang.org/x/tools/internal/gcimporter"
 	"golang.org/x/tools/internal/memoize"
@@ -164,8 +164,6 @@
 //        Destroy()
 //   - share cache.{goVersionRx,parseGoImpl}
 
-var born = time.Now()
-
 // Analyze applies a set of analyzers to the package denoted by id,
 // and returns their diagnostics for that package.
 //
@@ -174,9 +172,8 @@
 // Precondition: all analyzers within the process have distinct names.
 // (The names are relied on by the serialization logic.)
 func (s *snapshot) Analyze(ctx context.Context, id PackageID, analyzers []*source.Analyzer) ([]*source.Diagnostic, error) {
-	if false { // debugging
-		log.Println("Analyze@", time.Since(born)) // called after the 7s IWL in k8s
-	}
+	ctx, done := event.Start(ctx, "snapshot.Analyze", tag.Package.Of(string(id)))
+	defer done()
 
 	// Filter and sort enabled root analyzers.
 	// A disabled analyzer may still be run if required by another.
@@ -199,19 +196,13 @@
 		}
 	}
 
-	if false { // debugging
-		// TODO(adonovan): use proper tracing.
-		t0 := time.Now()
-		defer func() {
-			log.Printf("%v for analyze(%s, %s)", time.Since(t0), id, enabled)
-		}()
-	}
-
 	// Run the analysis.
 	res, err := s.analyze(ctx, id, enabled)
 	if err != nil {
 		return nil, err
 	}
+	// Inv: res is the successful result of analyzeImpl(analyzers, id),
+	// which augments the successful result of actuallyAnalyze.
 
 	// Report diagnostics only from enabled actions that succeeded.
 	// Errors from creating or analyzing packages are ignored.
@@ -225,7 +216,11 @@
 	// results, we should propagate the per-action errors.
 	var results []*source.Diagnostic
 	for _, a := range enabled {
-		summary := res.Actions[a.Name]
+		summary, ok := res.Actions[a.Name]
+		if summary == nil {
+			panic(fmt.Sprintf("analyzeSummary.Actions[%q] = (nil, %t); got %v (#60551)",
+				a.Name, ok, res.Actions))
+		}
 		if summary.Err != "" {
 			continue // action failed
 		}
@@ -309,7 +304,7 @@
 
 // analyze is a memoization of analyzeImpl.
 func (s *snapshot) analyze(ctx context.Context, id PackageID, analyzers []*analysis.Analyzer) (*analyzeSummary, error) {
-	// Use the sorted list of names of analyzers in the key.
+	// Use the caller-sorted list of names of analyzers in the key.
 	//
 	// TODO(adonovan): opt: account for analysis results at a
 	// finer grain to avoid duplicate work when a
@@ -568,6 +563,9 @@
 
 // actuallyAnalyze implements the cache-miss case.
 // This function does not access the snapshot.
+//
+// Postcondition: on success, the analyzeSummary.Actions
+// key set is {a.Name for a in analyzers}.
 func actuallyAnalyze(ctx context.Context, analyzers []*analysis.Analyzer, m *source.Metadata, vdeps map[PackageID]*analyzeSummary, compiledGoFiles []source.FileHandle) (*analyzeSummary, error) {
 	// Create a local FileSet for processing this package only.
 	fset := token.NewFileSet()
@@ -637,6 +635,7 @@
 
 	// Execute the graph in parallel.
 	execActions(roots)
+	// Inv: each root's summary is set (whether success or error).
 
 	// Don't return (or cache) the result in case of cancellation.
 	if err := ctx.Err(); err != nil {
@@ -645,8 +644,11 @@
 
 	// Return summaries only for the requested actions.
 	summaries := make(map[string]*actionSummary)
-	for _, act := range roots {
-		summaries[act.a.Name] = act.summary
+	for _, root := range roots {
+		if root.summary == nil {
+			panic("root has nil action.summary (#60551)")
+		}
+		summaries[root.a.Name] = root.summary
 	}
 
 	return &analyzeSummary{
@@ -897,6 +899,7 @@
 }
 
 // execActions executes a set of action graph nodes in parallel.
+// Postcondition: each action.summary is set, even in case of error.
 func execActions(actions []*action) {
 	var wg sync.WaitGroup
 	for _, act := range actions {
@@ -917,6 +920,9 @@
 					}
 				}
 			})
+			if act.summary == nil {
+				panic("nil action.summary (#60551)")
+			}
 		}()
 	}
 	wg.Wait()