Fix BytesReferenceStreamInput#skip with offset (#25634)

There is a bug when a call to `BytesReferenceStreamInput` skip is made
on a `BytesReference` that has an initial offset. The offset for the
current slice is added to the current index and then subtracted from the
length. This introduces the possibility of a negative number of bytes to
skip. This happens inside a loop, which leads to an infinte loop.

This commit correctly subtracts the current slice index from the
slice.length. Additionally, the `BytesArrayTests` are modified to test
instances that include an offset.
This commit is contained in:
Tim Brooks 2017-07-11 09:54:29 -05:00 committed by GitHub
parent 98c91a3bd0
commit a3ade99fcf
6 changed files with 48 additions and 18 deletions

View File

@ -32,7 +32,7 @@ import java.io.IOException;
*/ */
final class BytesReferenceStreamInput extends StreamInput { final class BytesReferenceStreamInput extends StreamInput {
private final BytesRefIterator iterator; private final BytesRefIterator iterator;
private int sliceOffset; private int sliceIndex;
private BytesRef slice; private BytesRef slice;
private final int length; // the total size of the stream private final int length; // the total size of the stream
private int offset; // the current position of the stream private int offset; // the current position of the stream
@ -42,7 +42,7 @@ final class BytesReferenceStreamInput extends StreamInput {
this.slice = iterator.next(); this.slice = iterator.next();
this.length = length; this.length = length;
this.offset = 0; this.offset = 0;
this.sliceOffset = 0; this.sliceIndex = 0;
} }
@Override @Override
@ -51,15 +51,15 @@ final class BytesReferenceStreamInput extends StreamInput {
throw new EOFException(); throw new EOFException();
} }
maybeNextSlice(); maybeNextSlice();
byte b = slice.bytes[slice.offset + (sliceOffset++)]; byte b = slice.bytes[slice.offset + (sliceIndex++)];
offset++; offset++;
return b; return b;
} }
private void maybeNextSlice() throws IOException { private void maybeNextSlice() throws IOException {
while (sliceOffset == slice.length) { while (sliceIndex == slice.length) {
slice = iterator.next(); slice = iterator.next();
sliceOffset = 0; sliceIndex = 0;
if (slice == null) { if (slice == null) {
throw new EOFException(); throw new EOFException();
} }
@ -92,12 +92,12 @@ final class BytesReferenceStreamInput extends StreamInput {
int destOffset = bOffset; int destOffset = bOffset;
while (remaining > 0) { while (remaining > 0) {
maybeNextSlice(); maybeNextSlice();
final int currentLen = Math.min(remaining, slice.length - sliceOffset); final int currentLen = Math.min(remaining, slice.length - sliceIndex);
assert currentLen > 0 : "length has to be > 0 to make progress but was: " + currentLen; assert currentLen > 0 : "length has to be > 0 to make progress but was: " + currentLen;
System.arraycopy(slice.bytes, slice.offset + sliceOffset, b, destOffset, currentLen); System.arraycopy(slice.bytes, slice.offset + sliceIndex, b, destOffset, currentLen);
destOffset += currentLen; destOffset += currentLen;
remaining -= currentLen; remaining -= currentLen;
sliceOffset += currentLen; sliceIndex += currentLen;
offset += currentLen; offset += currentLen;
assert remaining >= 0 : "remaining: " + remaining; assert remaining >= 0 : "remaining: " + remaining;
} }
@ -129,9 +129,9 @@ final class BytesReferenceStreamInput extends StreamInput {
int remaining = numBytesSkipped; int remaining = numBytesSkipped;
while (remaining > 0) { while (remaining > 0) {
maybeNextSlice(); maybeNextSlice();
int currentLen = Math.min(remaining, slice.length - (slice.offset + sliceOffset)); int currentLen = Math.min(remaining, slice.length - sliceIndex);
remaining -= currentLen; remaining -= currentLen;
sliceOffset += currentLen; sliceIndex += currentLen;
offset += currentLen; offset += currentLen;
assert remaining >= 0 : "remaining: " + remaining; assert remaining >= 0 : "remaining: " + remaining;
} }

View File

@ -24,15 +24,25 @@ import org.hamcrest.Matchers;
import java.io.IOException; import java.io.IOException;
public class BytesArrayTests extends AbstractBytesReferenceTestCase { public class BytesArrayTests extends AbstractBytesReferenceTestCase {
@Override @Override
protected BytesReference newBytesReference(int length) throws IOException { protected BytesReference newBytesReference(int length) throws IOException {
return newBytesReference(length, randomInt(length));
}
@Override
protected BytesReference newBytesReferenceWithOffsetOfZero(int length) throws IOException {
return newBytesReference(length, 0);
}
private BytesReference newBytesReference(int length, int offset) throws IOException {
// we know bytes stream output always creates a paged bytes reference, we use it to create randomized content // we know bytes stream output always creates a paged bytes reference, we use it to create randomized content
final BytesStreamOutput out = new BytesStreamOutput(length); final BytesStreamOutput out = new BytesStreamOutput(length + offset);
for (int i = 0; i < length; i++) { for (int i = 0; i < length + offset; i++) {
out.writeByte((byte) random().nextInt(1 << 8)); out.writeByte((byte) random().nextInt(1 << 8));
} }
assertEquals(length, out.size()); assertEquals(length + offset, out.size());
BytesArray ref = new BytesArray(out.bytes().toBytesRef()); BytesArray ref = new BytesArray(out.bytes().toBytesRef().bytes, offset, length);
assertEquals(length, ref.length()); assertEquals(length, ref.length());
assertTrue(ref instanceof BytesArray); assertTrue(ref instanceof BytesArray);
assertThat(ref.length(), Matchers.equalTo(length)); assertThat(ref.length(), Matchers.equalTo(length));
@ -46,14 +56,14 @@ public class BytesArrayTests extends AbstractBytesReferenceTestCase {
BytesArray pbr = (BytesArray) newBytesReference(sizes[i]); BytesArray pbr = (BytesArray) newBytesReference(sizes[i]);
byte[] array = pbr.array(); byte[] array = pbr.array();
assertNotNull(array); assertNotNull(array);
assertEquals(sizes[i], array.length); assertEquals(sizes[i], array.length - pbr.offset());
assertSame(array, pbr.array()); assertSame(array, pbr.array());
} }
} }
public void testArrayOffset() throws IOException { public void testArrayOffset() throws IOException {
int length = randomInt(PAGE_SIZE * randomIntBetween(2, 5)); int length = randomInt(PAGE_SIZE * randomIntBetween(2, 5));
BytesArray pbr = (BytesArray) newBytesReference(length); BytesArray pbr = (BytesArray) newBytesReferenceWithOffsetOfZero(length);
assertEquals(0, pbr.offset()); assertEquals(0, pbr.offset());
} }
} }

View File

@ -29,8 +29,14 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
public class CompositeBytesReferenceTests extends AbstractBytesReferenceTestCase { public class CompositeBytesReferenceTests extends AbstractBytesReferenceTestCase {
@Override @Override
protected BytesReference newBytesReference(int length) throws IOException { protected BytesReference newBytesReference(int length) throws IOException {
return newBytesReferenceWithOffsetOfZero(length);
}
@Override
protected BytesReference newBytesReferenceWithOffsetOfZero(int length) throws IOException {
// we know bytes stream output always creates a paged bytes reference, we use it to create randomized content // we know bytes stream output always creates a paged bytes reference, we use it to create randomized content
List<BytesReference> referenceList = newRefList(length); List<BytesReference> referenceList = newRefList(length);
BytesReference ref = new CompositeBytesReference(referenceList.toArray(new BytesReference[0])); BytesReference ref = new CompositeBytesReference(referenceList.toArray(new BytesReference[0]));

View File

@ -37,7 +37,13 @@ import java.util.Arrays;
public class PagedBytesReferenceTests extends AbstractBytesReferenceTestCase { public class PagedBytesReferenceTests extends AbstractBytesReferenceTestCase {
@Override
protected BytesReference newBytesReference(int length) throws IOException { protected BytesReference newBytesReference(int length) throws IOException {
return newBytesReferenceWithOffsetOfZero(length);
}
@Override
protected BytesReference newBytesReferenceWithOffsetOfZero(int length) throws IOException {
// we know bytes stream output always creates a paged bytes reference, we use it to create randomized content // we know bytes stream output always creates a paged bytes reference, we use it to create randomized content
ReleasableBytesStreamOutput out = new ReleasableBytesStreamOutput(length, bigarrays); ReleasableBytesStreamOutput out = new ReleasableBytesStreamOutput(length, bigarrays);
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {

View File

@ -28,8 +28,14 @@ import org.elasticsearch.common.io.stream.ReleasableBytesStreamOutput;
import java.io.IOException; import java.io.IOException;
public class ByteBufBytesReferenceTests extends AbstractBytesReferenceTestCase { public class ByteBufBytesReferenceTests extends AbstractBytesReferenceTestCase {
@Override @Override
protected BytesReference newBytesReference(int length) throws IOException { protected BytesReference newBytesReference(int length) throws IOException {
return newBytesReferenceWithOffsetOfZero(length);
}
@Override
protected BytesReference newBytesReferenceWithOffsetOfZero(int length) throws IOException {
ReleasableBytesStreamOutput out = new ReleasableBytesStreamOutput(length, bigarrays); ReleasableBytesStreamOutput out = new ReleasableBytesStreamOutput(length, bigarrays);
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
out.writeByte((byte) random().nextInt(1 << 8)); out.writeByte((byte) random().nextInt(1 << 8));

View File

@ -433,7 +433,7 @@ public abstract class AbstractBytesReferenceTestCase extends ESTestCase {
public void testSliceArrayOffset() throws IOException { public void testSliceArrayOffset() throws IOException {
int length = randomIntBetween(1, PAGE_SIZE * randomIntBetween(2, 5)); int length = randomIntBetween(1, PAGE_SIZE * randomIntBetween(2, 5));
BytesReference pbr = newBytesReference(length); BytesReference pbr = newBytesReferenceWithOffsetOfZero(length);
int sliceOffset = randomIntBetween(0, pbr.length() - 1); // an offset to the end would be len 0 int sliceOffset = randomIntBetween(0, pbr.length() - 1); // an offset to the end would be len 0
int sliceLength = randomIntBetween(1, pbr.length() - sliceOffset); int sliceLength = randomIntBetween(1, pbr.length() - sliceOffset);
BytesReference slice = pbr.slice(sliceOffset, sliceLength); BytesReference slice = pbr.slice(sliceOffset, sliceLength);
@ -466,7 +466,7 @@ public abstract class AbstractBytesReferenceTestCase extends ESTestCase {
public void testSliceToBytesRef() throws IOException { public void testSliceToBytesRef() throws IOException {
int length = randomIntBetween(0, PAGE_SIZE); int length = randomIntBetween(0, PAGE_SIZE);
BytesReference pbr = newBytesReference(length); BytesReference pbr = newBytesReferenceWithOffsetOfZero(length);
// get a BytesRef from a slice // get a BytesRef from a slice
int sliceOffset = randomIntBetween(0, pbr.length()); int sliceOffset = randomIntBetween(0, pbr.length());
int sliceLength = randomIntBetween(0, pbr.length() - sliceOffset); int sliceLength = randomIntBetween(0, pbr.length() - sliceOffset);
@ -544,6 +544,8 @@ public abstract class AbstractBytesReferenceTestCase extends ESTestCase {
protected abstract BytesReference newBytesReference(int length) throws IOException; protected abstract BytesReference newBytesReference(int length) throws IOException;
protected abstract BytesReference newBytesReferenceWithOffsetOfZero(int length) throws IOException;
public void testCompareTo() throws IOException { public void testCompareTo() throws IOException {
final int iters = randomIntBetween(5, 10); final int iters = randomIntBetween(5, 10);
for (int i = 0; i < iters; i++) { for (int i = 0; i < iters; i++) {