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  }