Skip to content

Commit 6c27717

Browse files
committed
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 <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Suzy Mueller <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 763f65c commit 6c27717

File tree

7 files changed

+97
-15
lines changed

7 files changed

+97
-15
lines changed

gopls/doc/settings.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,11 @@ Runs `go generate` for a given directory.
503503
Identifier: `regenerate_cgo`
504504

505505
Regenerates cgo definitions.
506+
### **Run vulncheck (experimental)**
507+
508+
Identifier: `run_vulncheck_exp`
509+
510+
Run vulnerability check (`govulncheck`).
506511
### **Run test(s) (legacy)**
507512

508513
Identifier: `test`
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
[{"id":"STD","affected":[{"package":{"name":"archive/zip"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.18.0"}]}],"ecosystem_specific":{"symbols":["OpenReader"]}}]}]
1+
[{"id":"GO-0000-001","affected":[{"package":{"name":"archive/zip"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.18.0"}]}],"ecosystem_specific":{"symbols":["OpenReader"]}}]}]

gopls/internal/regtest/misc/vuln_test.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ import (
6161
)
6262
6363
func main() {
64-
_, err := zip.OpenReader("file.zip") // vulnerable.
64+
_, err := zip.OpenReader("file.zip") // vulnerability GO-0000-001
6565
fmt.Println(err)
6666
}
6767
`
@@ -79,22 +79,39 @@ func main() {
7979
"GOVERSION": "go1.18",
8080
"_GOPLS_TEST_BINARY_RUN_AS_GOPLS": "true",
8181
},
82+
Settings{
83+
"codelenses": map[string]bool{
84+
"run_vulncheck_exp": true,
85+
},
86+
},
8287
).Run(t, files, func(t *testing.T, env *Env) {
83-
cmd, err := command.NewRunVulncheckExpCommand("Run Vulncheck Exp", command.VulncheckArgs{
84-
URI: protocol.URIFromPath(env.Sandbox.Workdir.AbsPath("go.mod")),
85-
Pattern: "./...",
86-
})
87-
if err != nil {
88-
t.Fatal(err)
89-
}
88+
env.OpenFile("go.mod")
9089

90+
// Test CodeLens is present.
91+
lenses := env.CodeLens("go.mod")
92+
93+
const wantCommand = "gopls." + string(command.RunVulncheckExp)
94+
var gotCodelens = false
95+
var lens protocol.CodeLens
96+
for _, l := range lenses {
97+
if l.Command.Command == wantCommand {
98+
gotCodelens = true
99+
lens = l
100+
break
101+
}
102+
}
103+
if !gotCodelens {
104+
t.Fatal("got no vulncheck codelens")
105+
}
106+
// Run Command included in the codelens.
91107
env.ExecuteCommand(&protocol.ExecuteCommandParams{
92-
Command: command.RunVulncheckExp.ID(),
93-
Arguments: cmd.Arguments,
108+
Command: lens.Command.Command,
109+
Arguments: lens.Command.Arguments,
94110
}, nil)
95111
env.Await(
96112
CompletedWork("Checking vulnerability", 1, true),
97113
// TODO(hyangah): once the diagnostics are published, wait for diagnostics.
114+
ShownMessage("Found GO-0000-001"),
98115
)
99116
})
100117
}

internal/lsp/command.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -823,8 +823,6 @@ func (c *commandHandler) RunVulncheckExp(ctx context.Context, args command.Vulnc
823823
}
824824

825825
cmd := exec.Command(os.Args[0], "vulncheck", "-config", args.Pattern)
826-
// TODO(hyangah): if args.URI is not go.mod file, we need to
827-
// adjust the directory accordingly.
828826
cmd.Dir = filepath.Dir(args.URI.SpanURI().Filename())
829827

830828
var viewEnv []string
@@ -860,8 +858,32 @@ func (c *commandHandler) RunVulncheckExp(ctx context.Context, args command.Vulnc
860858
return fmt.Errorf("failed to parse govulncheck output: %v", err)
861859
}
862860

863-
// TODO(hyangah): convert the results to diagnostics & code actions.
864-
return nil
861+
// TODO(jamalc,suzmue): convert the results to diagnostics & code actions.
862+
// Or should we just write to a file (*.vulncheck.json) or text format
863+
// and send "Show Document" request? If *.vulncheck.json is open,
864+
// VSCode Go extension will open its custom editor.
865+
set := make(map[string]bool)
866+
for _, v := range vulns.Vuln {
867+
if len(v.CallStackSummaries) > 0 {
868+
set[v.ID] = true
869+
}
870+
}
871+
if len(set) == 0 {
872+
return c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
873+
Type: protocol.Info,
874+
Message: "No vulnerabilities found",
875+
})
876+
}
877+
878+
list := make([]string, 0, len(set))
879+
for k := range set {
880+
list = append(list, k)
881+
}
882+
sort.Strings(list)
883+
return c.s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
884+
Type: protocol.Warning,
885+
Message: fmt.Sprintf("Found %v", strings.Join(list, ", ")),
886+
})
865887
})
866888
return err
867889
}

internal/lsp/mod/code_lens.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func LensFuncs() map[command.Command]source.LensFunc {
2222
command.UpgradeDependency: upgradeLenses,
2323
command.Tidy: tidyLens,
2424
command.Vendor: vendorLens,
25+
command.RunVulncheckExp: vulncheckLenses,
2526
}
2627
}
2728

@@ -151,3 +152,29 @@ func firstRequireRange(fh source.FileHandle, pm *source.ParsedModule) (protocol.
151152
}
152153
return source.LineToRange(pm.Mapper, fh.URI(), start, end)
153154
}
155+
156+
func vulncheckLenses(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
157+
pm, err := snapshot.ParseMod(ctx, fh)
158+
if err != nil || pm.File == nil {
159+
return nil, err
160+
}
161+
// Place the codelenses near the module statement.
162+
// A module may not have the require block,
163+
// but vulnerabilities can exist in standard libraries.
164+
uri := protocol.URIFromSpanURI(fh.URI())
165+
rng, err := moduleStmtRange(fh, pm)
166+
if err != nil {
167+
return nil, err
168+
}
169+
170+
vulncheck, err := command.NewRunVulncheckExpCommand("Run govulncheck", command.VulncheckArgs{
171+
URI: uri,
172+
Pattern: "./...",
173+
})
174+
if err != nil {
175+
return nil, err
176+
}
177+
return []protocol.CodeLens{
178+
{Range: rng, Command: vulncheck},
179+
}, nil
180+
}

internal/lsp/source/api_json.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/lsp/source/options.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ func DefaultOptions() *Options {
155155
string(command.GCDetails): false,
156156
string(command.UpgradeDependency): true,
157157
string(command.Vendor): true,
158+
// TODO(hyangah): enable command.RunVulncheckExp.
158159
},
159160
},
160161
},

0 commit comments

Comments
 (0)