gopls/internal/lsp/cache: fix crash in Session.updateViewLocked
The createView release function should not be called until
we have finished using the view's overlays.
Change-Id: I988c7f5e8fa8bc41108b491286501881b03a535f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/496884
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
Run-TryBot: Alan Donovan <adonovan@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/internal/lsp/cache/session.go b/gopls/internal/lsp/cache/session.go
index e13f4c8..8eae64e 100644
--- a/gopls/internal/lsp/cache/session.go
+++ b/gopls/internal/lsp/cache/session.go
@@ -109,13 +109,14 @@
// TODO(rfindley): clarify that createView can never be cancelled (with the
// possible exception of server shutdown).
+// On success, the caller becomes responsible for calling the release function once.
func (s *Session) createView(ctx context.Context, name string, folder span.URI, options *source.Options, seqID uint64) (*View, *snapshot, func(), error) {
index := atomic.AddInt64(&viewIndex, 1)
// Get immutable workspace information.
info, err := s.getWorkspaceInformation(ctx, folder, options)
if err != nil {
- return nil, nil, func() {}, err
+ return nil, nil, nil, err
}
gowork, _ := info.GOWORK()
@@ -327,6 +328,15 @@
}
v, snapshot, release, err := s.createView(ctx, view.name, view.folder, options, seqID)
+ if err != nil {
+ // we have dropped the old view, but could not create the new one
+ // this should not happen and is very bad, but we still need to clean
+ // up the view array if it happens
+ s.views = removeElement(s.views, i)
+ return nil, err
+ }
+ defer release()
+
// The new snapshot has lost the history of the previous view. As a result,
// it may not see open files that aren't in its build configuration (as it
// would have done via didOpen notifications). This can lead to inconsistent
@@ -336,15 +346,7 @@
for _, o := range v.fs.Overlays() {
_, _ = snapshot.ReadFile(ctx, o.URI())
}
- release()
- if err != nil {
- // we have dropped the old view, but could not create the new one
- // this should not happen and is very bad, but we still need to clean
- // up the view array if it happens
- s.views = removeElement(s.views, i)
- return nil, err
- }
// substitute the new view into the array where the old view was
s.views[i] = v
return v, nil