From 874686297a0fffb7dbd38296ff354ec067759440 Mon Sep 17 00:00:00 2001 From: Stefano Scafiti Date: Thu, 21 May 2026 12:56:26 +0200 Subject: [PATCH] roaring64: honor copy-on-write in Bitmap.Or When two bitmaps share a container key, the in-place Or was calling getContainerAtIndex, which returns the raw container without consulting needCopyOnWrite. If rb's container at that index was shared with another bitmap (e.g. produced by Clone() on a CoW-enabled bitmap), the Or would mutate the shared container in place and silently corrupt the other bitmap. Switch to getWritableContainerAtIndex, which clones the container first when needCopyOnWrite is set. This matches what the sibling AndNot/And methods in the same file already do, and what the 32-bit Bitmap.Or does via getUnionedWritableContainer. --- roaring64/roaring64.go | 2 +- roaring64/roaring64cow_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/roaring64/roaring64.go b/roaring64/roaring64.go index 8589925e..adb508fe 100644 --- a/roaring64/roaring64.go +++ b/roaring64/roaring64.go @@ -700,7 +700,7 @@ main: } s2 = x2.highlowcontainer.getKeyAtIndex(pos2) } else { - rb.highlowcontainer.getContainerAtIndex(pos1).Or(x2.highlowcontainer.getContainerAtIndex(pos2)) + rb.highlowcontainer.getWritableContainerAtIndex(pos1).Or(x2.highlowcontainer.getContainerAtIndex(pos2)) pos1++ pos2++ if (pos1 == length1) || (pos2 == length2) { diff --git a/roaring64/roaring64cow_test.go b/roaring64/roaring64cow_test.go index fb9eb38d..ac7010cd 100644 --- a/roaring64/roaring64cow_test.go +++ b/roaring64/roaring64cow_test.go @@ -1768,3 +1768,32 @@ func TestCloneCOWContainers(t *testing.T) { //assert.EqualValues(t, rb.ToArray(), newRb1.ToArray()) } + +// TestOrCOWSharedContainer verifies that in-place Or on a CoW-cloned bitmap +// does not mutate containers shared with the clone source. +func TestOrCOWSharedContainer(t *testing.T) { + rb1 := NewBitmap() + rb1.SetCopyOnWrite(true) + // Populate a single high-key container with values in [0, 1000). + for i := uint64(0); i < 1000; i++ { + rb1.Add(i) + } + + // Clone: rb2 shares rb1's container, with needCopyOnWrite set on both sides. + rb2 := rb1.Clone() + + // x has values in the same high-key container that are not already in rb1/rb2. + x := NewBitmap() + for i := uint64(1000); i < 2000; i++ { + x.Add(i) + } + + // In-place Or into rb1. Must NOT mutate the container shared with rb2. + rb1.Or(x) + + assert.EqualValues(t, 2000, rb1.GetCardinality()) + assert.EqualValues(t, 1000, rb2.GetCardinality(), + "rb2 was corrupted: Or on rb1 mutated a CoW-shared container") + assert.False(t, rb2.Contains(1500), + "rb2 was corrupted: contains a value that was only added to rb1") +}