github.com/anchore/syft@v1.38.2/internal/relationship/index.go (about) 1 package relationship 2 3 import ( 4 "slices" 5 "strings" 6 7 "github.com/anchore/syft/syft/artifact" 8 "github.com/anchore/syft/syft/file" 9 ) 10 11 // Index indexes relationships, preventing duplicates 12 type Index struct { 13 all []*sortableRelationship 14 fromID map[artifact.ID]*mappedRelationships 15 toID map[artifact.ID]*mappedRelationships 16 } 17 18 // NewIndex returns a new relationship Index 19 func NewIndex(relationships ...artifact.Relationship) *Index { 20 out := Index{ 21 fromID: make(map[artifact.ID]*mappedRelationships), 22 toID: make(map[artifact.ID]*mappedRelationships), 23 } 24 out.Add(relationships...) 25 return &out 26 } 27 28 // Add adds all the given relationships to the index, without adding duplicates 29 func (i *Index) Add(relationships ...artifact.Relationship) { 30 // store appropriate indexes for stable ordering to minimize ID() calls 31 for _, r := range relationships { 32 // prevent duplicates 33 if i.Contains(r) { 34 continue 35 } 36 37 fromID := r.From.ID() 38 toID := r.To.ID() 39 40 relationship := &sortableRelationship{ 41 from: fromID, 42 to: toID, 43 relationship: r, 44 } 45 46 // add to all relationships 47 i.all = append(i.all, relationship) 48 49 // add from -> to mapping 50 mapped := i.fromID[fromID] 51 if mapped == nil { 52 mapped = &mappedRelationships{} 53 i.fromID[fromID] = mapped 54 } 55 mapped.add(toID, relationship) 56 57 // add to -> from mapping 58 mapped = i.toID[toID] 59 if mapped == nil { 60 mapped = &mappedRelationships{} 61 i.toID[toID] = mapped 62 } 63 mapped.add(fromID, relationship) 64 } 65 } 66 67 func (i *Index) Remove(id artifact.ID) { 68 delete(i.fromID, id) 69 delete(i.toID, id) 70 71 for idx := 0; idx < len(i.all); { 72 if i.all[idx].from == id || i.all[idx].to == id { 73 i.all = append(i.all[:idx], i.all[idx+1:]...) 74 } else { 75 idx++ 76 } 77 } 78 } 79 80 func (i *Index) Replace(ogID artifact.ID, replacement artifact.Identifiable) { 81 for _, mapped := range fromMappedByID(i.fromID, ogID) { 82 // the stale relationship(i.e. if there's an elder ID in either side) should be discarded 83 if len(fromMappedByID(i.toID, mapped.relationship.To.ID())) == 0 { 84 continue 85 } 86 i.Add(artifact.Relationship{ 87 From: replacement, 88 To: mapped.relationship.To, 89 Type: mapped.relationship.Type, 90 }) 91 } 92 93 for _, mapped := range fromMappedByID(i.toID, ogID) { 94 // same as the above 95 if len(fromMappedByID(i.fromID, mapped.relationship.To.ID())) == 0 { 96 continue 97 } 98 i.Add(artifact.Relationship{ 99 From: mapped.relationship.From, 100 To: replacement, 101 Type: mapped.relationship.Type, 102 }) 103 } 104 105 i.Remove(ogID) 106 } 107 108 // From returns all relationships from the given identifiable, with specified types 109 func (i *Index) From(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship { 110 return toSortedSlice(fromMapped(i.fromID, identifiable), types) 111 } 112 113 // To returns all relationships to the given identifiable, with specified types 114 func (i *Index) To(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship { 115 return toSortedSlice(fromMapped(i.toID, identifiable), types) 116 } 117 118 // References returns all relationships that reference to or from the given identifiable 119 func (i *Index) References(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []artifact.Relationship { 120 return toSortedSlice(append(fromMapped(i.fromID, identifiable), fromMapped(i.toID, identifiable)...), types) 121 } 122 123 // Coordinates returns all coordinates for the provided identifiable for provided relationship types 124 // If no types are provided, all relationship types are considered. 125 func (i *Index) Coordinates(identifiable artifact.Identifiable, types ...artifact.RelationshipType) []file.Coordinates { 126 var coordinates []file.Coordinates 127 for _, relationship := range i.References(identifiable, types...) { 128 cords := extractCoordinates(relationship) 129 coordinates = append(coordinates, cords...) 130 } 131 return coordinates 132 } 133 134 // Contains indicates the relationship is present in this index 135 func (i *Index) Contains(r artifact.Relationship) bool { 136 if mapped := i.fromID[r.From.ID()]; mapped != nil { 137 if ids := mapped.typeMap[r.Type]; ids != nil { 138 return ids[r.To.ID()] != nil 139 } 140 } 141 return false 142 } 143 144 // All returns a sorted set of relationships matching all types, or all relationships if no types specified 145 func (i *Index) All(types ...artifact.RelationshipType) []artifact.Relationship { 146 return toSortedSlice(i.all, types) 147 } 148 149 func fromMapped(idMap map[artifact.ID]*mappedRelationships, identifiable artifact.Identifiable) []*sortableRelationship { 150 if identifiable == nil { 151 return nil 152 } 153 return fromMappedByID(idMap, identifiable.ID()) 154 } 155 156 func fromMappedByID(idMap map[artifact.ID]*mappedRelationships, id artifact.ID) []*sortableRelationship { 157 if idMap == nil { 158 return nil 159 } 160 mapped := idMap[id] 161 if mapped == nil { 162 return nil 163 } 164 return mapped.allRelated 165 } 166 167 func toSortedSlice(relationships []*sortableRelationship, types []artifact.RelationshipType) []artifact.Relationship { 168 // always return sorted for SBOM stability 169 slices.SortFunc(relationships, sortFunc) 170 var out []artifact.Relationship 171 for _, r := range relationships { 172 if len(types) == 0 || slices.Contains(types, r.relationship.Type) { 173 out = append(out, r.relationship) 174 } 175 } 176 return out 177 } 178 179 func extractCoordinates(relationship artifact.Relationship) (results []file.Coordinates) { 180 if coordinates, exists := relationship.From.(file.Coordinates); exists { 181 results = append(results, coordinates) 182 } 183 184 if coordinates, exists := relationship.To.(file.Coordinates); exists { 185 results = append(results, coordinates) 186 } 187 188 return results 189 } 190 191 type mappedRelationships struct { 192 typeMap map[artifact.RelationshipType]map[artifact.ID]*sortableRelationship 193 allRelated []*sortableRelationship 194 } 195 196 func (m *mappedRelationships) add(id artifact.ID, newRelationship *sortableRelationship) { 197 m.allRelated = append(m.allRelated, newRelationship) 198 if m.typeMap == nil { 199 m.typeMap = map[artifact.RelationshipType]map[artifact.ID]*sortableRelationship{} 200 } 201 typeMap := m.typeMap[newRelationship.relationship.Type] 202 if typeMap == nil { 203 typeMap = map[artifact.ID]*sortableRelationship{} 204 m.typeMap[newRelationship.relationship.Type] = typeMap 205 } 206 typeMap[id] = newRelationship 207 } 208 209 type sortableRelationship struct { 210 from artifact.ID 211 to artifact.ID 212 relationship artifact.Relationship 213 } 214 215 func sortFunc(a, b *sortableRelationship) int { 216 cmp := strings.Compare(string(a.relationship.Type), string(b.relationship.Type)) 217 if cmp != 0 { 218 return cmp 219 } 220 cmp = strings.Compare(string(a.from), string(b.from)) 221 if cmp != 0 { 222 return cmp 223 } 224 return strings.Compare(string(a.to), string(b.to)) 225 }