This is an automated email from the ASF dual-hosted git repository.

dcapwell pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-accord.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 29270f5b Fixed LargeBitSet and SmallBitSet to have the same semantics 
with inbound and out of bound inputs.  This was detected first by Cassandras 
SimpleBitSetSerializersTest#any test (#264)
29270f5b is described below

commit 29270f5b3460fa6eb23b97abe93363ddf6c54bee
Author: dcapwell <[email protected]>
AuthorDate: Wed Jan 7 12:36:24 2026 -0800

    Fixed LargeBitSet and SmallBitSet to have the same semantics with inbound 
and out of bound inputs.  This was detected first by Cassandras 
SimpleBitSetSerializersTest#any test (#264)
    
    patch by David Capwell; reviewed by Benedict Elliott Smith for 
CASSANDRA-21077
---
 .../main/java/accord/utils/ImmutableBitSet.java    |   2 +-
 .../src/main/java/accord/utils/LargeBitSet.java    |  37 +++++---
 .../src/main/java/accord/utils/SimpleBitSet.java   |   2 +-
 .../src/main/java/accord/utils/SmallBitSet.java    |  37 +++++++-
 .../test/java/accord/utils/SimpleBitSetTest.java   | 102 ++++++++++++++++++++-
 5 files changed, 161 insertions(+), 19 deletions(-)

diff --git a/accord-core/src/main/java/accord/utils/ImmutableBitSet.java 
b/accord-core/src/main/java/accord/utils/ImmutableBitSet.java
index bdc4a3f4..0a9e4598 100644
--- a/accord-core/src/main/java/accord/utils/ImmutableBitSet.java
+++ b/accord-core/src/main/java/accord/utils/ImmutableBitSet.java
@@ -67,7 +67,7 @@ public class ImmutableBitSet extends LargeBitSet
     }
 
     @Override
-    public void setRange(int from, int to)
+    public void setRange(int fromInclusive, int toExclusive)
     {
         throw new UnsupportedOperationException();
     }
diff --git a/accord-core/src/main/java/accord/utils/LargeBitSet.java 
b/accord-core/src/main/java/accord/utils/LargeBitSet.java
index fbeae5bf..60a07438 100644
--- a/accord-core/src/main/java/accord/utils/LargeBitSet.java
+++ b/accord-core/src/main/java/accord/utils/LargeBitSet.java
@@ -87,6 +87,7 @@ public class LargeBitSet implements SimpleBitSet
         Arrays.fill(bits, 0, length, 0L);
     }
 
+    @Override
     public boolean set(int i)
     {
         int index = indexOf(i);
@@ -98,36 +99,38 @@ public class LargeBitSet implements SimpleBitSet
         return true;
     }
 
-    public void setRange(int from, int to)
+    @Override
+    public void setRange(int fromInclusive, int toExclusive)
     {
-        Invariants.requireArgument(from <= to, "from > to (%s > %s)", from, 
to);
-        if (from == to)
+        int fromIndex = indexOf(fromInclusive);  // validates input so call 
early
+        validateExclusive(toExclusive);
+        Invariants.requireArgument(fromInclusive <= toExclusive, "from > to 
(%s > %s)", fromInclusive, toExclusive);
+        if (fromInclusive == toExclusive)
             return;
 
-        int fromIndex = from >>> 6;
-        int toIndex = (to + 63) >>> 6;
+        int toIndex = (toExclusive + 63) >>> 6;
         if (fromIndex + 1 == toIndex)
         {
-            long addBits = (-1L >>> (64 - (to & 63))) & (-1L << (from & 63));
+            long addBits = (-1L >>> (64 - (toExclusive & 63))) & (-1L << 
(fromInclusive & 63));
             orBitsAtIndex(fromIndex,  addBits);
         }
         else if (count == 0)
         {
-            bits[toIndex - 1] = -1L >>> (64 - (to & 63));
+            bits[toIndex - 1] = -1L >>> (64 - (toExclusive & 63));
             for (int i = fromIndex + 1, maxi = toIndex - 1; i < maxi ; ++i)
                 bits[i] = -1L;
-            bits[fromIndex] = -1L << (from & 63);
-            count = to - from;
+            bits[fromIndex] = -1L << (fromInclusive & 63);
+            count = toExclusive - fromInclusive;
         }
         else
         {
-            orBitsAtIndex(fromIndex, -1L << (from & 63));
+            orBitsAtIndex(fromIndex, -1L << (fromInclusive & 63));
             for (int i = fromIndex + 1, maxi = toIndex - 1; i < maxi ; ++i)
             {
                 count += 64 - Long.bitCount(bits[i]);
                 bits[i] = -1L;
             }
-            orBitsAtIndex(toIndex - 1, -1L >>> (64 - (to & 63)));
+            orBitsAtIndex(toIndex - 1, -1L >>> (64 - (toExclusive & 63)));
         }
     }
 
@@ -163,6 +166,7 @@ public class LargeBitSet implements SimpleBitSet
         count += Long.bitCount(nextBits) - Long.bitCount(prevBits);
     }
 
