Skip to content

Commit d49f960

Browse files
committed
internal/lsp/cache: report analysis panics during testing
Report a bug when an analysis dependency is found to be neither "vertical" (for facts) or "horizontal" (for results). This has been observed in practise: the lookup from a package ID to a package object is not consistent. The bug report is suppressed for command-line-arguments packages to see whether it occurs on ordinary packages too. Also, add disabled logic to disable recovery and dump all goroutines, for temporary use when debugging. Unrelated things found during debugging: - simplify package key used in analysis cache: the mode is always the same constant. - move TypeErrorAnalyzers logic closer to where needed. Updates golang/go#54655 Updates golang/go#54762 Change-Id: I0c2afd9ddd4fcc21748a03f8fa0769a2de09a56f Reviewed-on: https://go-review.googlesource.com/c/tools/+/426018 Reviewed-by: Robert Findley <[email protected]>
1 parent 27641fb commit d49f960

File tree

3 files changed

+54
-24
lines changed

3 files changed

+54
-24
lines changed

gopls/internal/lsp/cache/analysis.go

+52-22
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ import (
1010
"go/ast"
1111
"go/types"
1212
"reflect"
13+
"runtime/debug"
1314
"sync"
1415

1516
"golang.org/x/sync/errgroup"
1617
"golang.org/x/tools/go/analysis"
18+
"golang.org/x/tools/gopls/internal/lsp/source"
1719
"golang.org/x/tools/internal/analysisinternal"
20+
"golang.org/x/tools/internal/bug"
1821
"golang.org/x/tools/internal/event"
1922
"golang.org/x/tools/internal/event/tag"
20-
"golang.org/x/tools/gopls/internal/lsp/source"
2123
"golang.org/x/tools/internal/memoize"
2224
"golang.org/x/tools/internal/span"
2325
)
@@ -60,7 +62,7 @@ func (s *snapshot) Analyze(ctx context.Context, id string, analyzers []*source.A
6062
}
6163

6264
type actionKey struct {
63-
pkg packageKey
65+
pkgid PackageID
6466
analyzer *analysis.Analyzer
6567
}
6668

@@ -102,11 +104,7 @@ type packageFactKey struct {
102104
}
103105

