-
Notifications
You must be signed in to change notification settings - Fork 605
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: SPDX output performance with many relationships (#3053)
- Loading branch information
Showing
9 changed files
with
403 additions
and
284 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,88 +1,181 @@ | ||
package relationship | ||
|
||
import ( | ||
"github.com/scylladb/go-set/strset" | ||
"slices" | ||
"strings" | ||
|
||
"github.com/anchore/syft/syft/artifact" | ||
"github.com/anchore/syft/syft/file" | ||
) | ||
|
||
// Index indexes relationships, preventing duplicates | ||
type Index struct { | ||
typesByFromTo map[artifact.ID]map[artifact.ID]*strset.Set | ||
existing []artifact.Relationship | ||
additional []artifact.Relationship | ||
all []*sortableRelationship | ||
fromID map[artifact.ID]*mappedRelationships | ||
toID map[artifact.ID]*mappedRelationships | ||
} | ||
|
||
func NewIndex(existing ...artifact.Relationship) *Index { | ||
r := &Index{ | ||
typesByFromTo: make(map[artifact.ID]map[artifact.ID]*strset.Set), | ||
} | ||
r.TrackAll(existing...) | ||
return r | ||
// NewIndex returns a new relationship Index | ||
func NewIndex(relationships ...artifact.Relationship) *Index { | ||
out := Index{} | ||
out.Add(relationships...) | ||
return &out | ||
} | ||
|
||
func (i *Index) track(r artifact.Relationship) bool { | ||
fromID := r.From.ID() | ||
if _, ok := i.typesByFromTo[fromID]; !ok { | ||
i.typesByFromTo[fromID] = make(map[artifact.ID]*strset.Set) | ||
// Add adds all the given relationships to the index, without adding duplicates | ||
func (i *Index) Add(relationships ...artifact.Relationship) { | ||
if i.fromID == nil { | ||
i.fromID = map[artifact.ID]*mappedRelationships{} | ||
} | ||
|
||
toID := r.To.ID() | ||
if _, ok := i.typesByFromTo[fromID][toID]; !ok { | ||
i.typesByFromTo[fromID][toID] = strset.New() | ||
if i.toID == nil { | ||
i.toID = map[artifact.ID]*mappedRelationships{} | ||
} | ||
|
||
var exists bool | ||
if i.typesByFromTo[fromID][toID].Has(string(r.Type)) { | ||
exists = true | ||
// store appropriate indexes for stable ordering to minimize ID() calls | ||
for _, r := range relationships { | ||
// prevent duplicates | ||
if i.Contains(r) { | ||
continue | ||
} | ||
|
||
fromID := r.From.ID() | ||
toID := r.To.ID() | ||
|
||
relationship := &sortableRelationship{ | ||
from: fromID, | ||
to: toID, | ||
relationship: r, | ||
} | ||
|
||
// add to all relationships | ||
i.all = append(i.all, relationship) | ||
|
||
// add from -> to mapping | ||
mapped := i.fromID[fromID] | ||
if mapped == nil { | ||
mapped = &mappedRelationships{} | ||
i.fromID[fromID] = mapped | ||
} | ||
mapped.add(toID, relationship) | ||
|
||
// add to -> from mapping | ||
mapped = i.toID[toID] | ||
if mapped == nil { | ||
mapped = &mappedRelationships{} | ||
i.toID[toID] = mapped | ||
} | ||
mapped.add(fromID, relationship) | ||
} | ||
} | ||
|
||
i.typesByFromTo[fromID][toID].Add(string(r.Type)) | ||
// From returns all relationships from the given identifiable, with specified types | ||
func (i *Index) From(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship { | ||
return toSortedSlice(fromMapped(i.fromID, identifiable), types) | ||
} | ||
|
||
// To returns all relationships to the given identifiable, with specified types | ||
func (i *Index) To(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship { | ||
return toSortedSlice(fromMapped(i.toID, identifiable), types) | ||
} | ||
|
||
return !exists | ||
// References returns all relationships that reference to or from the given identifiable | ||
func (i *Index) References(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship { | ||
return toSortedSlice(append(fromMapped(i.fromID, identifiable), fromMapped(i.toID, identifiable)...), types) | ||
} | ||
|
||
// Track this relationship as "exists" in the index (this is used to prevent duplicate relationships from being added). | ||
// returns true if the relationship is new to the index, false otherwise. | ||
func (i *Index) Track(r artifact.Relationship) bool { | ||
unique := i.track(r) | ||
if unique { | ||
i.existing = append(i.existing, r) | ||
// Coordinates returns all coordinates for the provided identifiable for provided relationship types | ||
// If no types are provided, all relationship types are considered. | ||
func (i *Index) Coordinates(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []file.Coordinates { | ||
var coordinates []file.Coordinates | ||
for _, relationship := range i.References(identifiable, types...) { | ||
cords := extractCoordinates(relationship) | ||
coordinates = append(coordinates, cords...) | ||
} | ||
return unique | ||
return coordinates | ||
} | ||
|
||
// Add a new relationship to the index, returning true if the relationship is new to the index, false otherwise (thus is a duplicate). | ||
func (i *Index) Add(r artifact.Relationship) bool { | ||
if i.track(r) { | ||
i.additional = append(i.additional, r) | ||
return true | ||
// Contains indicates the relationship is present in this index | ||
func (i *Index) Contains(r artifact.Relationship) bool { | ||
if mapped := i.fromID[r.From.ID()]; mapped != nil { | ||
if ids := mapped.typeMap[r.Type]; ids != nil { | ||
return ids[r.To.ID()] != nil | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (i *Index) TrackAll(rs ...artifact.Relationship) { | ||
for _, r := range rs { | ||
i.Track(r) | ||
// All returns a sorted set of relationships matching all types, or all relationships if no types specified | ||
func (i *Index) All(types ...artifact.RelationshipType) []artifact.Relationship { | ||
return toSortedSlice(i.all, types) | ||
} | ||
|
||
func fromMapped(idMap map[artifact.ID]*mappedRelationships, identifiable artifact.Identifiable) []*sortableRelationship { | ||
if identifiable == nil || idMap == nil { | ||
return nil | ||
} | ||
mapped := idMap[identifiable.ID()] | ||
if mapped == nil { | ||
return nil | ||
} | ||
return mapped.allRelated | ||
} | ||
|
||
func (i *Index) AddAll(rs ...artifact.Relationship) { | ||
for _, r := range rs { | ||
i.Add(r) | ||
func toSortedSlice(relationships []*sortableRelationship, types []artifact.RelationshipType) []artifact.Relationship { | ||
// always return sorted for SBOM stability | ||
slices.SortFunc(relationships, sortFunc) | ||
var out []artifact.Relationship | ||
for _, r := range relationships { | ||
if len(types) == 0 || slices.Contains(types, r.relationship.Type) { | ||
out = append(out, r.relationship) | ||
} | ||
} | ||
return out | ||
} | ||
|
||
func (i *Index) NewRelationships() []artifact.Relationship { | ||
return i.additional | ||
func extractCoordinates(relationship artifact.Relationship) (results []file.Coordinates) { | ||
if coordinates, exists := relationship.From.(file.Coordinates); exists { | ||
results = append(results, coordinates) | ||
} | ||
|
||
if coordinates, exists := relationship.To.(file.Coordinates); exists { | ||
results = append(results, coordinates) | ||
} | ||
|
||
return results | ||
} | ||
|
||
type mappedRelationships struct { | ||
typeMap map[artifact.RelationshipType]map[artifact.ID]*sortableRelationship | ||
allRelated []*sortableRelationship | ||
} | ||
|
||
func (m *mappedRelationships) add(id artifact.ID, newRelationship *sortableRelationship) { | ||
m.allRelated = append(m.allRelated, newRelationship) | ||
if m.typeMap == nil { | ||
m.typeMap = map[artifact.RelationshipType]map[artifact.ID]*sortableRelationship{} | ||
} | ||
typeMap := m.typeMap[newRelationship.relationship.Type] | ||
if typeMap == nil { | ||
typeMap = map[artifact.ID]*sortableRelationship{} | ||
m.typeMap[newRelationship.relationship.Type] = typeMap | ||
} | ||
typeMap[id] = newRelationship | ||
} | ||
|
||
func (i *Index) ExistingRelationships() []artifact.Relationship { | ||
return i.existing | ||
type sortableRelationship struct { | ||
from artifact.ID | ||
to artifact.ID | ||
relationship artifact.Relationship | ||
} | ||
|
||
func (i *Index) AllUniqueRelationships() []artifact.Relationship { | ||
var all []artifact.Relationship | ||
all = append(all, i.existing...) | ||
all = append(all, i.additional...) | ||
return all | ||
func sortFunc(a, b *sortableRelationship) int { | ||
cmp := strings.Compare(string(a.relationship.Type), string(b.relationship.Type)) | ||
if cmp != 0 { | ||
return cmp | ||
} | ||
cmp = strings.Compare(string(a.from), string(b.from)) | ||
if cmp != 0 { | ||
return cmp | ||
} | ||
return strings.Compare(string(a.to), string(b.to)) | ||
} |
Oops, something went wrong.