Skip to content

Commit fa3d365

Browse files
authored
Fix CompositeBytesReference#slice to not throw AIOOBE with legal offsets. (#35955)
CompositeBytesReference#slice has two bugs: - One that makes it fail if the reference is empty and an empty slice is created, this is #35950 and is fixed by special-casing empty-slices. - One performance bug that makes it always create a composite slice when creating a slice that ends on a boundary, this is fixed by computing `limit` as the index of the sub reference that holds the last element rather than the next element after the slice. Closes #35950
1 parent 54cf1f9 commit fa3d365

File tree

3 files changed

+38
-15
lines changed

3 files changed

+38
-15
lines changed

server/src/main/java/org/elasticsearch/common/bytes/CompositeBytesReference.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.lucene.util.BytesRef;
2323
import org.apache.lucene.util.BytesRefBuilder;
2424
import org.apache.lucene.util.BytesRefIterator;
25+
import org.apache.lucene.util.FutureObjects;
2526
import org.apache.lucene.util.RamUsageEstimator;
2627

2728
import java.io.IOException;
@@ -77,10 +78,16 @@ public int length() {
7778

7879
@Override
7980
public BytesReference slice(int from, int length) {
81+
FutureObjects.checkFromIndexSize(from, length, this.length);
82+
83+
if (length == 0) {
84+
return BytesArray.EMPTY;
85+
}
86+
8087
// for slices we only need to find the start and the end reference
8188
// adjust them and pass on the references in between as they are fully contained
8289
final int to = from + length;
83-
final int limit = getOffsetIndex(from + length);
90+
final int limit = getOffsetIndex(to - 1);
8491
final int start = getOffsetIndex(from);
8592
final BytesReference[] inSlice = new BytesReference[1 + (limit - start)];
8693
for (int i = 0, j = start; i < inSlice.length; i++) {

server/src/test/java/org/elasticsearch/common/bytes/CompositeBytesReferenceTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.apache.lucene.util.BytesRefIterator;
2424
import org.elasticsearch.common.io.stream.BytesStreamOutput;
2525
import org.elasticsearch.common.io.stream.ReleasableBytesStreamOutput;
26+
import org.hamcrest.Matchers;
2627

2728
import java.io.IOException;
2829
import java.util.ArrayList;
@@ -113,4 +114,18 @@ public void testSliceArrayOffset() throws IOException {
113114
public void testSliceToBytesRef() throws IOException {
114115
// CompositeBytesReference shifts offsets
115116
}
117+
118+
public void testSliceIsNotCompositeIfMatchesSingleSubSlice() {
119+
CompositeBytesReference bytesRef = new CompositeBytesReference(
120+
new BytesArray(new byte[12]),
121+
new BytesArray(new byte[15]),
122+
new BytesArray(new byte[13]));
123+
124+
// Slices that cross boundaries are composite too
125+
assertThat(bytesRef.slice(5, 8), Matchers.instanceOf(CompositeBytesReference.class));
126+
127+
// But not slices that cover a single sub reference
128+
assertThat(bytesRef.slice(13, 10), Matchers.not(Matchers.instanceOf(CompositeBytesReference.class))); // strictly within sub
129+
assertThat(bytesRef.slice(12, 15), Matchers.not(Matchers.instanceOf(CompositeBytesReference.class))); // equal to sub
130+
}
116131
}

test/framework/src/main/java/org/elasticsearch/common/bytes/AbstractBytesReferenceTestCase.java

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,21 @@ public void testLength() throws IOException {
6868
}
6969

7070
public void testSlice() throws IOException {
71-
int length = randomInt(PAGE_SIZE * 3);
72-
BytesReference pbr = newBytesReference(length);
73-
int sliceOffset = randomIntBetween(0, length / 2);
74-
int sliceLength = Math.max(0, length - sliceOffset - 1);
75-
BytesReference slice = pbr.slice(sliceOffset, sliceLength);
76-
assertEquals(sliceLength, slice.length());
77-
for (int i = 0; i < sliceLength; i++) {
78-
assertEquals(pbr.get(i+sliceOffset), slice.get(i));
79-
}
80-
BytesRef singlePageOrNull = getSinglePageOrNull(slice);
81-
if (singlePageOrNull != null) {
82-
// we can't assert the offset since if the length is smaller than the refercence
83-
// the offset can be anywhere
84-
assertEquals(sliceLength, singlePageOrNull.length);
71+
for (int length : new int[] {0, 1, randomIntBetween(2, PAGE_SIZE), randomIntBetween(PAGE_SIZE + 1, 3 * PAGE_SIZE)}) {
72+
BytesReference pbr = newBytesReference(length);
73+
int sliceOffset = randomIntBetween(0, length / 2);
74+
int sliceLength = Math.max(0, length - sliceOffset - 1);
75+
BytesReference slice = pbr.slice(sliceOffset, sliceLength);
76+
assertEquals(sliceLength, slice.length());
77+
for (int i = 0; i < sliceLength; i++) {
78+
assertEquals(pbr.get(i+sliceOffset), slice.get(i));
79+
}
80+
BytesRef singlePageOrNull = getSinglePageOrNull(slice);
81+
if (singlePageOrNull != null) {
82+
// we can't assert the offset since if the length is smaller than the refercence
83+
// the offset can be anywhere
84+
assertEquals(sliceLength, singlePageOrNull.length);
85+
}
8586
}
8687
}
8788

0 commit comments

Comments
 (0)