104106
func (s *snapshot) actionHandle(ctx context.Context, id PackageID, a *analysis.Analyzer) (*actionHandle, error) {
105-
const mode = source.ParseFull
106-
key := actionKey{
107-
pkg: packageKey{id: id, mode: mode},
108-
analyzer: a,
109-
}
107+
key := actionKey{id, a}
110108

111109
s.mu.Lock()
112110
entry, hit := s.actions.Get(key)
@@ -126,7 +124,7 @@ func (s *snapshot) actionHandle(ctx context.Context, id PackageID, a *analysis.A
126124
// use of concurrency would lead to an exponential amount of duplicated
127125
// work. We should instead use an atomically updated future cache
128126
// and a parallel graph traversal.
129-
ph, err := s.buildPackageHandle(ctx, id, mode)
127+
ph, err := s.buildPackageHandle(ctx, id, source.ParseFull)
130128
if err != nil {
131129
return nil, err
132130
}
@@ -138,6 +136,8 @@ func (s *snapshot) actionHandle(ctx context.Context, id PackageID, a *analysis.A
138136
// Add a dependency on each required analyzer.
139137
var deps []*actionHandle
140138
for _, req := range a.Requires {
139+
// TODO(adonovan): opt: there's no need to repeat the package-handle
140+
// portion of the recursion here, since we have the pkg already.
141141
reqActionHandle, err := s.actionHandle(ctx, id, req)
142142
if err != nil {
143143
return nil, err
@@ -227,7 +227,8 @@ func actionImpl(ctx context.Context, snapshot *snapshot, deps []*actionHandle, a
227227
// in-memory outputs of prerequisite analyzers
228228
// become inputs to this analysis pass.
229229
inputs[dep.analyzer] = data.result
230-
} else if dep.analyzer == analyzer { // (always true)
230+
231+
} else if dep.analyzer == analyzer {
231232
// Same analysis, different package (vertical edge):
232233
// serialized facts produced by prerequisite analysis
233234
// become available to this analysis pass.
@@ -246,12 +247,34 @@ func actionImpl(ctx context.Context, snapshot *snapshot, deps []*actionHandle, a
246247
// to prevent side channels.
247248
packageFacts[key] = fact
248249
}
250+
251+
} else {
252+
// Edge is neither vertical nor horizontal.
253+
// This should never happen, yet an assertion here was
254+
// observed to fail due to an edge (bools, p) -> (inspector, p')
255+
// where p and p' are distinct packages with the
256+
// same ID ("command-line-arguments:file=.../main.go").
257+
//
258+
// It is not yet clear whether the command-line-arguments
259+
// package is significant, but it is clear that package
260+
// loading (the mapping from ID to *pkg) is inconsistent
261+
// within a single graph.
262+
263+
// Use the bug package so that we detect whether our tests
264+
// discover this problem in regular packages.
265+
// For command-line-arguments we quietly abort the analysis
266+
// for now since we already know there is a bug.
267+
errorf := bug.Errorf // report this discovery
268+
if source.IsCommandLineArguments(pkg.ID()) {
269+
errorf = fmt.Errorf // suppress reporting
270+
}
271+
return errorf("internal error: unexpected analysis dependency %s@%s -> %s", analyzer.Name, pkg.ID(), dep)
249272
}
250273
return nil
251274
})
252275
}
253276
if err := g.Wait(); err != nil {
254-
return nil, err // e.g. cancelled
277+
return nil, err // cancelled, or dependency failed
255278
}
256279

257280
// Now run the (pkg, analyzer) analysis.
@@ -338,13 +361,19 @@ func actionImpl(ctx context.Context, snapshot *snapshot, deps []*actionHandle, a
338361
var result interface{}
339362
var err error
340363
func() {
341-
defer func() {
342-
if r := recover(); r != nil {
343-
// TODO(adonovan): use bug.Errorf here so that we
344-
// detect crashes covered by our test suite.
345-
err = fmt.Errorf("analysis %s for package %s panicked: %v", analyzer.Name, pkg.PkgPath(), r)
346-
}
347-
}()
364+
// Set this flag temporarily when debugging crashes.
365+
// See https://github.com/golang/go/issues/54762.
366+
const norecover = false
367+
if norecover {
368+
debug.SetTraceback("all") // show all goroutines
369+
} else {
370+
defer func() {
371+
if r := recover(); r != nil {
372+
// Use bug.Errorf so that we detect panics during testing.
373+
err = bug.Errorf("analysis %s for package %s panicked: %v", analyzer.Name, pkg.PkgPath(), r)
374+
}
375+
}()
376+
}
348377
result, err = pass.Analyzer.Run(pass)
349378
}()
350379
if err != nil {
@@ -414,13 +443,14 @@ func factType(fact analysis.Fact) reflect.Type {
414443

415444
func (s *snapshot) DiagnosePackage(ctx context.Context, spkg source.Package) (map[span.URI][]*source.Diagnostic, error) {
416445
pkg := spkg.(*pkg)
417-
// Apply type error analyzers. They augment type error diagnostics with their own fixes.
418-
var analyzers []*source.Analyzer
419-
for _, a := range s.View().Options().TypeErrorAnalyzers {
420-
analyzers = append(analyzers, a)
421-
}
422446
var errorAnalyzerDiag []*source.Diagnostic
423447
if pkg.HasTypeErrors() {
448+
// Apply type error analyzers.
449+
// They augment type error diagnostics with their own fixes.
450+
var analyzers []*source.Analyzer
451+
for _, a := range s.View().Options().TypeErrorAnalyzers {
452+
analyzers = append(analyzers, a)
453+
}
424454
var err error
425455
errorAnalyzerDiag, err = s.Analyze(ctx, pkg.ID(), analyzers)
426456
if err != nil {

gopls/internal/lsp/cache/maps.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -212,5 +212,5 @@ func actionKeyLessInterface(a, b interface{}) bool {
212212
if x.analyzer.Name != y.analyzer.Name {
213213
return x.analyzer.Name < y.analyzer.Name
214214
}
215-
return packageKeyLess(x.pkg, y.pkg)
215+
return x.pkgid < y.pkgid
216216
}

gopls/internal/lsp/cache/snapshot.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1880,7 +1880,7 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
18801880
var actionsToDelete []actionKey
18811881
s.actions.Range(func(k, _ interface{}) {
18821882
key := k.(actionKey)
1883-
if _, ok := idsToInvalidate[key.pkg.id]; ok {
1883+
if _, ok := idsToInvalidate[key.pkgid]; ok {
18841884
actionsToDelete = append(actionsToDelete, key)
18851885
}
18861886
})

0 commit comments

Comments
 (0)