internal/lsp/mod/code_lens: add "run govulncheck" codelens
And, make gopls.run_vulncheck_exp show an information/error
message popup after a successful run. This is temporary.
We plan to publish the results as diagnostics and quick-fix.
Finally, changed the stdlib vulnerability info id in
testdata to GO-0000-0001 which looks more like a vulnerability
ID than STD.
Changed TestRunVulncheckExp to include tests on codelens
and use the command included in the codelens, instead of
directly calling the gopls.run_vulncheck_exp command.
Change-Id: Iaf91e4e61b2dfc1e050b887946a69efd3e3785b0
Reviewed-on: https://go-review.googlesource.com/c/tools/+/420995
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Suzy Mueller <suzmue@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md
index 9bf6e07..890a0a3 100644
--- a/gopls/doc/settings.md
+++ b/gopls/doc/settings.md
@@ -503,6 +503,11 @@
Identifier: `regenerate_cgo`
Regenerates cgo definitions.
+### **Run vulncheck (experimental)**
+
+Identifier: `run_vulncheck_exp`
+
+Run vulnerability check (`govulncheck`).
### **Run test(s) (legacy)**
Identifier: `test`
diff --git a/gopls/internal/regtest/misc/testdata/vulndb/stdlib.json b/gopls/internal/regtest/misc/testdata/vulndb/stdlib.json
index 240ee49..7cbfafc 100644
--- a/gopls/internal/regtest/misc/testdata/vulndb/stdlib.json
+++ b/gopls/internal/regtest/misc/testdata/vulndb/stdlib.json
@@ -1 +1 @@
-[{"id":"STD","affected":[{"package":{"name":"archive/zip"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.18.0"}]}],"ecosystem_specific":{"symbols":["OpenReader"]}}]}]
+[{"id":"GO-0000-001","affected":[{"package":{"name":"archive/zip"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.18.0"}]}],"ecosystem_specific":{"symbols":["OpenReader"]}}]}]
diff --git a/gopls/internal/regtest/misc/vuln_test.go b/gopls/internal/regtest/misc/vuln_test.go
index 9de68b6..78c193e 100644
--- a/gopls/internal/regtest/misc/vuln_test.go
+++ b/gopls/internal/regtest/misc/vuln_test.go
@@ -61,7 +61,7 @@
)
func main() {
- _, err := zip.OpenReader("file.zip") // vulnerable.
+ _, err := zip.OpenReader("file.zip") // vulnerability GO-0000-001
fmt.Println(err)
}
`
@@ -79,22 +79,39 @@
"GOVERSION": "go1.18",
"_GOPLS_TEST_BINARY_RUN_AS_GOPLS": "true",
},
+ Settings{
+ "codelenses": map[string]bool{
+ "run_vulncheck_exp": true,
+ },
+ },
).Run(t, files, func(t *testing.T, env *Env) {
- cmd, err := command.NewRunVulncheckExpCommand("Run Vulncheck Exp", command.VulncheckArgs{
- URI: protocol.URIFromPath(env.Sandbox.Workdir.AbsPath("go.mod")),
- Pattern: "./...",
- })
- if err != nil {
- t.Fatal(err)
- }
+ env.OpenFile("go.mod")
+ // Test CodeLens is present.
+ lenses := env.CodeLens("go.mod")
+
+ const wantCommand = "gopls." + string(command.RunVulncheckExp)
+ var gotCodelens = false
+ var lens protocol.CodeLens
+ for _, l := range lenses {
+ if l.Command.Command == wantCommand {
+ gotCodelens = true
+ lens = l
+ break
+ }
+ }
+ if !gotCodelens {
+ t.Fatal("got no vulncheck codelens")
+ }
+ // Run Command included in the codelens.
env.ExecuteCommand(&protocol.ExecuteCommandParams{
- Command: command.RunVulncheckExp.ID(),
- Arguments: cmd.Arguments,
+ Command: lens.Command.Command,
+ Arguments: lens.Command.Arguments,
}, nil)
env.Await(
CompletedWork("Checking vulnerability", 1, true),
// TODO(hyangah): once the diagnostics are published, wait for diagnostics.
+ ShownMessage("Found GO-0000-001"),
)
})
}
diff --git a/internal/lsp/command.go b/internal/lsp/command.go
index 7348c17..6df909b 100644
--- a/internal/lsp/command.go
+++ b/internal/lsp/command.go
@@ -823,8 +823,6 @@
}
cmd := exec.Command(os.Args[0], "vulncheck", "-config", args.Pattern)
- // TODO(hyangah): if args.URI is not go.mod file, we need to
- // adjust the directory accordingly.
cmd.Dir = filepath.Dir(args.URI.SpanURI().Filename())
var viewEnv []string
@@ -860,8 +858,32 @@
return fmt.Errorf("failed to parse govulncheck output: %v", err)
}
- // TODO(hyangah): convert the results to diagnostics & code actions.
- return nil
+ // TODO(jamalc,suzmue): convert the results to diagnostics & code actions.
+ // Or should we just write to a file (*.vulncheck.json) or text format
+ // and send "Show Document" request? If *.vulncheck.json is open,
+ // VSCode Go extension will open its custom editor.
+ set := make(map[string]bool)
+ for _, v := range vulns.Vuln {
+ if len(v.CallStackSummaries) > 0 {
+ set[v.ID] = true
+ }
+ }
+ if len(set) == 0 {
+ return c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
+ Type: protocol.Info,
+ Message: "No vulnerabilities found",
+ })
+ }
+
+ list := make([]string, 0, len(set))
+ for k := range set {
+ list = append(list, k)
+ }
+ sort.Strings(list)
+ return c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
+ Type: protocol.Warning,
+ Message: fmt.Sprintf("Found %v", strings.Join(list, ", ")),
+ })
})
return err
}
diff --git a/internal/lsp/mod/code_lens.go b/internal/lsp/mod/code_lens.go
index b26bae7..1de25c2 100644
--- a/internal/lsp/mod/code_lens.go
+++ b/internal/lsp/mod/code_lens.go
@@ -22,6 +22,7 @@
command.UpgradeDependency: upgradeLenses,
command.Tidy: tidyLens,
command.Vendor: vendorLens,
+ command.RunVulncheckExp: vulncheckLenses,
}
}
@@ -151,3 +152,29 @@
}
return source.LineToRange(pm.Mapper, fh.URI(), start, end)
}
+
+func vulncheckLenses(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
+ pm, err := snapshot.ParseMod(ctx, fh)
+ if err != nil || pm.File == nil {
+ return nil, err
+ }
+ // Place the codelenses near the module statement.
+ // A module may not have the require block,
+ // but vulnerabilities can exist in standard libraries.
+ uri := protocol.URIFromSpanURI(fh.URI())
+ rng, err := moduleStmtRange(fh, pm)
+ if err != nil {
+ return nil, err
+ }
+
+ vulncheck, err := command.NewRunVulncheckExpCommand("Run govulncheck", command.VulncheckArgs{
+ URI: uri,
+ Pattern: "./...",
+ })
+ if err != nil {
+ return nil, err
+ }
+ return []protocol.CodeLens{
+ {Range: rng, Command: vulncheck},
+ }, nil
+}
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
index cf75792..0b3b3d1 100755
--- a/internal/lsp/source/api_json.go
+++ b/internal/lsp/source/api_json.go
@@ -583,6 +583,11 @@
Default: "true",
},
{
+ Name: "\"run_vulncheck_exp\"",
+ Doc: "Run vulnerability check (`govulncheck`).",
+ Default: "false",
+ },
+ {
Name: "\"test\"",
Doc: "Runs `go test` for a specific set of test or benchmark functions.",
Default: "false",
@@ -808,6 +813,11 @@
Doc: "Regenerates cgo definitions.",
},
{
+ Lens: "run_vulncheck_exp",
+ Title: "Run vulncheck (experimental)",
+ Doc: "Run vulnerability check (`govulncheck`).",
+ },
+ {
Lens: "test",
Title: "Run test(s) (legacy)",
Doc: "Runs `go test` for a specific set of test or benchmark functions.",
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 126752b..2f40b59 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -155,6 +155,7 @@
string(command.GCDetails): false,
string(command.UpgradeDependency): true,
string(command.Vendor): true,
+ // TODO(hyangah): enable command.RunVulncheckExp.
},
},
},