Skip to content

Commit dba032f

Browse files
committed
internal/sarif: use empty arrays instead of nils
Sarif specification requires that some slice elements explicitly exist in the JSON output even if they are empty. For instance, results should be an empty array if the sarif handler finished but found nothing. Another example is tags. Each rule in govulncheck sarif has tags property that can sometimes be empty. If so, JSON should contain an empty slice of tags. Fixes golang/go#70157 Change-Id: I112181e4efa5bc0a1577ff98f1b9eab912ed814c Reviewed-on: https://go-review.googlesource.com/c/vuln/+/625656 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Maceo Thompson <maceothompson@google.com>
1 parent 47cd072 commit dba032f

File tree

4 files changed

+53
-10
lines changed

4 files changed

+53
-10
lines changed

cmd/govulncheck/testdata/common/testfiles/source-call/source_call_sarif.ct

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,3 +496,33 @@ $ govulncheck -C ${moddir}/vuln -format sarif ./...
496496
}
497497
]
498498
}
499+
500+
# Test sarif json output for no vulnerabilities
501+
$ govulncheck -C ${moddir}/novuln -format sarif ./...
502+
{
503+
"version": "2.1.0",
504+
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
505+
"runs": [
506+
{
507+
"tool": {
508+
"driver": {
509+
"name": "govulncheck",
510+
"semanticVersion": "v0.0.0",
511+
"informationUri": "https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck",
512+
"properties": {
513+
"protocol_version": "v1.0.0",
514+
"scanner_name": "govulncheck",
515+
"scanner_version": "v0.0.0-00000000000-20000101010101",
516+
"db": "testdata/vulndb-v1",
517+
"db_last_modified": "2023-04-03T15:57:51Z",
518+
"go_version": "go1.18",
519+
"scan_level": "symbol",
520+
"scan_mode": "source"
521+
},
522+
"rules": []
523+
}
524+
},
525+
"results": []
526+
}
527+
]
528+
}

cmd/govulncheck/testdata/common/testfiles/source-call/source_call_text.ct

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,7 @@ Your code is affected by 2 vulnerabilities from 1 module.
135135
This scan also found 1 vulnerability in packages you import and 1 vulnerability
136136
in modules you require, but your code doesn't appear to call these
137137
vulnerabilities.
138+
139+
# Test no vulnerabilities in source mode
140+
$ govulncheck -C ${moddir}/novuln ./...
141+
No vulnerabilities found.

internal/sarif/handler.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func toSarif(h *handler) Log {
142142
}
143143

144144
func rules(h *handler) []Rule {
145-
var rs []Rule
145+
rs := make([]Rule, 0, len(h.findings)) // must not be nil
146146
for id := range h.findings {
147147
osv := h.osvs[id]
148148
// s is either summary if it exists, or details
@@ -157,15 +157,24 @@ func rules(h *handler) []Rule {
157157
FullDescription: Description{Text: s},
158158
HelpURI: fmt.Sprintf("https://pkg.go.dev/vuln/%s", osv.ID),
159159
Help: Description{Text: osv.Details},
160-
Properties: RuleTags{Tags: osv.Aliases},
160+
Properties: RuleTags{Tags: tags(osv)},
161161
})
162162
}
163163
sort.SliceStable(rs, func(i, j int) bool { return rs[i].ID < rs[j].ID })
164164
return rs
165165
}
166166

167+
// tags returns an slice of zero or
168+
// more aliases of o.
169+
func tags(o *osv.Entry) []string {
170+
if len(o.Aliases) > 0 {
171+
return o.Aliases
172+
}
173+
return []string{} // must not be nil
174+
}
175+
167176
func results(h *handler) []Result {
168-
var results []Result
177+
results := make([]Result, 0, len(h.findings)) // must not be nil
169178
for osv, fs := range h.findings {
170179
var locs []Location
171180
if h.cfg.ScanMode != govulncheck.ScanModeBinary {
@@ -283,7 +292,7 @@ func stack(h *handler, f *govulncheck.Finding) Stack {
283292
trace := f.Trace
284293
top := trace[len(trace)-1] // belongs to top level module
285294

286-
var frames []Frame
295+
frames := make([]Frame, 0, len(trace)) // must not be nil
287296
for i := len(trace) - 1; i >= 0; i-- { // vulnerable symbol is at the top frame
288297
frame := trace[i]
289298
pos := govulncheck.Position{Line: 1, Column: 1}
@@ -350,7 +359,7 @@ func codeFlows(h *handler, fs []*govulncheck.Finding) []CodeFlow {
350359
}
351360

352361
func threadFlows(h *handler, fs []*govulncheck.Finding) []ThreadFlow {
353-
var tfs []ThreadFlow
362+
tfs := make([]ThreadFlow, 0, len(fs)) // must not be nil
354363
for _, f := range fs {
355364
trace := traces.Compact(f)
356365
top := trace[len(trace)-1] // belongs to top level module

internal/sarif/sarif.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ type Run struct {
6666
Tool Tool `json:"tool,omitempty"`
6767
// Results contain govulncheck findings. There should be exactly one
6868
// Result per a detected use of an OSV.
69-
Results []Result `json:"results,omitempty"`
69+
Results []Result `json:"results"`
7070
}
7171

7272
// Tool captures information about govulncheck analysis that was run.
@@ -85,7 +85,7 @@ type Driver struct {
8585
// Properties are govulncheck run metadata, such as vuln db, Go version, etc.
8686
Properties govulncheck.Config `json:"properties,omitempty"`
8787

88-
Rules []Rule `json:"rules,omitempty"`
88+
Rules []Rule `json:"rules"`
8989
}
9090

9191
// Rule corresponds to the static analysis rule/analyzer that
@@ -105,7 +105,7 @@ type Rule struct {
105105

106106
// RuleTags defines properties.tags.
107107
type RuleTags struct {
108-
Tags []string `json:"tags,omitempty"`
108+
Tags []string `json:"tags"`
109109
}
110110

111111
// Description is a text in its raw or markdown form.
@@ -143,7 +143,7 @@ type Result struct {
143143
// for, say, a particular symbol or package.
144144
type CodeFlow struct {
145145
// ThreadFlows is effectively a set of related information flows.
146-
ThreadFlows []ThreadFlow `json:"threadFlows,omitempty"`
146+
ThreadFlows []ThreadFlow `json:"threadFlows"`
147147
Message Description `json:"message,omitempty"`
148148
}
149149

@@ -165,7 +165,7 @@ type ThreadFlowLocation struct {
165165
// Stack is a sequence of frames and can encode a govulncheck call stack.
166166
type Stack struct {
167167
Message Description `json:"message,omitempty"`
168-
Frames []Frame `json:"frames,omitempty"`
168+
Frames []Frame `json:"frames"`
169169
}
170170

171171
// Frame is effectively a module location. It can also contain thread and

0 commit comments

Comments
 (0)