internal/lsp/cache: recover from panics in analyses

Some analyses may panic, so we should be careful to recover.

Change-Id: Ie13df07aca1a21f4e6a66648cd4890b6a31966cc
Reviewed-on: https://go-review.googlesource.com/c/tools/+/204817
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
Rebecca Stambler 2019-11-01 15:04:15 -04:00
parent a860bcda08
commit 8dbcdeb83d

View File

@ -12,6 +12,7 @@ import (
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/lsp/telemetry"
"golang.org/x/tools/internal/memoize" "golang.org/x/tools/internal/memoize"
"golang.org/x/tools/internal/telemetry/log" "golang.org/x/tools/internal/telemetry/log"
errors "golang.org/x/xerrors" errors "golang.org/x/xerrors"
@ -123,14 +124,14 @@ func (s *snapshot) actionHandle(ctx context.Context, id packageID, mode source.P
fset := s.view.session.cache.fset fset := s.view.session.cache.fset
h := s.view.session.cache.store.Bind(buildActionKey(a, cph), func(ctx context.Context) interface{} { h := s.view.session.cache.store.Bind(buildActionKey(a, cph), func(ctx context.Context) interface{} {
data := &actionData{} // Analyze dependencies first.
// Analyze dependencies.
results, err := execAll(ctx, fset, deps) results, err := execAll(ctx, fset, deps)
if err != nil { if err != nil {
data.err = err return &actionData{
return nil err: err,
} }
data.diagnostics, data.result, data.objectFacts, data.packageFacts, data.err = runAnalysis(ctx, fset, a, pkg, results) }
data := runAnalysis(ctx, fset, a, pkg, results)
return data return data
}) })
ah.handle = h ah.handle = h
@ -192,7 +193,18 @@ func execAll(ctx context.Context, fset *token.FileSet, actions []*actionHandle)
return results, g.Wait() return results, g.Wait()
} }
func runAnalysis(ctx context.Context, fset *token.FileSet, analyzer *analysis.Analyzer, pkg *pkg, deps map[*actionHandle]*actionData) ([]*source.Error, interface{}, map[objectFactKey]analysis.Fact, map[packageFactKey]analysis.Fact, error) { func runAnalysis(ctx context.Context, fset *token.FileSet, analyzer *analysis.Analyzer, pkg *pkg, deps map[*actionHandle]*actionData) (data *actionData) {
data = &actionData{
objectFacts: make(map[objectFactKey]analysis.Fact),
packageFacts: make(map[packageFactKey]analysis.Fact),
}
defer func() {
if r := recover(); r != nil {
log.Print(ctx, fmt.Sprintf("analysis panicked: %s", r), telemetry.Package.Of(pkg.PkgPath))
data.err = errors.Errorf("analysis %s for package %s panicked: %v", analyzer.Name, pkg.PkgPath())
}
}()
// Plumb the output values of the dependencies // Plumb the output values of the dependencies
// into the inputs of this action. Also facts. // into the inputs of this action. Also facts.
inputs := make(map[*analysis.Analyzer]interface{}) inputs := make(map[*analysis.Analyzer]interface{})
@ -300,14 +312,16 @@ func runAnalysis(ctx context.Context, fset *token.FileSet, analyzer *analysis.An
} }
if pkg.IsIllTyped() { if pkg.IsIllTyped() {
return nil, nil, nil, nil, errors.Errorf("analysis skipped due to errors in package: %v", pkg.GetErrors()) data.err = errors.Errorf("analysis skipped due to errors in package: %v", pkg.GetErrors())
return data
} }
result, err := pass.Analyzer.Run(pass) data.result, data.err = pass.Analyzer.Run(pass)
if err == nil { if data.err == nil {
if got, want := reflect.TypeOf(result), pass.Analyzer.ResultType; got != want { if got, want := reflect.TypeOf(data.result), pass.Analyzer.ResultType; got != want {
err = errors.Errorf( data.err = errors.Errorf(
"internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v", "internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v",
pass.Pkg.Path(), pass.Analyzer, got, want) pass.Pkg.Path(), pass.Analyzer, got, want)
return data
} }
} }
@ -319,15 +333,14 @@ func runAnalysis(ctx context.Context, fset *token.FileSet, analyzer *analysis.An
panic(fmt.Sprintf("%s:%s: Pass.ExportPackageFact(%T) called after Run", analyzer.Name, pkg.PkgPath(), fact)) panic(fmt.Sprintf("%s:%s: Pass.ExportPackageFact(%T) called after Run", analyzer.Name, pkg.PkgPath(), fact))
} }
var errors []*source.Error
for _, diag := range diagnostics { for _, diag := range diagnostics {
srcErr, err := sourceError(ctx, fset, pkg, diag) srcErr, err := sourceError(ctx, fset, pkg, diag)
if err != nil { if err != nil {
return nil, nil, nil, nil, err return nil
} }
errors = append(errors, srcErr) data.diagnostics = append(data.diagnostics, srcErr)
} }
return errors, result, objectFacts, packageFacts, err return data
} }
// exportedFrom reports whether obj may be visible to a package that imports pkg. // exportedFrom reports whether obj may be visible to a package that imports pkg.