Skip to content

Commit c9887ea

Browse files
committed
[gopls-release-branch.0.11] gopls/internal/lsp/cache: invalidate govulncheck results older than 1hr
1hr is long enough to make the computed results stale either because the vuln db is updated or the code is changed. Drop old results. Note: OSV entry caching TTL used by the govulncheck command line tool is 2hr. https://github.com/golang/vuln/blob/05fb7250142cc6010c39968839f2f3710afdd918/client/client.go#L230 Change-Id: I17ac5307a0e966bb91be74dcaf25cee14781eb2d Reviewed-on: https://go-review.googlesource.com/c/tools/+/456255 Run-TryBot: Hyang-Ah Hana Kim <[email protected]> Auto-Submit: Hyang-Ah Hana Kim <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Suzy Mueller <[email protected]> Reviewed-by: Robert Findley <[email protected]> Reviewed-on: https://go-review.googlesource.com/c/tools/+/456639
1 parent 706592b commit c9887ea

File tree

4 files changed

+94
-9
lines changed

4 files changed

+94
-9
lines changed

gopls/internal/govulncheck/types.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
package govulncheck
66

7+
import "time"
8+
79
// Result is the result of vulnerability scanning.
810
type Result struct {
911
// Vulns contains all vulnerabilities that are called or imported by
1012
// the analyzed module.
11-
Vulns []*Vuln
13+
Vulns []*Vuln `json:",omitempty"`
1214

1315
// Mode contains the source of the vulnerability info.
1416
// Clients of the gopls.fetch_vulncheck_result command may need
@@ -19,7 +21,11 @@ type Result struct {
1921
// without callstack traces just implies the package with the
2022
// vulnerability is known to the workspace and we do not know
2123
// whether the vulnerable symbols are actually used or not.
22-
Mode AnalysisMode
24+
Mode AnalysisMode `json:",omitempty"`
25+
26+
// AsOf describes when this Result was computed using govulncheck.
27+
// It is valid only with the govulncheck analysis mode.
28+
AsOf time.Time `json:",omitempty"`
2329
}
2430

2531
type AnalysisMode string

gopls/internal/lsp/cache/view.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"sort"
2020
"strings"
2121
"sync"
22+
"time"
2223

2324
"golang.org/x/mod/modfile"
2425
"golang.org/x/mod/semver"
@@ -1034,19 +1035,27 @@ func (v *View) ClearModuleUpgrades(modfile span.URI) {
10341035
delete(v.moduleUpgrades, modfile)
10351036
}
10361037

1037-
func (v *View) Vulnerabilities(modfile ...span.URI) map[span.URI]*govulncheck.Result {
1038+
const maxGovulncheckResultAge = 1 * time.Hour // Invalidate results older than this limit.
1039+
var timeNow = time.Now // for testing
1040+
1041+
func (v *View) Vulnerabilities(modfiles ...span.URI) map[span.URI]*govulncheck.Result {
10381042
m := make(map[span.URI]*govulncheck.Result)
1043+
now := timeNow()
10391044
v.mu.Lock()
10401045
defer v.mu.Unlock()
10411046

1042-
if len(modfile) == 0 {
1043-
for k, v := range v.vulns {
1044-
m[k] = v
1047+
if len(modfiles) == 0 { // empty means all modfiles
1048+
for modfile := range v.vulns {
1049+
modfiles = append(modfiles, modfile)
10451050
}
1046-
return m
10471051
}
1048-
for _, f := range modfile {
1049-
m[f] = v.vulns[f]
1052+
for _, modfile := range modfiles {
1053+
vuln := v.vulns[modfile]
1054+
if vuln != nil && now.Sub(vuln.AsOf) > maxGovulncheckResultAge {
1055+
v.vulns[modfile] = nil // same as SetVulnerabilities(modfile, nil)
1056+
vuln = nil
1057+
}
1058+
m[modfile] = vuln
10501059
}
10511060
return m
10521061
}

gopls/internal/lsp/cache/view_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ package cache
55

66
import (
77
"context"
8+
"encoding/json"
89
"io/ioutil"
910
"os"
1011
"path/filepath"
1112
"testing"
13+
"time"
1214

15+
"github.com/google/go-cmp/cmp"
16+
"golang.org/x/tools/gopls/internal/govulncheck"
1317
"golang.org/x/tools/gopls/internal/lsp/fake"
1418
"golang.org/x/tools/gopls/internal/lsp/source"
1519
"golang.org/x/tools/gopls/internal/span"
@@ -208,3 +212,67 @@ func TestSuffixes(t *testing.T) {
208212
}
209213
}
210214
}
215+
216+
func TestView_Vulnerabilities(t *testing.T) {
217+
// TODO(hyangah): use t.Cleanup when we get rid of go1.13 legacy CI.
218+
defer func() { timeNow = time.Now }()
219+
220+
now := time.Now()
221+
222+
view := &View{
223+
vulns: make(map[span.URI]*govulncheck.Result),
224+
}
225+
file1, file2 := span.URIFromPath("f1/go.mod"), span.URIFromPath("f2/go.mod")
226+
227+
vuln1 := &govulncheck.Result{AsOf: now.Add(-(maxGovulncheckResultAge * 3) / 4)} // already ~3/4*maxGovulncheckResultAge old
228+
view.SetVulnerabilities(file1, vuln1)
229+
230+
vuln2 := &govulncheck.Result{AsOf: now} // fresh.
231+
view.SetVulnerabilities(file2, vuln2)
232+
233+
t.Run("fresh", func(t *testing.T) {
234+
got := view.Vulnerabilities()
235+
want := map[span.URI]*govulncheck.Result{
236+
file1: vuln1,
237+
file2: vuln2,
238+
}
239+
240+
if diff := cmp.Diff(toJSON(want), toJSON(got)); diff != "" {
241+
t.Errorf("view.Vulnerabilities() mismatch (-want +got):\n%s", diff)
242+
}
243+
})
244+
245+
// maxGovulncheckResultAge/2 later
246+
timeNow = func() time.Time { return now.Add(maxGovulncheckResultAge / 2) }
247+
t.Run("after30min", func(t *testing.T) {
248+
got := view.Vulnerabilities()
249+
want := map[span.URI]*govulncheck.Result{
250+
file1: nil, // expired.
251+
file2: vuln2,
252+
}
253+
254+
if diff := cmp.Diff(toJSON(want), toJSON(got)); diff != "" {
255+
t.Errorf("view.Vulnerabilities() mismatch (-want +got):\n%s", diff)
256+
}
257+
})
258+
259+
// maxGovulncheckResultAge later
260+
timeNow = func() time.Time { return now.Add(maxGovulncheckResultAge + time.Minute) }
261+
262+
t.Run("after1hr", func(t *testing.T) {
263+
got := view.Vulnerabilities()
264+
want := map[span.URI]*govulncheck.Result{
265+
file1: nil,
266+
file2: nil,
267+
}
268+
269+
if diff := cmp.Diff(toJSON(want), toJSON(got)); diff != "" {
270+
t.Errorf("view.Vulnerabilities() mismatch (-want +got):\n%s", diff)
271+
}
272+
})
273+
}
274+
275+
func toJSON(x interface{}) string {
276+
b, _ := json.MarshalIndent(x, "", " ")
277+
return string(b)
278+
}

gopls/internal/lsp/command.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"path/filepath"
1818
"sort"
1919
"strings"
20+
"time"
2021

2122
"golang.org/x/mod/modfile"
2223
"golang.org/x/tools/go/ast/astutil"
@@ -927,6 +928,7 @@ func (c *commandHandler) RunGovulncheck(ctx context.Context, args command.Vulnch
927928
return fmt.Errorf("failed to parse govulncheck output: %v", err)
928929
}
929930
result.Mode = govulncheck.ModeGovulncheck
931+
result.AsOf = time.Now()
930932
deps.snapshot.View().SetVulnerabilities(args.URI.SpanURI(), &result)
931933

932934
c.s.diagnoseSnapshot(deps.snapshot, nil, false)

0 commit comments

Comments
 (0)