diff --git a/algo/reach.go b/algo/reach.go index 691f42f..3813180 100644 --- a/algo/reach.go +++ b/algo/reach.go @@ -278,7 +278,7 @@ func (s *ReachabilityCache) OrReach(node uint64, direction graph.Direction, dupl // from the result before the XOR operation. func (s *ReachabilityCache) XorReach(node uint64, direction graph.Direction, duplex cardinality.Duplex[uint64]) { // Reach bitmap will contain the member due to resolution of component reach - reachBitmap := s.ReachOfComponentContainingMember(node, direction).Clone() + reachBitmap := s.ReachOfComponentContainingMember(node, direction).Clone().(cardinality.Duplex[uint64]) reachBitmap.Remove(node) duplex.Xor(reachBitmap) diff --git a/cardinality/cardinality.go b/cardinality/cardinality.go index cbb66d8..c0b1641 100644 --- a/cardinality/cardinality.go +++ b/cardinality/cardinality.go @@ -7,9 +7,6 @@ type DuplexConstructor[T uint32 | uint64] func() Duplex[T] // Provider describes the most basic functionality of a cardinality provider algorithm: adding elements to the provider // and producing the cardinality of those elements. type Provider[T uint32 | uint64] interface { - Add(value ...T) - Or(other Provider[T]) - Clear() Cardinality() uint64 } @@ -26,13 +23,21 @@ func CloneProvider[T uint32 | uint64](provider Provider[T]) Provider[T] { } } +type ImmutableSimplex[T uint32 | uint64] interface { + Provider[T] + + Clone() Simplex[T] +} + // Simplex is a one-way cardinality provider that does not allow a user to retrieve encoded values back out of the // provider. This interface is suitable for algorithms such as HyperLogLog which utilizes a hash function to merge // identifiers into the cardinality provider. type Simplex[T uint32 | uint64] interface { - Provider[T] + ImmutableSimplex[T] - Clone() Simplex[T] + Add(value ...T) + Or(other Provider[T]) + Clear() } // Iterator allows enumeration of a duplex cardinality provider without requiring the allocation of the provider's set. @@ -41,18 +46,27 @@ type Iterator[T uint32 | uint64] interface { Next() T } +type ImmutableDuplex[T uint32 | uint64] interface { + Provider[T] + + Slice() []T + Contains(value T) bool + Each(delegate func(value T) bool) + + Clone() Duplex[T] +} + // Duplex is a two-way cardinality provider that allows a user to retrieve encoded values back out of the provider. This // interface is suitable for algorithms that behave similar to bitvectors. type Duplex[T uint32 | uint64] interface { - Provider[T] + ImmutableDuplex[T] + Add(value ...T) + CheckedAdd(value T) bool + Remove(value T) + Or(other Provider[T]) Xor(other Provider[T]) And(other Provider[T]) AndNot(other Provider[T]) - Remove(value T) - Slice() []T - Contains(value T) bool - Each(delegate func(value T) bool) - CheckedAdd(value T) bool - Clone() Duplex[T] + Clear() } diff --git a/cardinality/roaring32.go b/cardinality/roaring32.go index 0b90170..324c177 100644 --- a/cardinality/roaring32.go +++ b/cardinality/roaring32.go @@ -68,7 +68,13 @@ func (s bitmap32) CheckedAdd(value uint32) bool { } func (s bitmap32) Add(values ...uint32) { - s.bitmap.AddMany(values) + switch len(values) { + case 0: + case 1: + s.bitmap.Add(values[0]) + default: + s.bitmap.AddMany(values) + } } func (s bitmap32) Remove(value uint32) { diff --git a/cardinality/roaring64.go b/cardinality/roaring64.go index 4beaad0..357ed4e 100644 --- a/cardinality/roaring64.go +++ b/cardinality/roaring64.go @@ -68,7 +68,13 @@ func (s bitmap64) CheckedAdd(value uint64) bool { } func (s bitmap64) Add(values ...uint64) { - s.bitmap.AddMany(values) + switch len(values) { + case 0: + case 1: + s.bitmap.Add(values[0]) + default: + s.bitmap.AddMany(values) + } } func (s bitmap64) Remove(value uint64) { diff --git a/container/triplestore.go b/container/triplestore.go index 53e4c58..95926ce 100644 --- a/container/triplestore.go +++ b/container/triplestore.go @@ -1,14 +1,21 @@ package container import ( + "slices" + "github.com/specterops/dawgs/cardinality" "github.com/specterops/dawgs/graph" ) type Edge struct { - ID uint64 - Start uint64 - End uint64 + ID uint64 `json:"id"` + Kind graph.Kind `json:"kind"` + Start uint64 `json:"start_id"` + End uint64 `json:"end_id"` +} + +type Path struct { + Edges []Edge `json:"edges"` } func (s Edge) Pick(direction graph.Direction) uint64 { @@ -25,51 +32,124 @@ type Triplestore interface { NumEdges() uint64 EachEdge(delegate func(next Edge) bool) EachAdjacentEdge(node uint64, direction graph.Direction, delegate func(next Edge) bool) - - Projection(deletedNodes, deletedEdges cardinality.Duplex[uint64]) Triplestore } type MutableTriplestore interface { Triplestore - AddTriple(edge, start, end uint64) + Sort() + AddNode(node uint64) + AddEdge(edge Edge) +} + +func AdjacentEdges(ts Triplestore, node uint64, direction graph.Direction) []Edge { + var edges []Edge + + ts.EachAdjacentEdge(node, direction, func(next Edge) bool { + edges = append(edges, next) + return true + }) + + return edges } type triplestore struct { - nodes cardinality.Duplex[uint64] - edges []Edge - deletedEdges cardinality.Duplex[uint64] - startIndex map[uint64]cardinality.Duplex[uint64] - endIndex map[uint64]cardinality.Duplex[uint64] + edges []Edge + startIndex map[uint64][]uint64 + endIndex map[uint64][]uint64 } func NewTriplestore() MutableTriplestore { return &triplestore{ - nodes: cardinality.NewBitmap64(), - deletedEdges: cardinality.NewBitmap64(), - startIndex: map[uint64]cardinality.Duplex[uint64]{}, - endIndex: map[uint64]cardinality.Duplex[uint64]{}, + startIndex: map[uint64][]uint64{}, + endIndex: map[uint64][]uint64{}, } } -func (s *triplestore) DeleteEdge(id uint64) { - s.deletedEdges.Add(id) +func (s *triplestore) Sort() { + // Clear but preserve allocations + clear(s.startIndex) + clear(s.endIndex) + + // Sort edges by ID + slices.SortFunc(s.edges, func(a, b Edge) int { + if a.ID > b.ID { + return 1 + } + + if a.ID < b.ID { + return -1 + } + + return 0 + }) + + // Rebuild node to edge index lookups + for edgeIdx, edge := range s.edges { + s.startIndex[edge.Start] = append(s.startIndex[edge.Start], uint64(edgeIdx)) + s.endIndex[edge.End] = append(s.endIndex[edge.End], uint64(edgeIdx)) + } } func (s *triplestore) Edges() []Edge { return s.edges } +func (s *triplestore) ContainsNode(node uint64) bool { + _, hasNode := s.startIndex[node] + + if hasNode { + return true + } + + _, hasNode = s.endIndex[node] + return hasNode +} + +func (s *triplestore) nodeBitmap() cardinality.Duplex[uint64] { + nodes := cardinality.NewBitmap64() + + for nodeID := range s.startIndex { + nodes.Add(nodeID) + } + + for nodeID := range s.endIndex { + nodes.Add(nodeID) + } + + return nodes +} + func (s *triplestore) NumNodes() uint64 { - return s.nodes.Cardinality() + return s.nodeBitmap().Cardinality() } func (s *triplestore) AddNode(node uint64) { - s.nodes.Add(node) + if _, exists := s.startIndex[node]; !exists { + s.startIndex[node] = nil + } + + if _, exists := s.endIndex[node]; !exists { + s.endIndex[node] = nil + } } func (s *triplestore) EachNode(delegate func(node uint64) bool) { - s.nodes.Each(delegate) + nodes := cardinality.NewBitmap64() + + for nodeID := range s.startIndex { + nodes.Add(nodeID) + + if !delegate(nodeID) { + return + } + } + + for nodeID := range s.endIndex { + if nodes.CheckedAdd(nodeID) && !delegate(nodeID) { + return + } + } } func (s *triplestore) EachEdge(delegate func(edge Edge) bool) { @@ -80,112 +160,96 @@ func (s *triplestore) EachEdge(delegate func(edge Edge) bool) { } } -func (s *triplestore) AddTriple(edge, start, end uint64) { - s.edges = append(s.edges, Edge{ - ID: edge, - Start: start, - End: end, - }) - +func (s *triplestore) AddEdge(edge Edge) { + s.edges = append(s.edges, edge) edgeIdx := len(s.edges) - 1 - if edgeBitmap, exists := s.startIndex[start]; exists { - edgeBitmap.Add(uint64(edgeIdx)) - } else { - edgeBitmap = cardinality.NewBitmap64() - edgeBitmap.Add(uint64(edgeIdx)) - - s.startIndex[start] = edgeBitmap - } - - if edgeBitmap, exists := s.endIndex[end]; exists { - edgeBitmap.Add(uint64(edgeIdx)) - } else { - edgeBitmap = cardinality.NewBitmap64() - edgeBitmap.Add(uint64(edgeIdx)) - - s.endIndex[end] = edgeBitmap - } - - s.nodes.Add(start, end) + s.startIndex[edge.Start] = append(s.startIndex[edge.Start], uint64(edgeIdx)) + s.endIndex[edge.End] = append(s.endIndex[edge.End], uint64(edgeIdx)) } -func (s *triplestore) adjacentEdgeIndices(node uint64, direction graph.Direction) cardinality.Duplex[uint64] { - edgeIndices := cardinality.NewBitmap64() - +func (s *triplestore) adjacentEdgeIndices(node uint64, direction graph.Direction) []uint64 { switch direction { case graph.DirectionOutbound: if outboundEdges, hasOutbound := s.startIndex[node]; hasOutbound { - edgeIndices.Or(outboundEdges) + return outboundEdges } case graph.DirectionInbound: if inboundEdges, hasInbound := s.endIndex[node]; hasInbound { - edgeIndices.Or(inboundEdges) + return inboundEdges } default: + edgeIndices := cardinality.NewBitmap64() + if outboundEdges, hasOutbound := s.startIndex[node]; hasOutbound { - edgeIndices.Or(outboundEdges) + edgeIndices.Add(outboundEdges...) } if inboundEdges, hasInbound := s.endIndex[node]; hasInbound { - edgeIndices.Or(inboundEdges) + edgeIndices.Add(inboundEdges...) } + + return edgeIndices.Slice() } - return edgeIndices + return nil } func (s *triplestore) AdjacentEdges(node uint64, direction graph.Direction) []uint64 { var ( edgeIndices = s.adjacentEdgeIndices(node, direction) - edgeIDs = make([]uint64, 0, edgeIndices.Cardinality()) + edgeIDs = make([]uint64, len(edgeIndices)) ) - edgeIndices.Each(func(value uint64) bool { - edgeIDs = append(edgeIDs, s.edges[value].ID) - return true - }) + for idx, edgeIdx := range edgeIndices { + edgeIDs[idx] = s.edges[edgeIdx].ID + } return edgeIDs } -func (s *triplestore) adjacent(node uint64, direction graph.Direction) cardinality.Duplex[uint64] { +func (s *triplestore) adjacent(node uint64, direction graph.Direction) []uint64 { nodes := cardinality.NewBitmap64() - s.adjacentEdgeIndices(node, direction).Each(func(edgeIndex uint64) bool { - if edge := s.edges[edgeIndex]; !s.deletedEdges.Contains(edge.ID) { - switch direction { - case graph.DirectionOutbound: - nodes.Add(edge.End) + for _, edgeIndex := range s.adjacentEdgeIndices(node, direction) { + edge := s.edges[edgeIndex] - case graph.DirectionInbound: - nodes.Add(edge.Start) + switch direction { + case graph.DirectionOutbound: + nodes.Add(edge.End) - default: + case graph.DirectionInbound: + nodes.Add(edge.Start) + + default: + if node == edge.Start { nodes.Add(edge.End) + } else { nodes.Add(edge.Start) } } + } - return true - }) - - return nodes + return nodes.Slice() } func (s *triplestore) AdjacentNodes(node uint64, direction graph.Direction) []uint64 { - return s.adjacent(node, direction).Slice() + return s.adjacent(node, direction) } func (s *triplestore) EachAdjacentNode(node uint64, direction graph.Direction, delegate func(adjacent uint64) bool) { - s.adjacent(node, direction).Each(delegate) + for _, node := range s.adjacent(node, direction) { + if !delegate(node) { + break + } + } } func (s *triplestore) Degrees(node uint64, direction graph.Direction) uint64 { if adjacent := s.adjacent(node, direction); adjacent != nil { - return adjacent.Cardinality() + return uint64(len(adjacent)) } return 0 @@ -196,101 +260,9 @@ func (s *triplestore) NumEdges() uint64 { } func (s *triplestore) EachAdjacentEdge(node uint64, direction graph.Direction, delegate func(next Edge) bool) { - s.adjacentEdgeIndices(node, direction).Each(func(edgeIndex uint64) bool { - return delegate(s.edges[edgeIndex]) - }) -} - -func (s *triplestore) Projection(deletedNodes, deletedEdges cardinality.Duplex[uint64]) Triplestore { - return &triplestoreProjection{ - origin: s, - deletedNodes: deletedNodes, - deletedEdges: deletedEdges, - } -} - -type triplestoreProjection struct { - origin *triplestore - deletedNodes cardinality.Duplex[uint64] - deletedEdges cardinality.Duplex[uint64] -} - -func (s *triplestoreProjection) Projection(deletedNodes, deletedEdges cardinality.Duplex[uint64]) Triplestore { - var ( - allDeletedNodes = s.deletedNodes.Clone() - allDeletedEdges = s.deletedEdges.Clone() - ) - - allDeletedNodes.Or(deletedNodes) - allDeletedEdges.Or(deletedEdges) - - return &triplestoreProjection{ - origin: s.origin, - deletedNodes: allDeletedNodes, - deletedEdges: allDeletedEdges, - } -} - -func (s *triplestoreProjection) NumNodes() uint64 { - count := uint64(0) - - s.origin.EachNode(func(value uint64) bool { - if !s.deletedNodes.Contains(value) { - count += 1 - } - - return true - }) - - return count -} - -func (s *triplestoreProjection) NumEdges() uint64 { - count := uint64(0) - - s.origin.EachEdge(func(next Edge) bool { - if !s.deletedEdges.Contains(next.ID) { - count += 1 - } - - return true - }) - - return count -} - -func (s *triplestoreProjection) EachNode(delegate func(node uint64) bool) { - s.origin.EachNode(func(node uint64) bool { - if !s.deletedNodes.Contains(node) { - return delegate(node) - } - - return true - }) -} - -func (s *triplestoreProjection) EachEdge(delegate func(next Edge) bool) { - s.origin.EachEdge(func(next Edge) bool { - if !s.deletedEdges.Contains(next.ID) && !s.deletedNodes.Contains(next.Start) && !s.deletedNodes.Contains(next.Start) { - return delegate(next) - } - - return true - }) -} - -func (s *triplestoreProjection) EachAdjacentEdge(node uint64, direction graph.Direction, delegate func(next Edge) bool) { - s.origin.EachAdjacentEdge(node, direction, func(next Edge) bool { - if !s.deletedEdges.Contains(next.ID) && !s.deletedNodes.Contains(next.Start) && !s.deletedNodes.Contains(next.Start) { - return delegate(next) + for _, edgeIndex := range s.adjacentEdgeIndices(node, direction) { + if !delegate(s.edges[edgeIndex]) { + break } - - return true - }) -} - -func (s *triplestoreProjection) EachAdjacentNode(node uint64, direction graph.Direction, delegate func(adjacent uint64) bool) { - s.EachAdjacentEdge(node, direction, func(next Edge) bool { - return delegate(next.Pick(direction)) - }) + } } diff --git a/container/triplestore_test.go b/container/triplestore_test.go new file mode 100644 index 0000000..f12e3a8 --- /dev/null +++ b/container/triplestore_test.go @@ -0,0 +1,202 @@ +package container + +import ( + "math/rand" + "testing" + + "github.com/specterops/dawgs/graph" + "github.com/stretchr/testify/assert" +) + +func TestAddNode(t *testing.T) { + ts := NewTriplestore() + + node := uint64(10) + ts.AddNode(node) + assert.Equal(t, uint64(1), ts.NumNodes()) +} + +func TestAddEdge(t *testing.T) { + ts := NewTriplestore() + + edge := Edge{ + ID: 1, + Kind: graph.StringKind("12345"), + Start: 10, + End: 20, + } + + ts.AddEdge(edge) + assert.Equal(t, uint64(1), ts.NumEdges()) +} + +func TestEachEdge(t *testing.T) { + ts := NewTriplestore() + + edges := []Edge{ + {ID: 1, Start: 10, End: 20}, + {ID: 2, Start: 20, End: 30}, + } + + for _, e := range edges { + ts.AddEdge(e) + } + + var collected []Edge + ts.EachEdge(func(e Edge) bool { + collected = append(collected, e) + return true + }) + + assert.Equal(t, edges, collected) +} + +func TestAdjacentEdges(t *testing.T) { + ts := NewTriplestore() + + edges := []Edge{ + {ID: 1, Start: 10, End: 20}, + {ID: 2, Start: 20, End: 30}, + {ID: 3, Start: 10, End: 30}, + } + + for _, e := range edges { + ts.AddEdge(e) + } + + dir := graph.DirectionOutbound + adjacent := AdjacentEdges(ts, 10, dir) + assert.Len(t, adjacent, 2) + + dir = graph.DirectionInbound + adjacent = AdjacentEdges(ts, 30, dir) + assert.Len(t, adjacent, 2) +} + +func TestSort(t *testing.T) { + ts := NewTriplestore() + + edges := []Edge{ + {ID: 3, Start: 10, End: 20}, + {ID: 1, Start: 20, End: 30}, + {ID: 2, Start: 10, End: 30}, + } + + for _, e := range edges { + ts.AddEdge(e) + } + + ts.Sort() + + expected := []Edge{ + {ID: 1, Start: 20, End: 30}, + {ID: 2, Start: 10, End: 30}, + {ID: 3, Start: 10, End: 20}, + } + + var collected []Edge + ts.EachEdge(func(e Edge) bool { + collected = append(collected, e) + return true + }) + + assert.Equal(t, expected, collected) +} + +func TestDegrees(t *testing.T) { + ts := NewTriplestore() + + edges := []Edge{ + {ID: 1, Start: 10, End: 20}, + {ID: 2, Start: 20, End: 30}, + {ID: 3, Start: 10, End: 30}, + } + + for _, e := range edges { + ts.AddEdge(e) + } + + degOut := Degrees(ts, 10, graph.DirectionOutbound) + assert.Equal(t, uint64(2), degOut) + + degIn := Degrees(ts, 30, graph.DirectionInbound) + assert.Equal(t, uint64(2), degIn) +} + +func TestEachAdjacentNode(t *testing.T) { + ts := NewTriplestore() + + edges := []Edge{ + {ID: 1, Start: 10, End: 20}, + {ID: 2, Start: 20, End: 30}, + {ID: 3, Start: 10, End: 30}, + } + + for _, e := range edges { + ts.AddEdge(e) + } + + var collected []uint64 + ts.EachAdjacentNode(10, graph.DirectionOutbound, func(n uint64) bool { + collected = append(collected, n) + return true + }) + + assert.ElementsMatch(t, []uint64{20, 30}, collected) +} + +func TestNumNodes(t *testing.T) { + ts := NewTriplestore() + + ts.AddNode(10) + ts.AddNode(20) + ts.AddNode(30) + + assert.Equal(t, uint64(3), ts.NumNodes()) +} + +func TestAddMultipleEdges(t *testing.T) { + ts := NewTriplestore() + + edges := make([]Edge, 100) + for i := range edges { + edges[i] = Edge{ + ID: uint64(i), + Start: uint64(rand.Intn(100)), + End: uint64(rand.Intn(100)), + } + } + + for _, e := range edges { + ts.AddEdge(e) + } + + assert.Equal(t, uint64(len(edges)), ts.NumEdges()) +} + +func TestEachAdjacentEdge(t *testing.T) { + ts := NewTriplestore() + + edges := []Edge{ + {ID: 1, Start: 10, End: 20}, + {ID: 2, Start: 20, End: 30}, + {ID: 3, Start: 10, End: 30}, + } + + for _, e := range edges { + ts.AddEdge(e) + } + + var collected []Edge + ts.EachAdjacentEdge(10, graph.DirectionOutbound, func(e Edge) bool { + collected = append(collected, e) + return true + }) + + expected := []Edge{ + {ID: 1, Start: 10, End: 20}, + {ID: 3, Start: 10, End: 30}, + } + + assert.Equal(t, expected, collected) +} diff --git a/go.mod b/go.mod index e294cab..8dbd864 100644 --- a/go.mod +++ b/go.mod @@ -28,11 +28,11 @@ require ( github.com/lib/pq v1.10.9 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/crypto v0.47.0 // indirect + golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect - golang.org/x/net v0.49.0 // indirect + golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/text v0.33.0 // indirect + golang.org/x/text v0.34.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e9c170f..63a33f3 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -209,8 +209,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -250,8 +250,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= diff --git a/graph/types.go b/graph/types.go index e28b1bb..9e0bb74 100644 --- a/graph/types.go +++ b/graph/types.go @@ -182,7 +182,7 @@ func (s ThreadSafeKindBitmap) Clone() *ThreadSafeKindBitmap { clone := NewThreadSafeKindBitmap() for kind, kindBitmap := range s.bitmaps { - clone.bitmaps[kind] = kindBitmap.Clone() + clone.bitmaps[kind] = kindBitmap.Clone().(cardinality.Duplex[uint64]) } return clone