Skip to content

Commit 76c7a5b

Browse files
committed
internal/{report,worker}: update display of xrefs
Unify the display of xrefs in the worker and in vulnreport xref. Call out duplicate aliases more prominently, as they indicate a problem, whereas module xrefs are informational. Change-Id: I3898ab1709bb3bfd6aefcfa4aef236af5f270fa7 Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/599176 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Damien Neil <[email protected]>
1 parent 2ae4aed commit 76c7a5b

File tree

8 files changed

+184
-110
lines changed

8 files changed

+184
-110
lines changed

cmd/vulnreport/testdata/TestXref/found_xrefs.txtar

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ Expected output of test TestXref/found_xrefs
66
command: "vulnreport xref 4"
77

88
-- out --
9-
data/reports/GO-9999-0004.yaml: found xrefs:
10-
Module golang.org/x/tools appears in data/reports/GO-9999-0005.yaml
11-
GO-9999-0004 priority is low
9+
GO-9999-0004: found module xrefs
10+
- golang.org/x/tools appears in 1 other report(s):
11+
- data/reports/GO-9999-0005.yaml (https://github.com/golang/vulndb#5)
12+
13+
GO-9999-0004: priority is low
1214
- golang.org/x/tools has 50 importers (< 100)
1315
-- logs --
1416
info: xref: operating on 1 report(s)

cmd/vulnreport/testdata/TestXref/no_xrefs.txtar

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Expected output of test TestXref/no_xrefs
66
command: "vulnreport xref 1"
77

88
-- out --
9-
GO-9999-0001 priority is unknown
9+
GO-9999-0001: priority is unknown
1010
- module golang.org/x/vulndb not found
1111
-- logs --
1212
info: xref: operating on 1 report(s)

cmd/vulnreport/xref.go

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ package main
77
import (
88
"context"
99
"fmt"
10-
"strings"
1110

1211
"golang.org/x/exp/constraints"
13-
"golang.org/x/exp/maps"
1412
"golang.org/x/exp/slices"
1513
"golang.org/x/vulndb/cmd/vulnreport/log"
1614
"golang.org/x/vulndb/cmd/vulnreport/priority"
@@ -44,13 +42,13 @@ func (x *xref) run(ctx context.Context, input any) (err error) {
4442
r := input.(*yamlReport)
4543

4644
if xrefs := x.xref(r); len(xrefs) > 0 {
47-
log.Outf("%s: found xrefs:%s", r.Filename, xrefs)
45+
log.Out(xrefs)
4846
} else {
4947
log.Infof("%s: no xrefs found", r.Filename)
5048
}
5149

5250
pr, notGo := x.reportPriority(r.Report)
53-
log.Outf("%s priority is %s\n - %s", r.ID, pr.Priority, pr.Reason)
51+
log.Outf("%s: priority is %s\n - %s", r.ID, pr.Priority, pr.Reason)
5452
if notGo != nil {
5553
log.Outf("%s is likely not Go\n - %s", r.ID, notGo.Reason)
5654
}
@@ -84,21 +82,9 @@ type xrefer struct {
8482
}
8583

8684
func (x *xrefer) xref(r *yamlReport) string {
87-
out := &strings.Builder{}
88-
matches := x.rc.XRef(r.Report)
89-
delete(matches, r.Filename)
90-
// This sorts as CVEs, GHSAs, and then modules.
91-
for _, fname := range sorted(maps.Keys(matches)) {
92-
for _, id := range sorted(matches[fname]) {
93-
fmt.Fprintf(out, "\n%v appears in %v", id, fname)
94-
if r, ok := x.rc.Report(fname); ok {
95-
if r.IsExcluded() {
96-
fmt.Fprintf(out, " %v", r.Excluded)
97-
}
98-
}
99-
}
100-
}
101-
return out.String()
85+
aliasTitle := fmt.Sprintf("%s: found possible duplicates", r.ID)
86+
moduleTitle := fmt.Sprintf("%s: found module xrefs", r.ID)
87+
return x.rc.XRef(r.Report).ToString(aliasTitle, moduleTitle, "")
10288
}
10389

10490
func (x *xrefer) modulePriority(modulePath string) (*priority.Result, *priority.NotGoResult) {

internal/report/client.go

Lines changed: 102 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ package report
66

77
import (
88
"context"
9+
"fmt"
10+
"io"
911
"path/filepath"
12+
"strings"
1013

1114
"github.com/go-git/go-git/v5"
1215
"github.com/go-git/go-git/v5/plumbing/object"
@@ -36,8 +39,8 @@ const (
3639
type Client struct {
3740
byFile map[string]*Report
3841
byIssue map[int]*Report
39-
byAlias map[string][]*Report
40-
byModule map[string][]*Report
42+
byAlias map[string][]*File
43+
byModule map[string][]*File
4144
}
4245

4346
// NewClient returns a Client for accessing the reports in
@@ -82,33 +85,91 @@ func (c *Client) List() []*Report {
8285
return maps.Values(c.byFile)
8386
}
8487

85-
// XRef returns cross-references for a report.
86-
// The output, matches, is a map from filenames to aliases (CVE & GHSA IDs)
87-
// and modules (excluding std and cmd).
88-
func (c *Client) XRef(r *Report) (matches map[string][]string) {
89-
mods := make(map[string]bool)
90-
for _, m := range r.Modules {
91-
if mod := m.Module; mod != "" && mod != "std" && mod != "cmd" {
92-
mods[m.Module] = true
88+
type Xrefs struct {
89+
// map from aliases to files
90+
Aliases map[string][]*File
91+
// map from modules to files
92+
Modules map[string][]*File
93+
}
94+
95+
func fprintMap(out io.Writer, m map[string][]*File) {
96+
sortedKeys := func(m map[string][]*File) []string {
97+
s := slices.Clone(maps.Keys(m))
98+
slices.Sort(s)
99+
return s
100+
}
101+
102+
for _, k := range sortedKeys(m) {
103+
fs := m[k]
104+
fmt.Fprintf(out, "- %s appears in %d other report(s):\n", k, len(fs))
105+
for _, f := range fs {
106+
fmt.Fprintf(out, " - %s (https://github.com/golang/vulndb#%d)", f.Filename, f.IssNum)
107+
if f.Report.IsExcluded() {
108+
fmt.Fprintf(out, " %v", f.Report.Excluded)
109+
}
110+
fmt.Fprintf(out, "\n")
93111
}
94112
}
113+
}
95114

96-
// matches is a map from filename -> alias/module
97-
matches = make(map[string][]string)
98-
for fname, rr := range c.byFile {
99-
for _, alias := range rr.Aliases() {
100-
if slices.Contains(r.Aliases(), alias) {
101-
matches[fname] = append(matches[fname], alias)
115+
func (xs *Xrefs) ToString(aliasTitle, moduleTitle, noneMessage string) string {
116+
if len(xs.Modules) == 0 && len(xs.Aliases) == 0 {
117+
return noneMessage
118+
}
119+
120+
out := &strings.Builder{}
121+
122+
if len(xs.Aliases) != 0 {
123+
fmt.Fprint(out, aliasTitle+"\n")
124+
fprintMap(out, xs.Aliases)
125+
if len(xs.Modules) != 0 {
126+
fmt.Fprintf(out, "\n")
127+
}
128+
}
129+
130+
if len(xs.Modules) != 0 {
131+
fmt.Fprint(out, moduleTitle+"\n")
132+
fprintMap(out, xs.Modules)
133+
}
134+
135+
return out.String()
136+
}
137+
138+
type File struct {
139+
Filename string
140+
IssNum int
141+
*Report
142+
}
143+
144+
// XRef returns cross-references for a report.
145+
func (c *Client) XRef(r *Report) *Xrefs {
146+
x := &Xrefs{
147+
Aliases: make(map[string][]*File),
148+
Modules: make(map[string][]*File),
149+
}
150+
151+
for _, alias := range r.Aliases() {
152+
for _, f := range c.byAlias[alias] {
153+
if r.ID == f.Report.ID {
154+
continue
102155
}
156+
x.Aliases[alias] = append(x.Aliases[alias], f)
157+
}
158+
}
159+
160+
for _, m := range r.Modules {
161+
if m.IsFirstParty() {
162+
continue
103163
}
104-
for _, m := range rr.Modules {
105-
if mods[m.Module] {
106-
k := "Module " + m.Module
107-
matches[fname] = append(matches[fname], k)
164+
for _, f := range c.byModule[m.Module] {
165+
if r.ID == f.Report.ID {
166+
continue
108167
}
168+
x.Modules[m.Module] = append(x.Modules[m.Module], f)
109169
}
110170
}
111-
return matches
171+
172+
return x
112173
}
113174

114175
// Report returns the report with the given filename in vulndb, or
@@ -128,13 +189,21 @@ func (c *Client) HasReport(githubID int) (found bool) {
128189
// ReportsByAlias returns a list of reports in vulndb with the given
129190
// alias.
130191
func (c *Client) ReportsByAlias(alias string) []*Report {
131-
return c.byAlias[alias]
192+
var rs []*Report
193+
for _, f := range c.byAlias[alias] {
194+
rs = append(rs, f.Report)
195+
}
196+
return rs
132197
}
133198

134199
// ReportsByModule returns a list of reports in vulndb with the given
135200
// module.
136201
func (c *Client) ReportsByModule(module string) []*Report {
137-
return c.byModule[module]
202+
var rs []*Report
203+
for _, f := range c.byModule[module] {
204+
rs = append(rs, f.Report)
205+
}
206+
return rs
138207
}
139208

140209
// AliasHasReport returns whether the given alias exists in vulndb.
@@ -147,8 +216,8 @@ func newClient() *Client {
147216
return &Client{
148217
byIssue: make(map[int]*Report),
149218
byFile: make(map[string]*Report),
150-
byAlias: make(map[string][]*Report),
151-
byModule: make(map[string][]*Report),
219+
byAlias: make(map[string][]*File),
220+
byModule: make(map[string][]*File),
152221
}
153222
}
154223

@@ -187,13 +256,19 @@ func (c *Client) addReport(filename string, r *Report) error {
187256
return err
188257
}
189258

259+
f := &File{
260+
Filename: filename,
261+
IssNum: iss,
262+
Report: r,
263+
}
264+
190265
c.byFile[filename] = r
191266
c.byIssue[iss] = r
192267
for _, alias := range r.Aliases() {
193-
c.byAlias[alias] = append(c.byAlias[alias], r)
268+
c.byAlias[alias] = append(c.byAlias[alias], f)
194269
}
195270
for _, m := range r.Modules {
196-
c.byModule[m.Module] = append(c.byModule[m.Module], r)
271+
c.byModule[m.Module] = append(c.byModule[m.Module], f)
197272
}
198273

199274
return nil

internal/report/client_test.go

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ var (
4242
Modules: []*Module{
4343
{Module: "example.com/another/module"},
4444
},
45-
4645
GHSAs: []string{
4746
"GHSA-9999-abcd-efgh",
4847
},
@@ -55,6 +54,14 @@ var (
5554
},
5655
CVEs: []string{"CVE-9999-0005"},
5756
}
57+
fname6 = "data/reports/GO-9999-0006.yaml"
58+
r6 = Report{
59+
ID: "GO-9999-0006",
60+
Modules: []*Module{
61+
{Module: "example.com/another/module"},
62+
},
63+
GHSAs: []string{"GHSA-9999-abcd-efgh"},
64+
}
5865

5966
txtarFile = filepath.Join("testdata", "repo.txtar")
6067
)
@@ -71,7 +78,7 @@ func TestList(t *testing.T) {
7178
}
7279

7380
got := rc.List()
74-
want := []*Report{&r1, &r2, &r4, &r5}
81+
want := []*Report{&r1, &r2, &r4, &r5, &r6}
7582
byID := func(a, b *Report) bool { return a.ID < b.ID }
7683
if diff := cmp.Diff(got, want, cmpopts.SortSlices(byID)); diff != "" {
7784
t.Errorf("mismatch (-got, +want): %s", diff)
@@ -91,6 +98,7 @@ func TestXRef(t *testing.T) {
9198
tests := []struct {
9299
name string
93100
r *Report
101+
wantXrefs *Xrefs
94102
wantMatches map[string][]string
95103
}{
96104
{
@@ -103,7 +111,10 @@ func TestXRef(t *testing.T) {
103111
ID: "CVE-9999-0003",
104112
},
105113
},
106-
wantMatches: map[string][]string{},
114+
wantXrefs: &Xrefs{
115+
Aliases: map[string][]*File{},
116+
Modules: map[string][]*File{},
117+
},
107118
},
108119
{
109120
name: "Ignores std lib modules",
@@ -113,7 +124,10 @@ func TestXRef(t *testing.T) {
113124
},
114125
CVEs: []string{"CVE-9999-0003"},
115126
},
116-
wantMatches: map[string][]string{},
127+
wantXrefs: &Xrefs{
128+
Aliases: map[string][]*File{},
129+
Modules: map[string][]*File{},
130+
},
117131
},
118132
{
119133
name: "Match on CVE (ignores std module)",
@@ -123,26 +137,37 @@ func TestXRef(t *testing.T) {
123137
},
124138
CVEs: []string{"CVE-9999-0001"},
125139
},
126-
wantMatches: map[string][]string{
127-
fname1: {"CVE-9999-0001"},
140+
wantXrefs: &Xrefs{
141+
Aliases: map[string][]*File{
142+
"CVE-9999-0001": {
143+
{Filename: fname1, IssNum: 1, Report: &r1},
144+
},
145+
},
146+
Modules: map[string][]*File{},
128147
},
129148
},
130149
{
131150
name: "Match on GHSA & module",
132151
r: &r4,
133-
wantMatches: map[string][]string{
134-
fname4: {
135-
"GHSA-9999-abcd-efgh",
136-
"Module example.com/another/module",
152+
wantXrefs: &Xrefs{
153+
Aliases: map[string][]*File{
154+
"GHSA-9999-abcd-efgh": {
155+
{Filename: fname6, IssNum: 6, Report: &r6},
156+
},
157+
},
158+
Modules: map[string][]*File{
159+
"example.com/another/module": {
160+
{Filename: fname6, IssNum: 6, Report: &r6},
161+
},
137162
},
138163
},
139164
},
140165
}
141166
for _, tt := range tests {
142167
t.Run(tt.name, func(t *testing.T) {
143-
gotMatches := rc.XRef(tt.r)
144-
if diff := cmp.Diff(gotMatches, tt.wantMatches); diff != "" {
145-
t.Errorf("XRef(): matches mismatch (-got, +want): %s", diff)
168+
got := rc.XRef(tt.r)
169+
if diff := cmp.Diff(got, tt.wantXrefs); diff != "" {
170+
t.Errorf("XRef(): mismatch (-got, +want): %s", diff)
146171
}
147172
})
148173
}
@@ -198,14 +223,19 @@ func TestNewClient(t *testing.T) {
198223
}
199224

200225
files := map[string]*Report{
201-
fname1: &r1, fname2: &r2, fname4: &r4, fname5: &r5,
226+
fname1: &r1, fname2: &r2, fname4: &r4, fname5: &r5, fname6: &r6,
202227
}
203228
tc, err := NewTestClient(files)
204229
if err != nil {
205230
t.Fatal(err)
206231
}
207232

208-
if diff := cmp.Diff(c, tc, cmp.AllowUnexported(Client{})); diff != "" {
233+
less := func(f1, f2 *File) bool {
234+
return f1.ID < f2.ID
235+
}
236+
237+
if diff := cmp.Diff(c, tc, cmp.AllowUnexported(Client{}),
238+
cmpopts.SortSlices(less)); diff != "" {
209239
t.Errorf("NewClient() / NewTestClient() mismatch (-New, +NewTest): %s", diff)
210240
}
211241
}

0 commit comments

Comments
 (0)