mirror of https://github.com/golang/go.git
internal/lsp/command: add RunVulncheckExp
This is a command that runs govulncheck-like analysis. This is highly experimental and can change any time, so we mark it with the "Exp" suffix. Once the interface becomes stable, we will rename this command. It returns VulncheckResult that can be encoded as a JSON message. The result includes all potentially affecting vulnerabilities, and sample traces. This feature is currently available only when gopls is compiled with go1.18. Otherwise, the command will return an error. Updates golang/go#50577 Updates golang/vscode-go#2096 Change-Id: Ia37b0555f7bf98760292c9f68e50fb70dd494522 Reviewed-on: https://go-review.googlesource.com/c/tools/+/395576 Trust: Hyang-Ah Hana Kim <hyangah@gmail.com> Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com> Reviewed-by: Jonathan Amsterdam <jba@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
parent
4737f45953
commit
cd31eaad03
|
|
@ -265,6 +265,41 @@ Args:
|
|||
}
|
||||
```
|
||||
|
||||
### **Run vulncheck (experimental)**
|
||||
Identifier: `gopls.run_vulncheck_exp`
|
||||
|
||||
Run vulnerability check (`govulncheck`).
|
||||
|
||||
Args:
|
||||
|
||||
```
|
||||
{
|
||||
// Dir is the directory from which vulncheck will run from.
|
||||
"Dir": string,
|
||||
// Package pattern. E.g. "", ".", "./...".
|
||||
"Pattern": string,
|
||||
}
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```
|
||||
{
|
||||
"Vuln": []{
|
||||
"id": string,
|
||||
"details": string,
|
||||
"aliases": []string,
|
||||
"symbol": string,
|
||||
"pkg_path": string,
|
||||
"mod_path": string,
|
||||
"url": string,
|
||||
"current_version": string,
|
||||
"fixed_version": string,
|
||||
"call_stacks": [][]golang.org/x/tools/internal/lsp/command.StackEntry,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### **Start the gopls debug server**
|
||||
Identifier: `gopls.start_debugging`
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ package hooks // import "golang.org/x/tools/gopls/internal/hooks"
|
|||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/tools/gopls/internal/vulncheck"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"mvdan.cc/gofumpt/format"
|
||||
"mvdan.cc/xurls/v2"
|
||||
|
|
@ -28,4 +29,6 @@ func Options(options *source.Options) {
|
|||
})
|
||||
}
|
||||
updateAnalyzers(options)
|
||||
|
||||
options.Govulncheck = vulncheck.Govulncheck
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,13 @@
|
|||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
// Package vulncheck provides an analysis command
|
||||
// that runs vulnerability analysis using data from
|
||||
// golang.org/x/exp/vulncheck.
|
||||
// This package requires go1.18 or newer.
|
||||
package vulncheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/vulncheck"
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
|
@ -21,6 +19,42 @@ import (
|
|||
"golang.org/x/vuln/client"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Govulncheck = govulncheck
|
||||
}
|
||||
|
||||
func govulncheck(ctx context.Context, cfg *packages.Config, args command.VulncheckArgs) (res command.VulncheckResult, _ error) {
|
||||
if args.Pattern == "" {
|
||||
args.Pattern = "."
|
||||
}
|
||||
|
||||
dbClient, err := client.NewClient(findGOVULNDB(cfg), client.Options{HTTPCache: defaultCache()})
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
c := cmd{Client: dbClient}
|
||||
vulns, err := c.Run(ctx, cfg, args.Pattern)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.Vuln = vulns
|
||||
return res, err
|
||||
}
|
||||
|
||||
func findGOVULNDB(cfg *packages.Config) []string {
|
||||
for _, kv := range cfg.Env {
|
||||
if strings.HasPrefix(kv, "GOVULNDB=") {
|
||||
return strings.Split(kv[len("GOVULNDB="):], ",")
|
||||
}
|
||||
}
|
||||
if GOVULNDB := os.Getenv("GOVULNDB"); GOVULNDB != "" {
|
||||
return strings.Split(GOVULNDB, ",")
|
||||
}
|
||||
return []string{"https://storage.googleapis.com/go-vulndb"}
|
||||
}
|
||||
|
||||
type Vuln = command.Vuln
|
||||
type CallStack = command.CallStack
|
||||
type StackEntry = command.StackEntry
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
// 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 vulncheck provides an analysis command
|
||||
// that runs vulnerability analysis using data from
|
||||
// golang.org/x/exp/vulncheck.
|
||||
// This package requires go1.18 or newer.
|
||||
package vulncheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/lsp/command"
|
||||
)
|
||||
|
||||
// Govulncheck runs the in-process govulncheck implementation.
|
||||
// With go1.18+, this is swapped with the real implementation.
|
||||
var Govulncheck = func(ctx context.Context, cfg *packages.Config, args command.VulncheckArgs) (res command.VulncheckResult, _ error) {
|
||||
return res, errors.New("not implemented")
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
"golang.org/x/mod/modfile"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/event"
|
||||
"golang.org/x/tools/internal/gocommand"
|
||||
"golang.org/x/tools/internal/lsp/command"
|
||||
|
|
@ -781,3 +782,35 @@ func (c *commandHandler) StartDebugging(ctx context.Context, args command.Debugg
|
|||
result.URLs = []string{"http://" + listenedAddr}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *commandHandler) RunVulncheckExp(ctx context.Context, args command.VulncheckArgs) (result command.VulncheckResult, _ error) {
|
||||
err := c.run(ctx, commandConfig{
|
||||
progress: "Running vulncheck",
|
||||
requireSave: true,
|
||||
forURI: args.Dir, // Will dir work?
|
||||
}, func(ctx context.Context, deps commandDeps) error {
|
||||
view := deps.snapshot.View()
|
||||
opts := view.Options()
|
||||
if opts == nil || opts.Hooks.Govulncheck == nil {
|
||||
return errors.New("vulncheck feature is not available")
|
||||
}
|
||||
|
||||
buildFlags := opts.BuildFlags // XXX: is session.Options equivalent to view.Options?
|
||||
var viewEnv []string
|
||||
if e := opts.EnvSlice(); e != nil {
|
||||
viewEnv = append(os.Environ(), e...)
|
||||
}
|
||||
cfg := &packages.Config{
|
||||
Context: ctx,
|
||||
Tests: true, // TODO(hyangah): add a field in args.
|
||||
BuildFlags: buildFlags,
|
||||
Env: viewEnv,
|
||||
Dir: view.Folder().Filename(),
|
||||
// TODO(hyangah): configure overlay
|
||||
}
|
||||
var err error
|
||||
result, err = opts.Hooks.Govulncheck(ctx, cfg, args)
|
||||
return err
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ const (
|
|||
RegenerateCgo Command = "regenerate_cgo"
|
||||
RemoveDependency Command = "remove_dependency"
|
||||
RunTests Command = "run_tests"
|
||||
RunVulncheckExp Command = "run_vulncheck_exp"
|
||||
StartDebugging Command = "start_debugging"
|
||||
Test Command = "test"
|
||||
Tidy Command = "tidy"
|
||||
|
|
@ -57,6 +58,7 @@ var Commands = []Command{
|
|||
RegenerateCgo,
|
||||
RemoveDependency,
|
||||
RunTests,
|
||||
RunVulncheckExp,
|
||||
StartDebugging,
|
||||
Test,
|
||||
Tidy,
|
||||
|
|
@ -152,6 +154,12 @@ func Dispatch(ctx context.Context, params *protocol.ExecuteCommandParams, s Inte
|
|||
return nil, err
|
||||
}
|
||||
return nil, s.RunTests(ctx, a0)
|
||||
case "gopls.run_vulncheck_exp":
|
||||
var a0 VulncheckArgs
|
||||
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.RunVulncheckExp(ctx, a0)
|
||||
case "gopls.start_debugging":
|
||||
var a0 DebuggingArgs
|
||||
if err := UnmarshalArgs(params.Arguments, &a0); err != nil {
|
||||
|
|
@ -368,6 +376,18 @@ func NewRunTestsCommand(title string, a0 RunTestsArgs) (protocol.Command, error)
|
|||
}, nil
|
||||
}
|
||||
|
||||
func NewRunVulncheckExpCommand(title string, a0 VulncheckArgs) (protocol.Command, error) {
|
||||
args, err := MarshalArgs(a0)
|
||||
if err != nil {
|
||||
return protocol.Command{}, err
|
||||
}
|
||||
return protocol.Command{
|
||||
Title: title,
|
||||
Command: "gopls.run_vulncheck_exp",
|
||||
Arguments: args,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewStartDebuggingCommand(title string, a0 DebuggingArgs) (protocol.Command, error) {
|
||||
args, err := MarshalArgs(a0)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -143,6 +143,11 @@ type Interface interface {
|
|||
// Start the gopls debug server if it isn't running, and return the debug
|
||||
// address.
|
||||
StartDebugging(context.Context, DebuggingArgs) (DebuggingResult, error)
|
||||
|
||||
// RunVulncheckExp: Run vulncheck (experimental)
|
||||
//
|
||||
// Run vulnerability check (`govulncheck`).
|
||||
RunVulncheckExp(context.Context, VulncheckArgs) (VulncheckResult, error)
|
||||
}
|
||||
|
||||
type RunTestsArgs struct {
|
||||
|
|
|
|||
|
|
@ -665,6 +665,13 @@ var GeneratedAPIJSON = &APIJSON{
|
|||
Doc: "Runs `go test` for a specific set of test or benchmark functions.",
|
||||
ArgDoc: "{\n\t// The test file containing the tests to run.\n\t\"URI\": string,\n\t// Specific test names to run, e.g. TestFoo.\n\t\"Tests\": []string,\n\t// Specific benchmarks to run, e.g. BenchmarkFoo.\n\t\"Benchmarks\": []string,\n}",
|
||||
},
|
||||
{
|
||||
Command: "gopls.run_vulncheck_exp",
|
||||
Title: "Run vulncheck (experimental)",
|
||||
Doc: "Run vulnerability check (`govulncheck`).",
|
||||
ArgDoc: "{\n\t// Dir is the directory from which vulncheck will run from.\n\t\"Dir\": string,\n\t// Package pattern. E.g. \"\", \".\", \"./...\".\n\t\"Pattern\": string,\n}",
|
||||
ResultDoc: "{\n\t\"Vuln\": []{\n\t\t\"id\": string,\n\t\t\"details\": string,\n\t\t\"aliases\": []string,\n\t\t\"symbol\": string,\n\t\t\"pkg_path\": string,\n\t\t\"mod_path\": string,\n\t\t\"url\": string,\n\t\t\"current_version\": string,\n\t\t\"fixed_version\": string,\n\t\t\"call_stacks\": [][]golang.org/x/tools/internal/lsp/command.StackEntry,\n\t},\n}",
|
||||
},
|
||||
{
|
||||
Command: "gopls.start_debugging",
|
||||
Title: "Start the gopls debug server",
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import (
|
|||
"golang.org/x/tools/go/analysis/passes/unsafeptr"
|
||||
"golang.org/x/tools/go/analysis/passes/unusedresult"
|
||||
"golang.org/x/tools/go/analysis/passes/unusedwrite"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/lsp/analysis/fillreturns"
|
||||
"golang.org/x/tools/internal/lsp/analysis/fillstruct"
|
||||
"golang.org/x/tools/internal/lsp/analysis/infertypeargs"
|
||||
|
|
@ -476,6 +477,9 @@ type Hooks struct {
|
|||
TypeErrorAnalyzers map[string]*Analyzer
|
||||
ConvenienceAnalyzers map[string]*Analyzer
|
||||
StaticcheckAnalyzers map[string]*Analyzer
|
||||
|
||||
// Govulncheck is the implementation of the Govulncheck gopls command.
|
||||
Govulncheck func(context.Context, *packages.Config, command.VulncheckArgs) (command.VulncheckResult, error)
|
||||
}
|
||||
|
||||
// InternalOptions contains settings that are not intended for use by the
|
||||
|
|
@ -703,6 +707,7 @@ func (o *Options) Clone() *Options {
|
|||
ComputeEdits: o.ComputeEdits,
|
||||
GofumptFormat: o.GofumptFormat,
|
||||
URLRegexp: o.URLRegexp,
|
||||
Govulncheck: o.Govulncheck,
|
||||
},
|
||||
ServerOptions: o.ServerOptions,
|
||||
UserOptions: o.UserOptions,
|
||||
|
|
|
|||
Loading…
Reference in New Issue