golang.org/x/tools/gopls@v0.15.3/internal/vulncheck/scan/command.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build go1.18 6 // +build go1.18 7 8 package scan 9 10 import ( 11 "bytes" 12 "context" 13 "fmt" 14 "io" 15 "os" 16 "os/exec" 17 "sort" 18 "time" 19 20 "golang.org/x/sync/errgroup" 21 "golang.org/x/tools/gopls/internal/cache" 22 "golang.org/x/tools/gopls/internal/vulncheck" 23 "golang.org/x/tools/gopls/internal/vulncheck/govulncheck" 24 "golang.org/x/tools/gopls/internal/vulncheck/osv" 25 "golang.org/x/vuln/scan" 26 ) 27 28 // Main implements gopls vulncheck. 29 func Main(ctx context.Context, args ...string) error { 30 // wrapping govulncheck. 31 cmd := scan.Command(ctx, args...) 32 if err := cmd.Start(); err != nil { 33 return err 34 } 35 return cmd.Wait() 36 } 37 38 // RunGovulncheck implements the codelens "Run Govulncheck" 39 // that runs 'gopls vulncheck' and converts the output to gopls's internal data 40 // used for diagnostics and hover message construction. 41 // 42 // TODO(rfindley): this should accept a *View (which exposes) Options, rather 43 // than a snapshot. 44 func RunGovulncheck(ctx context.Context, pattern string, snapshot *cache.Snapshot, dir string, log io.Writer) (*vulncheck.Result, error) { 45 vulncheckargs := []string{ 46 "vulncheck", "--", 47 "-json", 48 "-mode", "source", 49 "-scan", "symbol", 50 } 51 if dir != "" { 52 vulncheckargs = append(vulncheckargs, "-C", dir) 53 } 54 if db := cache.GetEnv(snapshot, "GOVULNDB"); db != "" { 55 vulncheckargs = append(vulncheckargs, "-db", db) 56 } 57 vulncheckargs = append(vulncheckargs, pattern) 58 // TODO: support -tags. need to compute tags args from opts.BuildFlags. 59 // TODO: support -test. 60 61 ir, iw := io.Pipe() 62 handler := &govulncheckHandler{logger: log, osvs: map[string]*osv.Entry{}} 63 64 stderr := new(bytes.Buffer) 65 var g errgroup.Group 66 // We run the govulncheck's analysis in a separate process as it can 67 // consume a lot of CPUs and memory, and terminates: a separate process 68 // is a perfect garbage collector and affords us ways to limit its resource usage. 69 g.Go(func() error { 70 defer iw.Close() 71 72 cmd := exec.CommandContext(ctx, os.Args[0], vulncheckargs...) 73 cmd.Env = getEnvSlices(snapshot) 74 if goversion := cache.GetEnv(snapshot, cache.GoVersionForVulnTest); goversion != "" { 75 // Let govulncheck API use a different Go version using the (undocumented) hook 76 // in https://go.googlesource.com/vuln/+/v1.0.1/internal/scan/run.go#76 77 cmd.Env = append(cmd.Env, "GOVERSION="+goversion) 78 } 79 cmd.Stderr = stderr // stream vulncheck's STDERR as progress reports 80 cmd.Stdout = iw // let the other goroutine parses the result. 81 82 if err := cmd.Start(); err != nil { 83 return fmt.Errorf("failed to start govulncheck: %v", err) 84 } 85 if err := cmd.Wait(); err != nil { 86 return fmt.Errorf("failed to run govulncheck: %v", err) 87 } 88 return nil 89 }) 90 g.Go(func() error { 91 return govulncheck.HandleJSON(ir, handler) 92 }) 93 if err := g.Wait(); err != nil { 94 if stderr.Len() > 0 { 95 log.Write(stderr.Bytes()) 96 } 97 return nil, fmt.Errorf("failed to read govulncheck output: %v", err) 98 } 99 100 findings := handler.findings // sort so the findings in the result is deterministic. 101 sort.Slice(findings, func(i, j int) bool { 102 x, y := findings[i], findings[j] 103 if x.OSV != y.OSV { 104 return x.OSV < y.OSV 105 } 106 return x.Trace[0].Package < y.Trace[0].Package 107 }) 108 result := &vulncheck.Result{ 109 Mode: vulncheck.ModeGovulncheck, 110 AsOf: time.Now(), 111 Entries: handler.osvs, 112 Findings: findings, 113 } 114 return result, nil 115 } 116 117 type govulncheckHandler struct { 118 logger io.Writer // forward progress reports to logger. 119 120 osvs map[string]*osv.Entry 121 findings []*govulncheck.Finding 122 } 123 124 // Config implements vulncheck.Handler. 125 func (h *govulncheckHandler) Config(config *govulncheck.Config) error { 126 if config.GoVersion != "" { 127 fmt.Fprintf(h.logger, "Go: %v\n", config.GoVersion) 128 } 129 if config.ScannerName != "" { 130 scannerName := fmt.Sprintf("Scanner: %v", config.ScannerName) 131 if config.ScannerVersion != "" { 132 scannerName += "@" + config.ScannerVersion 133 } 134 fmt.Fprintln(h.logger, scannerName) 135 } 136 if config.DB != "" { 137 dbInfo := fmt.Sprintf("DB: %v", config.DB) 138 if config.DBLastModified != nil { 139 dbInfo += fmt.Sprintf(" (DB updated: %v)", config.DBLastModified.String()) 140 } 141 fmt.Fprintln(h.logger, dbInfo) 142 } 143 return nil 144 } 145 146 // Finding implements vulncheck.Handler. 147 func (h *govulncheckHandler) Finding(finding *govulncheck.Finding) error { 148 h.findings = append(h.findings, finding) 149 return nil 150 } 151 152 // OSV implements vulncheck.Handler. 153 func (h *govulncheckHandler) OSV(entry *osv.Entry) error { 154 h.osvs[entry.ID] = entry 155 return nil 156 } 157 158 // Progress implements vulncheck.Handler. 159 func (h *govulncheckHandler) Progress(progress *govulncheck.Progress) error { 160 if progress.Message != "" { 161 fmt.Fprintf(h.logger, "%v\n", progress.Message) 162 } 163 return nil 164 } 165 166 func getEnvSlices(snapshot *cache.Snapshot) []string { 167 return append(os.Environ(), snapshot.Options().EnvSlice()...) 168 }