gopls/internal/lsp/cache: wire in mod vulnerability analysis
Add a stub implementation of incremental go.mod vulnerability analysis,
following the pattern of mod tidy analysis.
Change-Id: Iaaea71f6a8a7e735f8b595289a528e78b19e2560
Reviewed-on: https://go-review.googlesource.com/c/tools/+/452770
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
diff --git a/gopls/internal/lsp/cache/mod_vuln.go b/gopls/internal/lsp/cache/mod_vuln.go
new file mode 100644
index 0000000..d783f5d
--- /dev/null
+++ b/gopls/internal/lsp/cache/mod_vuln.go
@@ -0,0 +1,72 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+ "context"
+ "os"
+
+ "golang.org/x/tools/gopls/internal/govulncheck"
+ "golang.org/x/tools/gopls/internal/lsp/source"
+ "golang.org/x/tools/gopls/internal/span"
+ "golang.org/x/tools/gopls/internal/vulncheck"
+ "golang.org/x/tools/internal/memoize"
+)
+
+// ModVuln returns import vulnerability analysis for the given go.mod URI.
+// Concurrent requests are combined into a single command.
+func (s *snapshot) ModVuln(ctx context.Context, modURI span.URI) (*govulncheck.Result, error) {
+ s.mu.Lock()
+ entry, hit := s.modVulnHandles.Get(modURI)
+ s.mu.Unlock()
+
+ type modVuln struct {
+ result *govulncheck.Result
+ err error
+ }
+
+ // Cache miss?
+ if !hit {
+ // If the file handle is an overlay, it may not be written to disk.
+ // The go.mod file has to be on disk for vulncheck to work.
+ //
+ // TODO(hyangah): use overlays for vulncheck.
+ fh, err := s.GetFile(ctx, modURI)
+ if err != nil {
+ return nil, err
+ }
+ if _, ok := fh.(*overlay); ok {
+ if info, _ := os.Stat(modURI.Filename()); info == nil {
+ return nil, source.ErrNoModOnDisk
+ }
+ }
+
+ handle := memoize.NewPromise("modVuln", func(ctx context.Context, arg interface{}) interface{} {
+ result, err := modVulnImpl(ctx, arg.(*snapshot), modURI)
+ return modVuln{result, err}
+ })
+
+ entry = handle
+ s.mu.Lock()
+ s.modVulnHandles.Set(modURI, entry, nil)
+ s.mu.Unlock()
+ }
+
+ // Await result.
+ v, err := s.awaitPromise(ctx, entry.(*memoize.Promise))
+ if err != nil {
+ return nil, err
+ }
+ res := v.(modVuln)
+ return res.result, res.err
+}
+
+func modVulnImpl(ctx context.Context, s *snapshot, uri span.URI) (*govulncheck.Result, error) {
+ fh, err := s.GetFile(ctx, uri)
+ if err != nil {
+ return nil, err
+ }
+ return vulncheck.AnalyzeVulnerableImports(ctx, s, fh)
+}
diff --git a/gopls/internal/lsp/cache/session.go b/gopls/internal/lsp/cache/session.go
index 4916f42..28acd5d 100644
--- a/gopls/internal/lsp/cache/session.go
+++ b/gopls/internal/lsp/cache/session.go
@@ -283,6 +283,7 @@
parseModHandles: persistent.NewMap(uriLessInterface),
parseWorkHandles: persistent.NewMap(uriLessInterface),
modTidyHandles: persistent.NewMap(uriLessInterface),
+ modVulnHandles: persistent.NewMap(uriLessInterface),
modWhyHandles: persistent.NewMap(uriLessInterface),
knownSubdirs: newKnownDirsSet(),
workspace: workspace,
diff --git a/gopls/internal/lsp/cache/snapshot.go b/gopls/internal/lsp/cache/snapshot.go
index c2cb946..9a4c5b3 100644
--- a/gopls/internal/lsp/cache/snapshot.go
+++ b/gopls/internal/lsp/cache/snapshot.go
@@ -140,6 +140,7 @@
// the view's go.mod file.
modTidyHandles *persistent.Map // from span.URI to *memoize.Promise[modTidyResult]
modWhyHandles *persistent.Map // from span.URI to *memoize.Promise[modWhyResult]
+ modVulnHandles *persistent.Map // from span.URI to *memoize.Promise[modVulnResult]
workspace *workspace // (not guarded by mu)
@@ -230,6 +231,7 @@
s.parseModHandles.Destroy()
s.parseWorkHandles.Destroy()
s.modTidyHandles.Destroy()
+ s.modVulnHandles.Destroy()
s.modWhyHandles.Destroy()
if s.workspaceDir != "" {
@@ -1713,6 +1715,7 @@
parseWorkHandles: s.parseWorkHandles.Clone(),
modTidyHandles: s.modTidyHandles.Clone(),
modWhyHandles: s.modWhyHandles.Clone(),
+ modVulnHandles: s.modVulnHandles.Clone(),
knownSubdirs: s.knownSubdirs.Clone(),
workspace: newWorkspace,
}
@@ -1752,6 +1755,7 @@
// Invalidate go.mod-related handles.
result.modTidyHandles.Delete(uri)
result.modWhyHandles.Delete(uri)
+ result.modVulnHandles.Delete(uri)
// Invalidate handles for cached symbols.
result.symbolizeHandles.Delete(uri)
@@ -1815,9 +1819,9 @@
// TODO(maybe): Only delete mod handles for
// which the withoutURI is relevant.
// Requires reverse-engineering the go command. (!)
-
result.modTidyHandles.Clear()
result.modWhyHandles.Clear()
+ result.modVulnHandles.Clear()
}
result.parseModHandles.Delete(uri)