mirror of https://github.com/golang/go.git
gopls/internal/lsp: add gopls.fetch_vulncheck_result
This CL changes the signature of View's Vulnerabilities and SetVulnerabilities, to use govulncheck.Result instead of []*govulncheck.Vuln. That allows us to hold extra information about the analysis in addition to the list of vulnerabilities. Also, instead of aliasing x/vuln/exp/govulncheck.Result define our own gopls/internal/govulncheck.Result type so the gopls documentation is less confusing. Change-Id: I7a18da9bf047b9ebed6fc0264b5e0f66c04ba3f3 Reviewed-on: https://go-review.googlesource.com/c/tools/+/451696 Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com> Reviewed-by: Suzy Mueller <suzmue@golang.org> Reviewed-by: Robert Findley <rfindley@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> gopls-CI: kokoro <noreply+kokoro@google.com>
This commit is contained in:
parent
8ba9a370ee
commit
4ce4f93a92
|
|
@ -100,6 +100,26 @@ Args:
|
|||
}
|
||||
```
|
||||
|
||||
### **Get known vulncheck result**
|
||||
Identifier: `gopls.fetch_vulncheck_result`
|
||||
|
||||
Fetch the result of latest vulnerability check (`govulncheck`).
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// The file URI.
|
||||
"URI": string,
|
||||
}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
map[golang.org/x/tools/gopls/internal/lsp/protocol.DocumentURI]*golang.org/x/tools/gopls/internal/govulncheck.Result
|
||||
```
|
||||
|
||||
### **Toggle gc_details**
|
||||
Identifier: `gopls.gc_details`
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
// 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 govulncheck
|
||||
|
||||
// Result is the result of vulnerability scanning.
|
||||
type Result struct {
|
||||
// Vulns contains all vulnerabilities that are called or imported by
|
||||
// the analyzed module.
|
||||
Vulns []*Vuln
|
||||
}
|
||||
|
|
@ -8,7 +8,9 @@
|
|||
// Package govulncheck provides an experimental govulncheck API.
|
||||
package govulncheck
|
||||
|
||||
import "golang.org/x/vuln/exp/govulncheck"
|
||||
import (
|
||||
"golang.org/x/vuln/exp/govulncheck"
|
||||
)
|
||||
|
||||
var (
|
||||
// Source reports vulnerabilities that affect the analyzed packages.
|
||||
|
|
@ -22,9 +24,6 @@ type (
|
|||
// Config is the configuration for Main.
|
||||
Config = govulncheck.Config
|
||||
|
||||
// Result is the result of executing Source.
|
||||
Result = govulncheck.Result
|
||||
|
||||
// Vuln represents a single OSV entry.
|
||||
Vuln = govulncheck.Vuln
|
||||
|
||||
|
|
|
|||
|
|
@ -13,13 +13,6 @@ import (
|
|||
"golang.org/x/vuln/osv"
|
||||
)
|
||||
|
||||
// Result is the result of executing Source or Binary.
|
||||
type Result struct {
|
||||
// Vulns contains all vulnerabilities that are called or imported by
|
||||
// the analyzed module.
|
||||
Vulns []*Vuln
|
||||
}
|
||||
|
||||
// Vuln represents a single OSV entry.
|
||||
type Vuln struct {
|
||||
// OSV contains all data from the OSV entry for this vulnerability.
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
|
|||
name: name,
|
||||
folder: folder,
|
||||
moduleUpgrades: map[span.URI]map[string]string{},
|
||||
vulns: map[span.URI][]*govulncheck.Vuln{},
|
||||
vulns: map[span.URI]*govulncheck.Result{},
|
||||
filesByURI: map[span.URI]*fileBase{},
|
||||
filesByBase: map[string][]*fileBase{},
|
||||
rootURI: root,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ type View struct {
|
|||
// Each modfile has a map of module name to upgrade version.
|
||||
moduleUpgrades map[span.URI]map[string]string
|
||||
|
||||
vulns map[span.URI][]*govulncheck.Vuln
|
||||
vulns map[span.URI]*govulncheck.Result
|
||||
|
||||
// keep track of files by uri and by basename, a single file may be mapped
|
||||
// to multiple uris, and the same basename may map to multiple files
|
||||
|
|
@ -1044,16 +1044,24 @@ func (v *View) ClearModuleUpgrades(modfile span.URI) {
|
|||
delete(v.moduleUpgrades, modfile)
|
||||
}
|
||||
|
||||
func (v *View) Vulnerabilities(modfile span.URI) []*govulncheck.Vuln {
|
||||
func (v *View) Vulnerabilities(modfile ...span.URI) map[span.URI]*govulncheck.Result {
|
||||
m := make(map[span.URI]*govulncheck.Result)
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
vulns := make([]*govulncheck.Vuln, len(v.vulns[modfile]))
|
||||
copy(vulns, v.vulns[modfile])
|
||||
return vulns
|
||||
if len(modfile) == 0 {
|
||||
for k, v := range v.vulns {
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
for _, f := range modfile {
|
||||
m[f] = v.vulns[f]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (v *View) SetVulnerabilities(modfile span.URI, vulns []*govulncheck.Vuln) {
|
||||
func (v *View) SetVulnerabilities(modfile span.URI, vulns *govulncheck.Result) {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
|
|
|
|||
|
|
@ -833,6 +833,17 @@ type pkgLoadConfig struct {
|
|||
Tests bool
|
||||
}
|
||||
|
||||
func (c *commandHandler) FetchVulncheckResult(ctx context.Context, arg command.URIArg) (map[protocol.DocumentURI]*govulncheck.Result, error) {
|
||||
ret := map[protocol.DocumentURI]*govulncheck.Result{}
|
||||
err := c.run(ctx, commandConfig{forURI: arg.URI}, func(ctx context.Context, deps commandDeps) error {
|
||||
for modfile, result := range deps.snapshot.View().Vulnerabilities() {
|
||||
ret[protocol.URIFromSpanURI(modfile)] = result
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (c *commandHandler) RunVulncheckExp(ctx context.Context, args command.VulncheckArgs) error {
|
||||
if args.URI == "" {
|
||||
return errors.New("VulncheckArgs is missing URI field")
|
||||
|
|
@ -887,10 +898,10 @@ func (c *commandHandler) RunVulncheckExp(ctx context.Context, args command.Vulnc
|
|||
// TODO: for easy debugging, log the failed stdout somewhere?
|
||||
return fmt.Errorf("failed to parse govulncheck output: %v", err)
|
||||
}
|
||||
vulns := result.Vulns
|
||||
deps.snapshot.View().SetVulnerabilities(args.URI.SpanURI(), vulns)
|
||||
c.s.diagnoseSnapshot(deps.snapshot, nil, false)
|
||||
deps.snapshot.View().SetVulnerabilities(args.URI.SpanURI(), &result)
|
||||
|
||||
c.s.diagnoseSnapshot(deps.snapshot, nil, false)
|
||||
vulns := result.Vulns
|
||||
affecting := make([]string, 0, len(vulns))
|
||||
for _, v := range vulns {
|
||||
if v.IsCalled() {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const (
|
|||
ApplyFix Command = "apply_fix"
|
||||
CheckUpgrades Command = "check_upgrades"
|
||||
EditGoDirective Command = "edit_go_directive"
|
||||
FetchVulncheckResult Command = "fetch_vulncheck_result"
|
||||
GCDetails Command = "gc_details"
|
||||
Generate Command = "generate"
|
||||
GenerateGoplsMod Command = "generate_gopls_mod"
|
||||
|
|
@ -50,6 +51,7 @@ var Commands = []Command{
|
|||
ApplyFix,
|
||||
CheckUpgrades,
|
||||
EditGoDirective,
|
||||
FetchVulncheckResult,
|
||||
GCDetails,
|
||||
Generate,
|
||||
GenerateGoplsMod,
|
||||
|
|
@ -102,6 +104,12 @@ func Dispatch(ctx context.Context, params *protocol.ExecuteCommandParams, s Inte
|
|||
return nil, err
|
||||
}
|
||||
return nil, s.EditGoDirective(ctx, a0)
|
||||
case "gopls.fetch_vulncheck_result":
|
||||
var a0 URIArg
|
||||
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.FetchVulncheckResult(ctx, a0)
|
||||
case "gopls.gc_details":
|
||||
var a0 protocol.DocumentURI
|
||||
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
|
||||
|
|
@ -276,6 +284,18 @@ func NewEditGoDirectiveCommand(title string, a0 EditGoDirectiveArgs) (protocol.C
|
|||
}, nil
|
||||
}
|
||||
|
||||
func NewFetchVulncheckResultCommand(title string, a0 URIArg) (protocol.Command, error) {
|
||||
args, err := MarshalArgs(a0)
|
||||
if err != nil {
|
||||
return protocol.Command{}, err
|
||||
}
|
||||
return protocol.Command{
|
||||
Title: title,
|
||||
Command: "gopls.fetch_vulncheck_result",
|
||||
Arguments: args,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewGCDetailsCommand(title string, a0 protocol.DocumentURI) (protocol.Command, error) {
|
||||
args, err := MarshalArgs(a0)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package command
|
|||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/tools/gopls/internal/govulncheck"
|
||||
"golang.org/x/tools/gopls/internal/lsp/protocol"
|
||||
)
|
||||
|
||||
|
|
@ -153,6 +154,11 @@ type Interface interface {
|
|||
//
|
||||
// Run vulnerability check (`govulncheck`).
|
||||
RunVulncheckExp(context.Context, VulncheckArgs) error
|
||||
|
||||
// FetchVulncheckResult: Get known vulncheck result
|
||||
//
|
||||
// Fetch the result of latest vulnerability check (`govulncheck`).
|
||||
FetchVulncheckResult(context.Context, URIArg) (map[protocol.DocumentURI]*govulncheck.Result, error)
|
||||
}
|
||||
|
||||
type RunTestsArgs struct {
|
||||
|
|
|
|||
|
|
@ -180,14 +180,17 @@ func ModVulnerabilityDiagnostics(ctx context.Context, snapshot source.Snapshot,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
vs := snapshot.View().Vulnerabilities(fh.URI())
|
||||
// TODO(suzmue): should we just store the vulnerabilities like this?
|
||||
vs := snapshot.View().Vulnerabilities(fh.URI())[fh.URI()]
|
||||
if vs == nil || len(vs.Vulns) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type modVuln struct {
|
||||
mod *govulncheck.Module
|
||||
vuln *govulncheck.Vuln
|
||||
}
|
||||
vulnsByModule := make(map[string][]modVuln)
|
||||
for _, vuln := range vs {
|
||||
for _, vuln := range vs.Vulns {
|
||||
for _, mod := range vuln.Modules {
|
||||
vulnsByModule[mod.Path] = append(vulnsByModule[mod.Path], modVuln{mod, vuln})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle,
|
|||
}
|
||||
|
||||
// Get the vulnerability info.
|
||||
affecting, nonaffecting := lookupVulns(snapshot.View().Vulnerabilities(fh.URI()), req.Mod.Path)
|
||||
affecting, nonaffecting := lookupVulns(snapshot.View().Vulnerabilities(fh.URI())[fh.URI()], req.Mod.Path)
|
||||
|
||||
// Get the `go mod why` results for the given file.
|
||||
why, err := snapshot.ModWhy(ctx, fh)
|
||||
|
|
@ -117,8 +117,11 @@ func formatHeader(modpath string, options *source.Options) string {
|
|||
return b.String()
|
||||
}
|
||||
|
||||
func lookupVulns(vulns []*govulncheck.Vuln, modpath string) (affecting, nonaffecting []*govulncheck.Vuln) {
|
||||
for _, vuln := range vulns {
|
||||
func lookupVulns(vulns *govulncheck.Result, modpath string) (affecting, nonaffecting []*govulncheck.Vuln) {
|
||||
if vulns == nil {
|
||||
return nil, nil
|
||||
}
|
||||
for _, vuln := range vulns.Vulns {
|
||||
for _, mod := range vuln.Modules {
|
||||
if mod.Path != modpath {
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -689,6 +689,13 @@ var GeneratedAPIJSON = &APIJSON{
|
|||
Doc: "Runs `go mod edit -go=version` for a module.",
|
||||
ArgDoc: "{\n\t// Any document URI within the relevant module.\n\t\"URI\": string,\n\t// The version to pass to `go mod edit -go`.\n\t\"Version\": string,\n}",
|
||||
},
|
||||
{
|
||||
Command: "gopls.fetch_vulncheck_result",
|
||||
Title: "Get known vulncheck result",
|
||||
Doc: "Fetch the result of latest vulnerability check (`govulncheck`).",
|
||||
ArgDoc: "{\n\t// The file URI.\n\t\"URI\": string,\n}",
|
||||
ResultDoc: "map[golang.org/x/tools/gopls/internal/lsp/protocol.DocumentURI]*golang.org/x/tools/gopls/internal/govulncheck.Result",
|
||||
},
|
||||
{
|
||||
Command: "gopls.gc_details",
|
||||
Title: "Toggle gc_details",
|
||||
|
|
|
|||
|
|
@ -314,11 +314,11 @@ type View interface {
|
|||
// Vulnerabilites returns known vulnerabilities for the given modfile.
|
||||
// TODO(suzmue): replace command.Vuln with a different type, maybe
|
||||
// https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck/govulnchecklib#Summary?
|
||||
Vulnerabilities(modfile span.URI) []*govulncheck.Vuln
|
||||
Vulnerabilities(modfile ...span.URI) map[span.URI]*govulncheck.Result
|
||||
|
||||
// SetVulnerabilities resets the list of vulnerabilites that exists for the given modules
|
||||
// required by modfile.
|
||||
SetVulnerabilities(modfile span.URI, vulnerabilities []*govulncheck.Vuln)
|
||||
SetVulnerabilities(modfile span.URI, vulncheckResult *govulncheck.Result)
|
||||
|
||||
// FileKind returns the type of a file
|
||||
FileKind(FileHandle) FileKind
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"golang.org/x/tools/gopls/internal/govulncheck"
|
||||
"golang.org/x/tools/gopls/internal/lsp/command"
|
||||
"golang.org/x/tools/gopls/internal/lsp/protocol"
|
||||
. "golang.org/x/tools/gopls/internal/lsp/regtest"
|
||||
|
|
@ -180,9 +181,43 @@ func main() {
|
|||
// TODO(hyangah): once the diagnostics are published, wait for diagnostics.
|
||||
ShownMessage("Found GOSTDLIB"),
|
||||
)
|
||||
testFetchVulncheckResult(t, env, map[string][]string{"go.mod": {"GOSTDLIB"}})
|
||||
})
|
||||
}
|
||||
|
||||
func testFetchVulncheckResult(t *testing.T, env *Env, want map[string][]string) {
|
||||
t.Helper()
|
||||
|
||||
var result map[protocol.DocumentURI]*govulncheck.Result
|
||||
fetchCmd, err := command.NewFetchVulncheckResultCommand("fetch", command.URIArg{
|
||||
URI: env.Sandbox.Workdir.URI("go.mod"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
env.ExecuteCommand(&protocol.ExecuteCommandParams{
|
||||
Command: fetchCmd.Command,
|
||||
Arguments: fetchCmd.Arguments,
|
||||
}, &result)
|
||||
|
||||
for _, v := range want {
|
||||
sort.Strings(v)
|
||||
}
|
||||
got := map[string][]string{}
|
||||
for k, r := range result {
|
||||
var osv []string
|
||||
for _, v := range r.Vulns {
|
||||
osv = append(osv, v.OSV.ID)
|
||||
}
|
||||
sort.Strings(osv)
|
||||
modfile := env.Sandbox.Workdir.RelPath(k.SpanURI().Filename())
|
||||
got[modfile] = osv
|
||||
}
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("fetch vulnchheck result = got %v, want %v: diff %v", got, want, diff)
|
||||
}
|
||||
}
|
||||
|
||||
const workspace1 = `
|
||||
-- go.mod --
|
||||
module golang.org/entry
|
||||
|
|
@ -332,6 +367,9 @@ func TestRunVulncheckWarning(t *testing.T) {
|
|||
ReadDiagnostics("go.mod", gotDiagnostics),
|
||||
),
|
||||
)
|
||||
testFetchVulncheckResult(t, env, map[string][]string{
|
||||
"go.mod": {"GO-2022-01", "GO-2022-02", "GO-2022-03"},
|
||||
})
|
||||
env.OpenFile("x/x.go")
|
||||
lineX := env.RegexpSearch("x/x.go", `c\.C1\(\)\.Vuln1\(\)`)
|
||||
env.OpenFile("y/y.go")
|
||||
|
|
@ -470,6 +508,7 @@ func TestRunVulncheckInfo(t *testing.T) {
|
|||
ReadDiagnostics("go.mod", gotDiagnostics)),
|
||||
ShownMessage("No vulnerabilities found")) // only count affecting vulnerabilities.
|
||||
|
||||
testFetchVulncheckResult(t, env, map[string][]string{"go.mod": {"GO-2022-02"}})
|
||||
// wantDiagnostics maps a module path in the require
|
||||
// section of a go.mod to diagnostics that will be returned
|
||||
// when running vulncheck.
|
||||
|
|
|
|||
Loading…
Reference in New Issue