+    @Override
     public boolean unset(int i)
     {
         int index = indexOf(i);
@@ -174,6 +178,7 @@ public class LargeBitSet implements SimpleBitSet
         return true;
     }
 
+    @Override
     public final boolean get(int i)
     {
         int index = indexOf(i);
@@ -320,7 +325,7 @@ public class LargeBitSet implements SimpleBitSet
     private int nextSetBitInternal(int i, int exclIndexBound, int ifNotFound)
     {
         Invariants.requireArgument(i >= 0);
-        Invariants.requireArgument(i <= size());
+        validateExclusive(i);
 
         if (count == 0)
             return ifNotFound;
@@ -443,10 +448,16 @@ public class LargeBitSet implements SimpleBitSet
     {
         int index = i >>> 6;
         if (index >= length)
-            throw new IndexOutOfBoundsException(String.format("%d >= %d", 
index, length));
+            throw new IndexOutOfBoundsException(String.format("Unable to 
access bit %d; must be between 0 and %d", i, size() - 1));
         return index;
     }
 
+    private void validateExclusive(int i)
+    {
+        if (i > size())
+            throw new IndexOutOfBoundsException(String.format("Unable to 
access bit %d; must be between 0 and %d", i, size()));
+    }
+
     private int lowerLimitOf(int i)
     {
         return i >>> 6;
diff --git a/accord-core/src/main/java/accord/utils/SimpleBitSet.java 
b/accord-core/src/main/java/accord/utils/SimpleBitSet.java
index 4380e3c8..695ca935 100644
--- a/accord-core/src/main/java/accord/utils/SimpleBitSet.java
+++ b/accord-core/src/main/java/accord/utils/SimpleBitSet.java
@@ -22,7 +22,7 @@ public interface SimpleBitSet
 {
     boolean get(int i);
     boolean set(int i);
-    void setRange(int from, int to);
+    void setRange(int fromInclusive, int toExclusive);
     boolean unset(int i);
     void clear();
     boolean isEmpty();
diff --git a/accord-core/src/main/java/accord/utils/SmallBitSet.java 
b/accord-core/src/main/java/accord/utils/SmallBitSet.java
index 2deb43b3..e4cdeb64 100644
--- a/accord-core/src/main/java/accord/utils/SmallBitSet.java
+++ b/accord-core/src/main/java/accord/utils/SmallBitSet.java
@@ -18,7 +18,6 @@
 
 package accord.utils;
 
-import static accord.utils.LargeBitSet.bit;
 import static accord.utils.LargeBitSet.bitsEqualOrGreater;
 import static java.lang.Long.numberOfTrailingZeros;
 
@@ -40,6 +39,25 @@ public class SmallBitSet implements SimpleBitSet
         return bits;
     }
 
+    private static long bit(int i)
+    {
+        validateInclusive(i);
+        return 1L << i;
+    }
+
+    private static void validateInclusive(int i)
+    {
+        if (i >>> 6 > 0) // i >= 64 || i < 0
+            throw new IndexOutOfBoundsException("Unable to access bit " + i + 
"; must be between 0 and 63");
+    }
+
+    private static void validateExclusive(int i)
+    {
+        if (i >= 65 || i < 0)
+            throw new IndexOutOfBoundsException("Unable to access bit " + i + 
"; must be between 0 and 64");
+    }
+
+    @Override
     public boolean set(int i)
     {
         long bit = bit(i);
@@ -49,17 +67,25 @@ public class SmallBitSet implements SimpleBitSet
     }
 
     @Override
-    public void setRange(int from, int to)
+    public void setRange(int fromInclusive, int toExclusive)
     {
-        bits |= ~(-1L << to) & (-1L << from);
+        validateInclusive(fromInclusive);
+        validateExclusive(toExclusive);
+        Invariants.requireArgument(fromInclusive <= toExclusive, "from > to 
(%s > %s)", fromInclusive, toExclusive);
+        if (fromInclusive == toExclusive)
+            return;
+
+        bits |= (-1L >>> (64 - (toExclusive & 63))) & (-1L << (fromInclusive & 
63));
     }
 
+    @Override
     public boolean get(int i)
     {
         long bit = bit(i);
         return 0 != (bits & bit);
     }
 
+    @Override
     public boolean unset(int i)
     {
         long bit = bit(i);
@@ -83,6 +109,11 @@ public class SmallBitSet implements SimpleBitSet
     @Override
     public int nextSetBit(int fromIndex)
     {
+        if (fromIndex >= 64)
+        {
+            validateExclusive(fromIndex);
+            return -1;
+        }
         long bits = this.bits & bitsEqualOrGreater(fromIndex);
         if (bits == 0)
             return -1;
diff --git a/accord-core/src/test/java/accord/utils/SimpleBitSetTest.java 
b/accord-core/src/test/java/accord/utils/SimpleBitSetTest.java
index 96deea32..69976ad5 100644
--- a/accord-core/src/test/java/accord/utils/SimpleBitSetTest.java
+++ b/accord-core/src/test/java/accord/utils/SimpleBitSetTest.java
@@ -20,6 +20,8 @@ package accord.utils;
 
 
 import java.util.BitSet;
+import java.util.List;
+import java.util.function.Consumer;
 
 import org.junit.jupiter.api.Test;
 
@@ -42,6 +44,21 @@ class SimpleBitSetTest
         return new Property.SimpleCommand<>("Set(" + idx + ')', s2 -> 
s2.set(idx));
     }
 
+    private static Property.Command<State, Void, ?> setRange(RandomSource rs, 
State state)
+    {
+        int from = rs.nextInt(0, state.size);
+        int to = rs.nextInt(0, state.size + 1); // do not filter from == to as 
this "must" no-op
+        if (from > to)
+        {
+            int tmp = from;
+            from = to;
+            to = tmp;
+        }
+        int finalFrom = from;
+        int finalTo = to;
+        return new Property.SimpleCommand<>("setRange(" + from + ", " + to + 
')', s2 -> s2.setRange(finalFrom, finalTo));
+    }
+
     private static Property.Command<State, Void, ?> unset(RandomSource rs, 
State state)
     {
         int idx = rs.nextInt(0, state.size);
@@ -64,19 +81,81 @@ class SimpleBitSetTest
         return new Property.SimpleCommand<>("NextSetBit(" + idx + ')', s2 -> 
s2.nextSetBit(idx));
     }
 
+    private static Property.Command<State, Void, ?> 
getSetBitCount(RandomSource rs, State state)
+    {
+        return new Property.SimpleCommand<>("getSetBitCount()", s2 -> 
s2.getSetBitCount());
+    }
+
     @Test
     public void test()
     {
         stateful().withExamples(1000).check(commands(() -> State::new)
                                             .add(SimpleBitSetTest::get)
                                             .add(SimpleBitSetTest::set)
+                                            .add(SimpleBitSetTest::setRange)
                                             .add(SimpleBitSetTest::unset)
                                             .add(SimpleBitSetTest::clear)
                                             .add(SimpleBitSetTest::isEmpty)
                                             .add(SimpleBitSetTest::nextSetBit)
+                                            
.add(SimpleBitSetTest::getSetBitCount)
                                             .build());
     }
 
+    @Test
+    public void outOfRange()
+    {
+        SmallBitSet model = new SmallBitSet();
+        LargeBitSet target = new LargeBitSet(64);
+
+        for (var set : List.of(model, target))
+            set.setRange(0, 64);
+
+        // get methods do not reject, but return false or -1
+        
Assertions.assertThat(target.getSetBitCount()).isEqualTo(model.getSetBitCount());
+        Assertions.assertThat(target.isEmpty()).isEqualTo(model.isEmpty());
+
+        Assertions.assertThat(target.nextSetBit(64))
+                .describedAs("nextSetBit(%s)", 64)
+                .isEqualTo(model.nextSetBit(64))
+                .isEqualTo(-1);
+
+        // set methods reject
+        for (int index : new int[] {64, 65, Integer.MAX_VALUE})
+        {
+            asertThrownSameWay(model, target, bs -> bs.get(index));
+            asertThrownSameWay(model, target, bs -> bs.set(index));
+            asertThrownSameWay(model, target, bs -> bs.setRange(index, 100)); 
// from/index gets rejected
+            asertThrownSameWay(model, target, bs -> bs.setRange(0, 100));     
// to gets rejected
+            asertThrownSameWay(model, target, bs -> bs.unset(index));
+
+            if (index > 64)
+                asertThrownSameWay(model, target, bs -> bs.nextSetBit(index));
+        }
+
+        asertThrownSameWay(model, target, bs -> bs.setRange(1, 0));
+    }
+
+    private static void asertThrownSameWay(SimpleBitSet model, SimpleBitSet 
target, Consumer<SimpleBitSet> fn)
+    {
+        Throwable expected = assertThrown(() -> fn.accept(target));
+        Throwable actual = assertThrown(() -> fn.accept(model));
+
+        
Assertions.assertThat(actual).hasSameClassAs(expected).hasMessage(expected.getMessage());
+    }
+
+    private static Throwable assertThrown(Runnable fn)
+    {
+        try
+        {
+            fn.run();
+        }
+        catch (Throwable t)
+        {
+            return t;
+        }
+        throw new AssertionError("Expected logic to throw but did not");
+    }
+
     private static class State implements SimpleBitSet
     {
         private final BitSet model;
@@ -114,7 +193,10 @@ class SimpleBitSetTest
         @Override
         public void setRange(int from, int to)
         {
-            throw new UnsupportedOperationException();
+            model.set(from, to);
+            sut.setRange(from, to);
+
+            getSetBitCount();
         }
 
         @Override
@@ -167,9 +249,27 @@ class SimpleBitSetTest
         @Override
         public int getSetBitCount()
         {
+            Assertions.assertThat(sut.getSetBitCount())
+                      .describedAs(toBinaryString())
+                      .isEqualTo(model.cardinality());
             return sut.getSetBitCount();
         }
 
+        public String toBinaryString()
+        {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < size; i++)
+            {
+                boolean m = model.get(i);
+                boolean s = sut.get(i);
+                if (m == s)
+                    sb.append(m ? 1 : 0);
+                else
+                    sb.append("(expected=").append(m ? 1 : 0).append(", 
actual=").append(s ? 1 : 0).append(')');
+            }
+            return sb.toString();
+        }
+
         @Override
         public String toString()
         {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to