Skip to content

Commit 45fb6f9

Browse files
authored
Improved the runtime of DiffVulnerabilityResults (#1091)
- Improved the runtime of DiffVulnerabilityResults - Precomputed the existence of Sources, Packages, and Vulnerabilities, which brings the time complexity down from O(n^2) to O(n).
1 parent 56c68b8 commit 45fb6f9

File tree

1 file changed

+40
-12
lines changed

1 file changed

+40
-12
lines changed

internal/ci/vulnerability_result_diff.go

+40-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
package ci
22

33
import (
4-
"slices"
5-
64
"github.com/google/osv-scanner/internal/output"
75
"github.com/google/osv-scanner/pkg/grouper"
86
"github.com/google/osv-scanner/pkg/models"
97
)
108

119
// DiffVulnerabilityResults will return any new vulnerabilities that are in `newRes`
1210
// which is not present in `oldRes`, but not the reverse.
13-
//
14-
// Current implementation is O(n^2) on the number of vulns, but can be reduced to linear time
1511
func DiffVulnerabilityResults(oldRes, newRes models.VulnerabilityResults) models.VulnerabilityResults {
1612
result := models.VulnerabilityResults{}
13+
// Initialize caches for quick lookup
14+
sourceToIndex, packageToIndex, vulnToIndex := initializeCaches(oldRes)
15+
1716
for _, ps := range newRes.Results {
18-
sourceIdx := slices.IndexFunc(oldRes.Results, func(elem models.PackageSource) bool { return elem.Source == ps.Source })
19-
if sourceIdx == -1 {
17+
sourceIdx, sourceExists := sourceToIndex[ps.Source]
18+
if !sourceExists {
2019
// Newly introduced source, so all results for this source are going to be new, add everything for this source
2120
result.Results = append(result.Results, ps)
2221
continue
@@ -27,9 +26,8 @@ func DiffVulnerabilityResults(oldRes, newRes models.VulnerabilityResults) models
2726
})
2827
resultPS := &result.Results[len(result.Results)-1]
2928
for _, pv := range ps.Packages {
30-
pkgs := oldRes.Results[sourceIdx].Packages
31-
pkgIdx := slices.IndexFunc(pkgs, func(elem models.PackageVulns) bool { return elem.Package == pv.Package })
32-
if pkgIdx == -1 {
29+
pkgIdx, packageExists := packageToIndex[sourceIdx][pv.Package]
30+
if !packageExists {
3331
// Newly introduced package, so all results for this package are going to be new, add everything for this package
3432
resultPS.Packages = append(resultPS.Packages, pv)
3533
continue
@@ -41,9 +39,7 @@ func DiffVulnerabilityResults(oldRes, newRes models.VulnerabilityResults) models
4139
})
4240
resultPV := &resultPS.Packages[len(resultPS.Packages)-1]
4341
for _, v := range pv.Vulnerabilities {
44-
vulns := pkgs[pkgIdx].Vulnerabilities
45-
vulnIdx := slices.IndexFunc(vulns, func(elem models.Vulnerability) bool { return elem.ID == v.ID })
46-
if vulnIdx == -1 {
42+
if !vulnToIndex[sourceIdx][pkgIdx][v.ID] {
4743
// Vulnerability is new, add it to the results
4844
resultPV.Vulnerabilities = append(resultPV.Vulnerabilities, v)
4945
continue
@@ -71,6 +67,38 @@ func DiffVulnerabilityResults(oldRes, newRes models.VulnerabilityResults) models
7167
return result
7268
}
7369

70+
// initializeCaches sets up maps for quick lookup of sources, packages, and vulnerabilities by their indices.
71+
func initializeCaches(oldRes models.VulnerabilityResults) (map[models.SourceInfo]int, []map[models.PackageInfo]int, [][]map[string]bool) {
72+
sourceToIndex := make(map[models.SourceInfo]int, len(oldRes.Results))
73+
// The index in the array corresponds to a source index, a query would look like packageToIndex[sourceIndex][packageInfo]
74+
packageToIndex := make([]map[models.PackageInfo]int, len(oldRes.Results))
75+
// The first index in the array corresponds to a source index, and the second index corresponds to a package index
76+
// a query would look like vulnToIndex[sourceIndex][packageIndex][vulnID]
77+
vulnToIndex := make([][]map[string]bool, len(oldRes.Results))
78+
79+
// Populate index maps for sources, packages, and vulnerabilities
80+
for sourceIndex, vulnResult := range oldRes.Results {
81+
sourceToIndex[oldRes.Results[sourceIndex].Source] = sourceIndex
82+
if vulnToIndex[sourceIndex] == nil {
83+
vulnToIndex[sourceIndex] = make([]map[string]bool, len(vulnResult.Packages))
84+
}
85+
for packageIndex, pkg := range vulnResult.Packages {
86+
if packageToIndex[sourceIndex] == nil {
87+
packageToIndex[sourceIndex] = make(map[models.PackageInfo]int, len(vulnResult.Packages))
88+
}
89+
packageToIndex[sourceIndex][pkg.Package] = packageIndex
90+
if vulnToIndex[sourceIndex][packageIndex] == nil {
91+
vulnToIndex[sourceIndex][packageIndex] = make(map[string]bool, len(pkg.Vulnerabilities))
92+
}
93+
for _, vuln := range pkg.Vulnerabilities {
94+
vulnToIndex[sourceIndex][packageIndex][vuln.ID] = true // Mark the vulnerability as present
95+
}
96+
}
97+
}
98+
99+
return sourceToIndex, packageToIndex, vulnToIndex
100+
}
101+
74102
// DiffVulnerabilityResultsByOccurrences will return the occurrence of each vulnerability that are in `newRes`
75103
// which is not present in `oldRes`, but not the reverse. This calculates the difference by vulnerability ID,
76104
// while ignoring the source of the vulnerability.

0 commit comments

Comments
 (0)