Index: lucene/CHANGES.txt
===================================================================
--- lucene/CHANGES.txt (revision 1531627)
+++ lucene/CHANGES.txt (working copy)
@@ -136,6 +136,9 @@
* LUCENE-5269: Fix bug in NGramTokenFilter where it would sometimes count
unicode characters incorrectly. (Mike McCandless, Robert Muir)
+* LUCENE-5272: OpenBitSet.ensureCapacity did not modify numBits, causing
+ false assertion errors in fastSet. (Shai Erera)
+
API Changes:
* LUCENE-5222: Add SortField.needsScores(). Previously it was not possible
Index: lucene/core/src/java/org/apache/lucene/util/OpenBitSet.java
===================================================================
--- lucene/core/src/java/org/apache/lucene/util/OpenBitSet.java (revision 1531627)
+++ lucene/core/src/java/org/apache/lucene/util/OpenBitSet.java (working copy)
@@ -81,8 +81,7 @@
// Used only for assert:
private long numBits;
- /** Constructs an OpenBitSet large enough to hold numBits.
- */
+ /** Constructs an OpenBitSet large enough to hold {@code numBits}. */
public OpenBitSet(long numBits) {
this.numBits = numBits;
bits = new long[bits2words(numBits)];
@@ -89,24 +88,28 @@
wlen = bits.length;
}
+ /** Constructor: allocates enough space for 64 bits. */
public OpenBitSet() {
this(64);
}
- /** Constructs an OpenBitSet from an existing long[].
- *
- * The first 64 bits are in long[0],
- * with bit index 0 at the least significant bit, and bit index 63 at the most significant.
- * Given a bit index,
- * the word containing it is long[index/64], and it is at bit number index%64 within that word.
+ /**
+ * Constructs an OpenBitSet from an existing long[].
*
- * numWords are the number of elements in the array that contain - * set bits (non-zero longs). - * numWords should be <= bits.length, and - * any existing words in the array at position >= numWords should be zero. - * + * The first 64 bits are in long[0], with bit index 0 at the least significant + * bit, and bit index 63 at the most significant. Given a bit index, the word + * containing it is long[index/64], and it is at bit number index%64 within + * that word. + *
+ * numWords are the number of elements in the array that contain set bits + * (non-zero longs). numWords should be <= bits.length, and any existing + * words in the array at position >= numWords should be zero. + * */ public OpenBitSet(long[] bits, int numWords) { + if (numWords > bits.length) { + throw new IllegalArgumentException("numWords cannot exceed bits.length"); + } this.bits = bits; this.wlen = numWords; this.numBits = wlen * 64; @@ -150,17 +153,9 @@ /** Expert: returns the long[] storing the bits */ public long[] getBits() { return bits; } - /** Expert: sets a new long[] to use as the bit storage */ - public void setBits(long[] bits) { this.bits = bits; } - /** Expert: gets the number of longs in the array that are in use */ public int getNumWords() { return wlen; } - /** Expert: sets the number of longs in the array that are in use */ - public void setNumWords(int nWords) { this.wlen=nWords; } - - - /** Returns true or false for the specified bit index. */ @Override public boolean get(int index) { @@ -287,7 +282,7 @@ // since endIndex is one past the end, this is index of the last // word to be changed. - int endWord = expandingWordNum(endIndex-1); + int endWord = expandingWordNum(endIndex-1); long startmask = -1L << startIndex; long endmask = -1L >>> -endIndex; // 64-(endIndex&0x3f) is the same as -endIndex due to wrap @@ -302,19 +297,14 @@ bits[endWord] |= endmask; } - - protected int expandingWordNum(long index) { int wordNum = (int)(index >> 6); - if (wordNum>=wlen) { - ensureCapacity(index+1); - wlen = wordNum+1; + if (wordNum >= wlen) { + ensureCapacity(index + 1); } - assert (numBits = Math.max(numBits, index+1)) >= 0; return wordNum; } - /** clears a bit. * The index should be less than the OpenBitSet size. */ @@ -519,7 +509,7 @@ // since endIndex is one past the end, this is index of the last // word to be changed. - int endWord = expandingWordNum(endIndex-1); + int endWord = expandingWordNum(endIndex-1); /*** Grrr, java shifting wraps around so -1L>>>64 == -1 * for that reason, make sure not to use endmask if the bits to flip will @@ -833,22 +823,22 @@ return false; } - - - /** Expand the long[] with the size given as a number of words (64 bit longs). - * getNumWords() is unchanged by this call. - */ + /** Expand the long[] with the size given as a number of words (64 bit longs). */ public void ensureCapacityWords(int numWords) { - if (bits.length < numWords) { - bits = ArrayUtil.grow(bits, numWords); - } + bits = ArrayUtil.grow(bits, numWords); + wlen = numWords; + assert (this.numBits = Math.max(this.numBits, numWords << 6)) >= 0; } - /** Ensure that the long[] is big enough to hold numBits, expanding it if necessary. - * getNumWords() is unchanged by this call. + /** + * Ensure that the long[] is big enough to hold numBits, expanding it if + * necessary. */ public void ensureCapacity(long numBits) { ensureCapacityWords(bits2words(numBits)); + // ensureCapacityWords sets numBits to a multiple of 64, but we want to set + // it to exactly what the app asked. + assert (this.numBits = Math.max(this.numBits, numBits + 1)) >= 0; } /** Lowers numWords, the number of words in use, @@ -862,10 +852,9 @@ /** returns the number of 64 bit words it would take to hold numBits */ public static int bits2words(long numBits) { - return (int)(((numBits-1)>>>6)+1); + return (int)(((numBits-1)>>>6)+1); } - /** returns true if both sets have the same bits set */ @Override public boolean equals(Object o) { Index: lucene/core/src/test/org/apache/lucene/util/TestOpenBitSet.java =================================================================== --- lucene/core/src/test/org/apache/lucene/util/TestOpenBitSet.java (revision 1531627) +++ lucene/core/src/test/org/apache/lucene/util/TestOpenBitSet.java (working copy) @@ -331,6 +331,38 @@ checkPrevSetBitArray(new int[] {0,2}); } + public void testEnsureCapacity() { + OpenBitSet bits = new OpenBitSet(1); + int bit = random().nextInt(100) + 10; + bits.ensureCapacity(bit); // make room for more bits + bits.fastSet(bit); + assertTrue(bits.fastGet(bit)); + bits.ensureCapacity(bit + 1); + bits.fastSet(bit + 1); + assertTrue(bits.fastGet(bit + 1)); + bits.ensureCapacity(3); // should not change numBits nor grow the array + bits.fastSet(3); + assertTrue(bits.fastGet(3)); + bits.fastSet(bit-1); + assertTrue(bits.fastGet(bit-1)); + + // test ensureCapacityWords + int numWords = random().nextInt(10) + 2; // make sure we grow the array (at least 128 bits) + bits.ensureCapacityWords(numWords); + bit = _TestUtil.nextInt(random(), 128, numWords << 6); // pick a higher bit than 128, but still within range + bits.fastSet(bit); + assertTrue(bits.fastGet(bit)); + bits.fastClear(bit); + assertFalse(bits.fastGet(bit)); + bits.fastFlip(bit); + assertTrue(bits.fastGet(bit)); + bits.ensureCapacityWords(2); // should not change numBits nor grow the array + bits.fastSet(3); + assertTrue(bits.fastGet(3)); + bits.fastSet(bit-1); + assertTrue(bits.fastGet(bit-1)); + } + }