Skip to content

Commit f918e87

Browse files
committed
gopls/internal/vulncheck: copy logic of govulncheck -html
reference commit: b2400d8 The latest relevant change in the code copied is CL 403075. Change-Id: If50cb4e0096e4f33876236cf8620430e1bcfcd86 Reviewed-on: https://go-review.googlesource.com/c/tools/+/405795 Reviewed-by: Jamal Carvalho <[email protected]>
1 parent a518b79 commit f918e87

File tree

6 files changed

+177
-173
lines changed

6 files changed

+177
-173
lines changed

gopls/doc/commands.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ Result:
296296
"CurrentVersion": string,
297297
"FixedVersion": string,
298298
"CallStacks": [][]golang.org/x/tools/internal/lsp/command.StackEntry,
299+
"CallStackSummaries": []string,
299300
},
300301
}
301302
```

gopls/internal/vulncheck/command.go

Lines changed: 45 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package vulncheck
99

1010
import (
1111
"context"
12-
"fmt"
1312
"log"
1413
"os"
1514
"strings"
@@ -68,79 +67,72 @@ type cmd struct {
6867

6968
// Run runs the govulncheck after loading packages using the provided packages.Config.
7069
func (c *cmd) Run(ctx context.Context, cfg *packages.Config, patterns ...string) (_ []Vuln, err error) {
71-
// TODO: how&where can we ensure cfg is the right config for the given patterns?
72-
73-
// vulncheck.Source may panic if the packages are incomplete. (e.g. broken code or failed dependency fetch)
74-
defer func() {
75-
if r := recover(); r != nil {
76-
err = fmt.Errorf("cannot run vulncheck: %v", r)
77-
}
78-
}()
79-
return c.run(ctx, cfg, patterns)
80-
}
81-
82-
func (c *cmd) run(ctx context.Context, packagesCfg *packages.Config, patterns []string) ([]Vuln, error) {
83-
packagesCfg.Mode |= packages.NeedModule | packages.NeedName | packages.NeedFiles |
70+
cfg.Mode |= packages.NeedModule | packages.NeedName | packages.NeedFiles |
8471
packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes |
8572
packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedDeps
8673

8774
log.Println("loading packages...")
8875

89-
loadedPkgs, err := packages.Load(packagesCfg, patterns...)
76+
loadedPkgs, err := packages.Load(cfg, patterns...)
9077
if err != nil {
9178
log.Printf("package load failed: %v", err)
9279
return nil, err
9380
}
9481
log.Printf("loaded %d packages\n", len(loadedPkgs))
9582

9683
pkgs := vulncheck.Convert(loadedPkgs)
97-
res, err := vulncheck.Source(ctx, pkgs, &vulncheck.Config{
98-
Client: c.Client,
99-
ImportsOnly: false,
84+
r, err := vulncheck.Source(ctx, pkgs, &vulncheck.Config{
85+
Client: c.Client,
10086
})
101-
cs := vulncheck.CallStacks(res)
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
// Skip vulns that are in the import graph but have no calls to them.
92+
var vulns []*vulncheck.Vuln
93+
for _, v := range r.Vulns {
94+
if v.CallSink != 0 {
95+
vulns = append(vulns, v)
96+
}
97+
}
10298

103-
return toVulns(loadedPkgs, cs)
99+
callStacks := vulncheck.CallStacks(r)
100+
// Create set of top-level packages, used to find representative symbols
101+
topPackages := map[string]bool{}
102+
for _, p := range pkgs {
103+
topPackages[p.PkgPath] = true
104+
}
105+
vulnGroups := groupByIDAndPackage(vulns)
106+
moduleVersions := moduleVersionMap(r.Modules)
104107

108+
return toVulns(callStacks, moduleVersions, topPackages, vulnGroups)
105109
// TODO: add import graphs.
106110
}
107111

108-
func packageModule(p *packages.Package) *packages.Module {
109-
m := p.Module
110-
if m == nil {
111-
return nil
112-
}
113-
if r := m.Replace; r != nil {
114-
return r
115-
}
116-
return m
117-
}
112+
func toVulns(callStacks map[*vulncheck.Vuln][]vulncheck.CallStack, moduleVersions map[string]string, topPackages map[string]bool, vulnGroups [][]*vulncheck.Vuln) ([]Vuln, error) {
113+
var vulns []Vuln
118114

119-
func toVulns(pkgs []*packages.Package, callstacks map[*vulncheck.Vuln][]vulncheck.CallStack) ([]Vuln, error) {
120-
// Build a map from module paths to versions.
121-
moduleVersions := map[string]string{}
122-
packages.Visit(pkgs, nil, func(p *packages.Package) {
123-
if m := packageModule(p); m != nil {
124-
moduleVersions[m.Path] = m.Version
115+
for _, vg := range vulnGroups {
116+
v0 := vg[0]
117+
vuln := Vuln{
118+
ID: v0.OSV.ID,
119+
PkgPath: v0.PkgPath,
120+
CurrentVersion: moduleVersions[v0.ModPath],
121+
FixedVersion: latestFixed(v0.OSV.Affected),
122+
Details: v0.OSV.Details,
123+
124+
Aliases: v0.OSV.Aliases,
125+
Symbol: v0.Symbol,
126+
ModPath: v0.ModPath,
127+
URL: href(v0.OSV),
125128
}
126-
})
127129

128-
var vulns []Vuln
129-
for v, trace := range callstacks {
130-
if len(trace) == 0 {
131-
continue
132-
}
133-
vuln := Vuln{
134-
ID: v.OSV.ID,
135-
Details: v.OSV.Details,
136-
Aliases: v.OSV.Aliases,
137-
Symbol: v.Symbol,
138-
PkgPath: v.PkgPath,
139-
ModPath: v.ModPath,
140-
URL: href(v.OSV),
141-
CurrentVersion: moduleVersions[v.ModPath],
142-
FixedVersion: fixedVersion(v.OSV),
143-
CallStacks: toCallStacks(trace),
130+
// Keep first call stack for each vuln.
131+
for _, v := range vg {
132+
if css := callStacks[v]; len(css) > 0 {
133+
vuln.CallStacks = append(vuln.CallStacks, toCallStack(css[0]))
134+
vuln.CallStackSummaries = append(vuln.CallStackSummaries, summarizeCallStack(css[0], topPackages, v.PkgPath))
135+
}
144136
}
145137
vulns = append(vulns, vuln)
146138
}

gopls/internal/vulncheck/command_test.go

Lines changed: 17 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"golang.org/x/tools/internal/lsp/tests"
2626
"golang.org/x/vuln/client"
2727
"golang.org/x/vuln/osv"
28-
"golang.org/x/vuln/vulncheck"
2928
)
3029

3130
func TestCmd_Run(t *testing.T) {
@@ -54,51 +53,40 @@ func TestCmd_Run(t *testing.T) {
5453
URL: "https://pkg.go.dev/vuln/GO-2022-01",
5554
CurrentVersion: "v1.1.3",
5655
FixedVersion: "v1.0.4",
56+
CallStackSummaries: []string{
57+
"golang.org/entry/x.X calls golang.org/amod/avuln.VulnData.Vuln1",
58+
"golang.org/entry/x.X calls golang.org/cmod/c.C1, which eventually calls golang.org/amod/avuln.VulnData.Vuln2",
59+
},
5760
},
5861
CallStacksStr: []string{
59-
"golang.org/cmod/c.I.t0 called from golang.org/entry/x.X [approx.] (x.go:8)\n" +
62+
"golang.org/entry/x.X [approx.] (x.go:8)\n" +
6063
"golang.org/amod/avuln.VulnData.Vuln1 (avuln.go:3)\n",
61-
},
62-
},
63-
{
64-
Vuln: Vuln{
65-
ID: "GO-2022-01",
66-
Symbol: "VulnData.Vuln2",
67-
PkgPath: "golang.org/amod/avuln",
68-
ModPath: "golang.org/amod",
69-
URL: "https://pkg.go.dev/vuln/GO-2022-01",
70-
CurrentVersion: "v1.1.3",
71-
FixedVersion: "v1.0.4",
72-
},
73-
CallStacksStr: []string{
74-
"C1 called from golang.org/entry/x.X (x.go:8)\n" +
75-
"Vuln2 called from golang.org/cmod/c.C1 (c.go:13)\n" +
64+
"golang.org/entry/x.X (x.go:8)\n" +
65+
"golang.org/cmod/c.C1 (c.go:13)\n" +
7666
"golang.org/amod/avuln.VulnData.Vuln2 (avuln.go:4)\n",
7767
},
7868
},
7969
{
8070
Vuln: Vuln{
81-
ID: "GO-2022-02",
82-
Symbol: "Vuln",
83-
PkgPath: "golang.org/bmod/bvuln",
84-
ModPath: "golang.org/bmod",
85-
URL: "https://pkg.go.dev/vuln/GO-2022-02",
86-
CurrentVersion: "v0.5.0",
71+
ID: "GO-2022-02",
72+
Symbol: "Vuln",
73+
PkgPath: "golang.org/bmod/bvuln",
74+
ModPath: "golang.org/bmod",
75+
URL: "https://pkg.go.dev/vuln/GO-2022-02",
76+
CurrentVersion: "v0.5.0",
77+
CallStackSummaries: []string{"golang.org/entry/y.Y calls golang.org/bmod/bvuln.Vuln"},
8778
},
8879
CallStacksStr: []string{
89-
"t0 called from golang.org/entry/y.Y [approx.] (y.go:5)\n" +
90-
"golang.org/bmod/bvuln.Vuln (bvuln.go:2)\n",
91-
"Y called from golang.org/entry/x.CallY (x.go:12)\n" +
92-
"t0 called from golang.org/entry/y.Y [approx.] (y.go:5)\n" +
80+
"golang.org/entry/y.Y [approx.] (y.go:5)\n" +
9381
"golang.org/bmod/bvuln.Vuln (bvuln.go:2)\n",
9482
},
9583
},
9684
}
9785
// sort reports for stability before comparison.
9886
for _, rpts := range [][]report{got, want} {
9987
sort.Slice(rpts, func(i, j int) bool {
100-
a, b := got[i], got[j]
101-
if b.ID != b.ID {
88+
a, b := rpts[i], rpts[j]
89+
if a.ID != b.ID {
10290
return a.ID < b.ID
10391
}
10492
if a.PkgPath != b.PkgPath {
@@ -254,50 +242,6 @@ var testClient1 = &mockClient{
254242
},
255243
}
256244

257-
var goldenReport1 = []string{`
258-
{
259-
ID: "GO-2022-01",
260-
Symbol: "VulnData.Vuln1",
261-
PkgPath: "golang.org/amod/avuln",
262-
ModPath: "golang.org/amod",
263-
URL: "https://pkg.go.dev/vuln/GO-2022-01",
264-
CurrentVersion "v1.1.3",
265-
FixedVersion "v1.0.4",
266-
"call_stacks": [
267-
"golang.org/cmod/c.I.t0 called from golang.org/entry/x.X [approx.] (x.go:8)\ngolang.org/amod/avuln.VulnData.Vuln1 (avuln.go:3)\n\n"
268-
]
269-
}
270-
`,
271-
`
272-
{
273-
"id": "GO-2022-02",
274-
"symbol": "Vuln",
275-
"pkg_path": "golang.org/bmod/bvuln",
276-
"mod_path": "golang.org/bmod",
277-
"url": "https://pkg.go.dev/vuln/GO-2022-02",
278-
"current_version": "v0.5.0",
279-
"call_stacks": [
280-
"t0 called from golang.org/entry/y.Y [approx.] (y.go:5)\ngolang.org/bmod/bvuln.Vuln (bvuln.go:2)\n\n",
281-
"Y called from golang.org/entry/x.CallY (x.go:12)\nt0 called from golang.org/entry/y.Y [approx.] (y.go:5)\ngolang.org/bmod/bvuln.Vuln (bvuln.go:2)\n\n"
282-
]
283-
}
284-
`,
285-
`
286-
{
287-
"id": "GO-2022-01",
288-
"symbol": "VulnData.Vuln2",
289-
"pkg_path": "golang.org/amod/avuln",
290-
"mod_path": "golang.org/amod",
291-
"url": "https://pkg.go.dev/vuln/GO-2022-01",
292-
"current_version": "v1.1.3",
293-
FixedVersion: "v1.0.4",
294-
"call_stacks": [
295-
"C1 called from golang.org/entry/x.X (x.go:8)\nVuln2 called from golang.org/cmod/c.C1 (c.go:13)\ngolang.org/amod/avuln.VulnData.Vuln2 (avuln.go:4)\n\n"
296-
]
297-
}
298-
`,
299-
}
300-
301245
type mockClient struct {
302246
client.Client
303247
ret map[string][]*osv.Entry
@@ -347,19 +291,6 @@ func runTest(t *testing.T, workspaceData, proxyData string, test func(context.Co
347291
test(ctx, snapshot)
348292
}
349293

350-
func sortStrs(s []string) []string {
351-
sort.Strings(s)
352-
return s
353-
}
354-
355-
func pkgPaths(pkgs []*vulncheck.Package) []string {
356-
var r []string
357-
for _, p := range pkgs {
358-
r = append(r, p.PkgPath)
359-
}
360-
return sortStrs(r)
361-
}
362-
363294
// TODO: expose this as a method of Snapshot.
364295
func packagesCfg(ctx context.Context, snapshot source.Snapshot) *packages.Config {
365296
view := snapshot.View()

0 commit comments

Comments
 (0)