From ca641b8ce181d8b95c487527db5deebc022f8293 Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Thu, 26 Jan 2023 12:36:25 -0500 Subject: [PATCH] chore: rewrite vulnerability merging with generics Signed-off-by: Keith Zantow --- grype/match/match.go | 75 +++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/grype/match/match.go b/grype/match/match.go index 128ceae169f..1af95948fa8 100644 --- a/grype/match/match.go +++ b/grype/match/match.go @@ -46,54 +46,51 @@ func (m *Match) Merge(other Match) error { // there are cases related vulnerabilities are synthetic, for example when // orienting results by CVE. we need to keep track of these - related := strset.New() - for _, r := range m.Vulnerability.RelatedVulnerabilities { - related.Add(referenceID(r)) - } - for _, r := range other.Vulnerability.RelatedVulnerabilities { - if related.Has(referenceID(r)) { - continue - } - m.Vulnerability.RelatedVulnerabilities = append(m.Vulnerability.RelatedVulnerabilities, r) - } - - // for stable output - sort.Slice(m.Vulnerability.RelatedVulnerabilities, func(i, j int) bool { - a := m.Vulnerability.RelatedVulnerabilities[i] - b := m.Vulnerability.RelatedVulnerabilities[j] - return strings.Compare(referenceID(a), referenceID(b)) < 0 - }) + m.Vulnerability.RelatedVulnerabilities = mergeSlices( + m.Vulnerability.RelatedVulnerabilities, + other.Vulnerability.RelatedVulnerabilities, + func(r vulnerability.Reference) string { + return fmt.Sprintf("%s:%s", r.Namespace, r.ID) + }, + ) // also keep details from the other match that are unique - detailIDs := strset.New() - for _, d := range m.Details { - detailIDs.Add(d.ID()) + m.Details = mergeSlices( + m.Details, + other.Details, + func(detail Detail) string { + return detail.ID() + }, + ) + + // retain all unique CPEs for consistent output + m.Vulnerability.CPEs = ensureNonNil(cpe.Merge(m.Vulnerability.CPEs, other.Vulnerability.CPEs)) + + return nil +} + +func mergeSlices[T any](existing []T, new []T, id func(T) string) []T { + ids := strset.New() + for _, t := range existing { + ids.Add(id(t)) } - for _, d := range other.Details { - if detailIDs.Has(d.ID()) { - continue + for _, t := range new { + if !ids.Has(id(t)) { + existing = append(existing, t) } - m.Details = append(m.Details, d) } // for stable output - sort.Slice(m.Details, func(i, j int) bool { - a := m.Details[i] - b := m.Details[j] - return strings.Compare(a.ID(), b.ID()) < 0 + sort.Slice(existing, func(i, j int) bool { + return strings.Compare(id(existing[i]), id(existing[j])) < 0 }) - // retain all unique CPEs for consistent output - m.Vulnerability.CPEs = cpe.Merge(m.Vulnerability.CPEs, other.Vulnerability.CPEs) - if m.Vulnerability.CPEs == nil { - // ensure we always have a non-nil slice - m.Vulnerability.CPEs = []cpe.CPE{} - } - - return nil + return existing } -// referenceID returns an "ID" string for a vulnerability.Reference -func referenceID(r vulnerability.Reference) string { - return fmt.Sprintf("%s:%s", r.Namespace, r.ID) +func ensureNonNil[T any](slice []T) []T { + if slice == nil { + return []T{} + } + return slice }