From 76371940296ebc4a5c2e3d4a5c366e1a1dfc66a0 Mon Sep 17 00:00:00 2001 From: Prashanth Badari <102688956+prashanthbdremio@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:12:27 -0400 Subject: [PATCH 01/11] Fix IOOBE in ListVector/LargeListVector.setReaderAndWriterIndex() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GH-343 (Jan 2026) changed setReaderAndWriterIndex() to unconditionally set offsetBuffer.writerIndex((valueCount + 1) * OFFSET_WIDTH) so that even empty lists export the trailing-zero offset required by the Arrow IPC spec. However, unlike the analogous fix in BaseVariableWidthVector, this change did not include a realloc guard — when the offset buffer has never been allocated (capacity == 0), the writerIndex is set to 4 on a zero-capacity buffer, producing the inconsistent state: readerIndex: 0, writerIndex: 4, capacity(0) Netty's AbstractByteBuf.writerIndex() bounds-checks against capacity and throws IndexOutOfBoundsException. This crashes Dremio's FragmentWritableBatch serialization path (SingleSenderOperator, PartitionSenderOperator, etc.) when an empty ListVector reaches the VectorUnloader -> NettyArrowBuf.unwrapBuffer() chain. The fix adds the same realloc guard that BaseVariableWidthVector already uses: grow the offset buffer to the required size before setting writerIndex. This mirrors what exportCDataBuffers() already does for the capacity == 0 case. Affects both ListVector (OFFSET_WIDTH = 4) and LargeListVector (OFFSET_WIDTH = 8). Co-Authored-By: Claude Opus 4.6 (1M context) Change-Id: Ib5ca53064ea5bc69c43fd6cc09692b07199c586c --- .../apache/arrow/vector/complex/LargeListVector.java | 11 ++++++++++- .../org/apache/arrow/vector/complex/ListVector.java | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java index 92dd3eaef7..d8ac81a721 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java @@ -316,7 +316,16 @@ private void setReaderAndWriterIndex() { // Both are set to 0 means 0 bytes are written to the IPC stream which will crash IPC readers // in other libraries. According to Arrow spec, we should still output the offset buffer which // is [0]. - offsetBuffer.writerIndex((long) (valueCount + 1) * OFFSET_WIDTH); + final long requiredOffsetBufferSize = (long) (valueCount + 1) * OFFSET_WIDTH; + if (offsetBuffer.capacity() < requiredOffsetBufferSize) { + ArrowBuf newOffsetBuffer = allocateOffsetBuffer(requiredOffsetBufferSize); + if (offsetBuffer.capacity() > 0) { + newOffsetBuffer.setBytes(0, offsetBuffer, 0, offsetBuffer.capacity()); + } + offsetBuffer.getReferenceManager().release(); + offsetBuffer = newOffsetBuffer; + } + offsetBuffer.writerIndex(requiredOffsetBufferSize); } /** diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java index 6c3993df63..dd7cde8d7d 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java @@ -274,7 +274,16 @@ private void setReaderAndWriterIndex() { // Both are set to 0 means 0 bytes are written to the IPC stream which will crash IPC readers // in other libraries. According to Arrow spec, we should still output the offset buffer which // is [0]. - offsetBuffer.writerIndex((long) (valueCount + 1) * OFFSET_WIDTH); + final long requiredOffsetBufferSize = (long) (valueCount + 1) * OFFSET_WIDTH; + if (offsetBuffer.capacity() < requiredOffsetBufferSize) { + ArrowBuf newOffsetBuffer = allocateOffsetBuffer(requiredOffsetBufferSize); + if (offsetBuffer.capacity() > 0) { + newOffsetBuffer.setBytes(0, offsetBuffer, 0, offsetBuffer.capacity()); + } + offsetBuffer.getReferenceManager().release(); + offsetBuffer = newOffsetBuffer; + } + offsetBuffer.writerIndex(requiredOffsetBufferSize); } /** From 81ac711c9d0ec72d201bb83a020c47d650514e80 Mon Sep 17 00:00:00 2001 From: Prashanth Badari <102688956+prashanthbdremio@users.noreply.github.com> Date: Thu, 18 Jun 2026 13:27:18 -0400 Subject: [PATCH 02/11] Add regression tests for never-allocated ListVector/LargeListVector Tests exercise the exact bug path: setValueCount(0) on a vector where allocateNew() was never called, leaving the offset buffer at capacity 0. - testEmptyListOffsetBufferWithoutAllocate: verifies getFieldBuffers() produces a valid offset buffer after the realloc guard fires - testEmptyListGetBuffersWithoutAllocate: exercises getBuffers(false), the IPC serialization entry point that produced the original Netty IOOBE via VectorUnloader -> NettyArrowBuf.unwrapBuffer() - Analogous tests for LargeListVector The existing testEmptyListOffsetBuffer / testEmptyLargeListOffsetBuffer tests call allocateNew() before setValueCount(0), so the offset buffer always has nonzero capacity and the realloc guard is never entered. Co-Authored-By: Claude Opus 4.6 (1M context) Change-Id: I1972ad3d80f405ed9b41103e88902571b63defb0 --- .../arrow/vector/TestLargeListVector.java | 35 +++++++++++++++++ .../apache/arrow/vector/TestListVector.java | 38 +++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java index bf9bba9c78..295aef1b63 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java @@ -1120,6 +1120,41 @@ public void testEmptyLargeListOffsetBuffer() { } } + @Test + public void testEmptyLargeListOffsetBufferWithoutAllocate() { + // Regression test for the Arrow 19 IOOBE: a never-allocated LargeListVector must still produce + // a valid offset buffer after setValueCount(0). Without the realloc guard in + // setReaderAndWriterIndex(), this sets writerIndex=8 on a capacity-0 buffer. + try (LargeListVector list = LargeListVector.empty("list", allocator)) { + list.addOrGetVector(FieldType.nullable(MinorType.INT.getType())); + list.setValueCount(0); // no allocateNew() — offset buffer starts at capacity 0 + + List buffers = list.getFieldBuffers(); + assertTrue( + buffers.get(1).readableBytes() >= LargeListVector.OFFSET_WIDTH, + "Offset buffer should have at least " + + LargeListVector.OFFSET_WIDTH + + " bytes for offset[0]"); + assertEquals(0L, list.getOffsetBuffer().getLong(0)); + } + } + + @Test + public void testEmptyLargeListGetBuffersWithoutAllocate() { + // Exercises the getBuffers(false) entry point — the IPC serialization path. + try (LargeListVector list = LargeListVector.empty("list", allocator)) { + list.addOrGetVector(FieldType.nullable(MinorType.INT.getType())); + list.setValueCount(0); + + ArrowBuf[] bufs = list.getBuffers(false); + assertTrue( + list.getOffsetBuffer().capacity() >= LargeListVector.OFFSET_WIDTH, + "Offset buffer capacity should be >= " + + LargeListVector.OFFSET_WIDTH + + " after setReaderAndWriterIndex"); + } + } + private void writeIntValues(UnionLargeListWriter writer, int[] values) { writer.startList(); for (int v : values) { diff --git a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java index 0c90b32abc..cf227102d5 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java @@ -1399,6 +1399,44 @@ public void testEmptyListOffsetBuffer() { } } + @Test + public void testEmptyListOffsetBufferWithoutAllocate() { + // Regression test for the Arrow 19 IOOBE: a never-allocated ListVector must still produce + // a valid offset buffer after setValueCount(0). Without the realloc guard in + // setReaderAndWriterIndex(), this sets writerIndex=4 on a capacity-0 buffer. + try (ListVector list = ListVector.empty("list", allocator)) { + list.addOrGetVector(FieldType.nullable(MinorType.INT.getType())); + list.setValueCount(0); // no allocateNew() — offset buffer starts at capacity 0 + + List buffers = list.getFieldBuffers(); + assertTrue( + buffers.get(1).readableBytes() >= BaseRepeatedValueVector.OFFSET_WIDTH, + "Offset buffer should have at least " + + BaseRepeatedValueVector.OFFSET_WIDTH + + " bytes for offset[0]"); + assertEquals(0, list.getOffsetBuffer().getInt(0)); + } + } + + @Test + public void testEmptyListGetBuffersWithoutAllocate() { + // Exercises the getBuffers(false) entry point — the IPC serialization path that produced the + // original Netty IOOBE via VectorUnloader -> NettyArrowBuf.unwrapBuffer(). + try (ListVector list = ListVector.empty("list", allocator)) { + list.addOrGetVector(FieldType.nullable(MinorType.INT.getType())); + list.setValueCount(0); + + ArrowBuf[] bufs = list.getBuffers(false); + // getBufferSize() returns 0 for valueCount==0, so getBuffers returns empty array. + // But the offset buffer on the vector itself must have been grown to valid capacity. + assertTrue( + list.getOffsetBuffer().capacity() >= BaseRepeatedValueVector.OFFSET_WIDTH, + "Offset buffer capacity should be >= " + + BaseRepeatedValueVector.OFFSET_WIDTH + + " after setReaderAndWriterIndex"); + } + } + private void writeIntValues(UnionListWriter writer, int[] values) { writer.startList(); for (int v : values) { From 3c64cb2b36da9d7912cea21cfaec13c132ba5b23 Mon Sep 17 00:00:00 2001 From: Prashanth Badari <102688956+prashanthbdremio@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:16:23 -0400 Subject: [PATCH 03/11] Narrow realloc guard to capacity==0 only The previous guard (capacity < required) could replace an already-allocated offset buffer with a smaller one when setReaderAndWriterIndex() was called before the vector was populated (e.g., during validateFull() on an empty vector). The replacement buffer was too small for subsequent writes. Narrowing to capacity==0 targets only the never-allocated empty singleton from allocator.getEmpty(), which is the exact state that causes the IOOBE. Buffers with capacity>0 were properly allocated and should not be replaced. Also removes the dead copy branch (capacity>0 is always false when we enter the guard). Co-Authored-By: Claude Opus 4.6 (1M context) Change-Id: I28ee94d2a2b431f7699ce8c3ce133f184ede5458 --- .../org/apache/arrow/vector/complex/LargeListVector.java | 5 +---- .../java/org/apache/arrow/vector/complex/ListVector.java | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java index d8ac81a721..15bd6b3ce0 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java @@ -317,11 +317,8 @@ private void setReaderAndWriterIndex() { // in other libraries. According to Arrow spec, we should still output the offset buffer which // is [0]. final long requiredOffsetBufferSize = (long) (valueCount + 1) * OFFSET_WIDTH; - if (offsetBuffer.capacity() < requiredOffsetBufferSize) { + if (offsetBuffer.capacity() == 0) { ArrowBuf newOffsetBuffer = allocateOffsetBuffer(requiredOffsetBufferSize); - if (offsetBuffer.capacity() > 0) { - newOffsetBuffer.setBytes(0, offsetBuffer, 0, offsetBuffer.capacity()); - } offsetBuffer.getReferenceManager().release(); offsetBuffer = newOffsetBuffer; } diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java index dd7cde8d7d..e0b3705241 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java @@ -275,11 +275,8 @@ private void setReaderAndWriterIndex() { // in other libraries. According to Arrow spec, we should still output the offset buffer which // is [0]. final long requiredOffsetBufferSize = (long) (valueCount + 1) * OFFSET_WIDTH; - if (offsetBuffer.capacity() < requiredOffsetBufferSize) { + if (offsetBuffer.capacity() == 0) { ArrowBuf newOffsetBuffer = allocateOffsetBuffer(requiredOffsetBufferSize); - if (offsetBuffer.capacity() > 0) { - newOffsetBuffer.setBytes(0, offsetBuffer, 0, offsetBuffer.capacity()); - } offsetBuffer.getReferenceManager().release(); offsetBuffer = newOffsetBuffer; } From 1a009d1bbd0a6569b7201eb4fccbfb158c539b95 Mon Sep 17 00:00:00 2001 From: Prashanth Badari <102688956+prashanthbdremio@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:25:02 -0400 Subject: [PATCH 04/11] Move fix from setReaderAndWriterIndex() to getFieldBuffers() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous approach mutated this.offsetBuffer inside setReaderAndWriterIndex(), which is called from getFieldBuffers(), getBuffers(), and indirectly from validation. Replacing the offset buffer during validation broke subsequent writes — tests that called validateFull() on an empty vector before populating data got a 4-byte buffer that was too small for the actual data. The fix now mirrors exportCDataBuffers() exactly: getFieldBuffers() detects the inconsistent state (capacity==0 but writerIndex>0, produced by setReaderAndWriterIndex on a never-allocated buffer) and substitutes a properly-sized temporary buffer for serialization. The vector's own offsetBuffer is never mutated, so subsequent allocateNew/setValueCount calls work normally. setReaderAndWriterIndex() is reverted to upstream Arrow 19 behavior. Co-Authored-By: Claude Opus 4.6 (1M context) Change-Id: I696163fac4fcf5ada1af0edb5da2f4e8ba4e3e84 --- .../arrow/vector/complex/LargeListVector.java | 17 ++++++++--------- .../apache/arrow/vector/complex/ListVector.java | 17 ++++++++--------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java index 15bd6b3ce0..ae8f8916de 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java @@ -277,8 +277,13 @@ public List getFieldBuffers() { List result = new ArrayList<>(2); setReaderAndWriterIndex(); result.add(validityBuffer); - result.add(offsetBuffer); - + if (offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { + ArrowBuf tempOffset = allocateOffsetBuffer(offsetBuffer.writerIndex()); + tempOffset.writerIndex(offsetBuffer.writerIndex()); + result.add(tempOffset); + } else { + result.add(offsetBuffer); + } return result; } @@ -316,13 +321,7 @@ private void setReaderAndWriterIndex() { // Both are set to 0 means 0 bytes are written to the IPC stream which will crash IPC readers // in other libraries. According to Arrow spec, we should still output the offset buffer which // is [0]. - final long requiredOffsetBufferSize = (long) (valueCount + 1) * OFFSET_WIDTH; - if (offsetBuffer.capacity() == 0) { - ArrowBuf newOffsetBuffer = allocateOffsetBuffer(requiredOffsetBufferSize); - offsetBuffer.getReferenceManager().release(); - offsetBuffer = newOffsetBuffer; - } - offsetBuffer.writerIndex(requiredOffsetBufferSize); + offsetBuffer.writerIndex((long) (valueCount + 1) * OFFSET_WIDTH); } /** diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java index e0b3705241..9e10d80671 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java @@ -235,8 +235,13 @@ public List getFieldBuffers() { List result = new ArrayList<>(2); setReaderAndWriterIndex(); result.add(validityBuffer); - result.add(offsetBuffer); - + if (offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { + ArrowBuf tempOffset = allocateOffsetBuffer(offsetBuffer.writerIndex()); + tempOffset.writerIndex(offsetBuffer.writerIndex()); + result.add(tempOffset); + } else { + result.add(offsetBuffer); + } return result; } @@ -274,13 +279,7 @@ private void setReaderAndWriterIndex() { // Both are set to 0 means 0 bytes are written to the IPC stream which will crash IPC readers // in other libraries. According to Arrow spec, we should still output the offset buffer which // is [0]. - final long requiredOffsetBufferSize = (long) (valueCount + 1) * OFFSET_WIDTH; - if (offsetBuffer.capacity() == 0) { - ArrowBuf newOffsetBuffer = allocateOffsetBuffer(requiredOffsetBufferSize); - offsetBuffer.getReferenceManager().release(); - offsetBuffer = newOffsetBuffer; - } - offsetBuffer.writerIndex(requiredOffsetBufferSize); + offsetBuffer.writerIndex((long) (valueCount + 1) * OFFSET_WIDTH); } /** From 360a456fafcef9a8f93e8403098d3ab1e9f1c309 Mon Sep 17 00:00:00 2001 From: Prashanth Badari <102688956+prashanthbdremio@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:51:06 -0400 Subject: [PATCH 05/11] Fix regression tests to match getFieldBuffers() approach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tests now check the buffer RETURNED by getFieldBuffers() (which is a temp allocation when the vector's offset buffer has capacity 0) instead of checking the vector's own getOffsetBuffer() which is intentionally not mutated. Remove testEmptyListGetBuffersWithoutAllocate / LargeList variant — getBuffers(false) returns an empty array for valueCount==0 (via the getBufferSize()==0 guard), so the offset buffer is never exposed through that path and doesn't need testing. Release the temp buffer after assertions to avoid memory leak detection. Co-Authored-By: Claude Opus 4.6 (1M context) Change-Id: I73877ba98047b6bda6fcf7737e53778deaa7cb74 --- .../arrow/vector/TestLargeListVector.java | 36 ++++++---------- .../apache/arrow/vector/TestListVector.java | 42 +++++++------------ 2 files changed, 27 insertions(+), 51 deletions(-) diff --git a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java index 295aef1b63..8c64ea3b5e 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java @@ -1122,36 +1122,24 @@ public void testEmptyLargeListOffsetBuffer() { @Test public void testEmptyLargeListOffsetBufferWithoutAllocate() { - // Regression test for the Arrow 19 IOOBE: a never-allocated LargeListVector must still produce - // a valid offset buffer after setValueCount(0). Without the realloc guard in - // setReaderAndWriterIndex(), this sets writerIndex=8 on a capacity-0 buffer. + // Regression test for the Arrow 19 IOOBE: a never-allocated LargeListVector must produce a + // valid offset buffer from getFieldBuffers() even when allocateNew() was never called. try (LargeListVector list = LargeListVector.empty("list", allocator)) { list.addOrGetVector(FieldType.nullable(MinorType.INT.getType())); - list.setValueCount(0); // no allocateNew() — offset buffer starts at capacity 0 + list.setValueCount(0); List buffers = list.getFieldBuffers(); + ArrowBuf offsetBuf = buffers.get(1); assertTrue( - buffers.get(1).readableBytes() >= LargeListVector.OFFSET_WIDTH, - "Offset buffer should have at least " - + LargeListVector.OFFSET_WIDTH - + " bytes for offset[0]"); - assertEquals(0L, list.getOffsetBuffer().getLong(0)); - } - } - - @Test - public void testEmptyLargeListGetBuffersWithoutAllocate() { - // Exercises the getBuffers(false) entry point — the IPC serialization path. - try (LargeListVector list = LargeListVector.empty("list", allocator)) { - list.addOrGetVector(FieldType.nullable(MinorType.INT.getType())); - list.setValueCount(0); - - ArrowBuf[] bufs = list.getBuffers(false); + offsetBuf.capacity() >= LargeListVector.OFFSET_WIDTH, + "Returned offset buffer should have capacity >= " + + LargeListVector.OFFSET_WIDTH); assertTrue( - list.getOffsetBuffer().capacity() >= LargeListVector.OFFSET_WIDTH, - "Offset buffer capacity should be >= " - + LargeListVector.OFFSET_WIDTH - + " after setReaderAndWriterIndex"); + offsetBuf.readableBytes() >= LargeListVector.OFFSET_WIDTH, + "Returned offset buffer should have readableBytes >= " + + LargeListVector.OFFSET_WIDTH); + assertEquals(0L, offsetBuf.getLong(0)); + offsetBuf.close(); } } diff --git a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java index cf227102d5..c9ebdb9dc7 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java @@ -1401,39 +1401,27 @@ public void testEmptyListOffsetBuffer() { @Test public void testEmptyListOffsetBufferWithoutAllocate() { - // Regression test for the Arrow 19 IOOBE: a never-allocated ListVector must still produce - // a valid offset buffer after setValueCount(0). Without the realloc guard in - // setReaderAndWriterIndex(), this sets writerIndex=4 on a capacity-0 buffer. + // Regression test for the Arrow 19 IOOBE: a never-allocated ListVector must produce a valid + // offset buffer from getFieldBuffers() even when allocateNew() was never called. + // getFieldBuffers() substitutes a properly-sized temp buffer when the vector's own offset + // buffer has capacity 0 but writerIndex > 0 (the inconsistent state from Arrow 19). try (ListVector list = ListVector.empty("list", allocator)) { list.addOrGetVector(FieldType.nullable(MinorType.INT.getType())); - list.setValueCount(0); // no allocateNew() — offset buffer starts at capacity 0 + list.setValueCount(0); List buffers = list.getFieldBuffers(); + ArrowBuf offsetBuf = buffers.get(1); assertTrue( - buffers.get(1).readableBytes() >= BaseRepeatedValueVector.OFFSET_WIDTH, - "Offset buffer should have at least " - + BaseRepeatedValueVector.OFFSET_WIDTH - + " bytes for offset[0]"); - assertEquals(0, list.getOffsetBuffer().getInt(0)); - } - } - - @Test - public void testEmptyListGetBuffersWithoutAllocate() { - // Exercises the getBuffers(false) entry point — the IPC serialization path that produced the - // original Netty IOOBE via VectorUnloader -> NettyArrowBuf.unwrapBuffer(). - try (ListVector list = ListVector.empty("list", allocator)) { - list.addOrGetVector(FieldType.nullable(MinorType.INT.getType())); - list.setValueCount(0); - - ArrowBuf[] bufs = list.getBuffers(false); - // getBufferSize() returns 0 for valueCount==0, so getBuffers returns empty array. - // But the offset buffer on the vector itself must have been grown to valid capacity. + offsetBuf.capacity() >= BaseRepeatedValueVector.OFFSET_WIDTH, + "Returned offset buffer should have capacity >= " + + BaseRepeatedValueVector.OFFSET_WIDTH); assertTrue( - list.getOffsetBuffer().capacity() >= BaseRepeatedValueVector.OFFSET_WIDTH, - "Offset buffer capacity should be >= " - + BaseRepeatedValueVector.OFFSET_WIDTH - + " after setReaderAndWriterIndex"); + offsetBuf.readableBytes() >= BaseRepeatedValueVector.OFFSET_WIDTH, + "Returned offset buffer should have readableBytes >= " + + BaseRepeatedValueVector.OFFSET_WIDTH); + assertEquals(0, offsetBuf.getInt(0)); + // Release the temp buffer allocated by getFieldBuffers() + offsetBuf.close(); } } From 9a861c9825788e007be5d4f5e8beee33e67a173a Mon Sep 17 00:00:00 2001 From: Prashanth Badari <102688956+prashanthbdremio@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:54:40 -0400 Subject: [PATCH 06/11] Fix spotless formatting in test files Co-Authored-By: Claude Opus 4.6 (1M context) Change-Id: I47527f1d5934a122c7c7381c037c4f951b672592 --- .../java/org/apache/arrow/vector/TestLargeListVector.java | 6 ++---- .../test/java/org/apache/arrow/vector/TestListVector.java | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java index 8c64ea3b5e..4dc7559d3d 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java @@ -1132,12 +1132,10 @@ public void testEmptyLargeListOffsetBufferWithoutAllocate() { ArrowBuf offsetBuf = buffers.get(1); assertTrue( offsetBuf.capacity() >= LargeListVector.OFFSET_WIDTH, - "Returned offset buffer should have capacity >= " - + LargeListVector.OFFSET_WIDTH); + "Returned offset buffer should have capacity >= " + LargeListVector.OFFSET_WIDTH); assertTrue( offsetBuf.readableBytes() >= LargeListVector.OFFSET_WIDTH, - "Returned offset buffer should have readableBytes >= " - + LargeListVector.OFFSET_WIDTH); + "Returned offset buffer should have readableBytes >= " + LargeListVector.OFFSET_WIDTH); assertEquals(0L, offsetBuf.getLong(0)); offsetBuf.close(); } diff --git a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java index c9ebdb9dc7..8e393f9928 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java @@ -1413,8 +1413,7 @@ public void testEmptyListOffsetBufferWithoutAllocate() { ArrowBuf offsetBuf = buffers.get(1); assertTrue( offsetBuf.capacity() >= BaseRepeatedValueVector.OFFSET_WIDTH, - "Returned offset buffer should have capacity >= " - + BaseRepeatedValueVector.OFFSET_WIDTH); + "Returned offset buffer should have capacity >= " + BaseRepeatedValueVector.OFFSET_WIDTH); assertTrue( offsetBuf.readableBytes() >= BaseRepeatedValueVector.OFFSET_WIDTH, "Returned offset buffer should have readableBytes >= " From 18b45a6c1754f9f44d3b06803498f7f9bf92b8be Mon Sep 17 00:00:00 2001 From: Prashanth Badari <102688956+prashanthbdremio@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:29:16 -0400 Subject: [PATCH 07/11] Fix temp buffer leak: assign to this.offsetBuffer in getFieldBuffers() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous approach returned a temp buffer that was not owned by the vector. VectorUnloader.retain() added a ref but the original ref was never released, leaking OFFSET_WIDTH bytes per empty list serialized. Now getFieldBuffers() assigns the new buffer to this.offsetBuffer so the vector owns it. Uses allocateOffsetBuffer(offsetAllocationSizeInBytes) to get a full-sized buffer (no side effect since the argument matches the field value), which is large enough for subsequent writes if the vector is populated after validation — fixing the TestValidateVector regression. Co-Authored-By: Claude Opus 4.6 (1M context) Change-Id: I4d5caed0bf2d323215556d6a1a90330b4ab399e8 --- .../apache/arrow/vector/complex/LargeListVector.java | 10 +++++----- .../org/apache/arrow/vector/complex/ListVector.java | 10 +++++----- .../org/apache/arrow/vector/TestLargeListVector.java | 2 +- .../java/org/apache/arrow/vector/TestListVector.java | 7 +++---- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java index ae8f8916de..8f72d8a879 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java @@ -278,12 +278,12 @@ public List getFieldBuffers() { setReaderAndWriterIndex(); result.add(validityBuffer); if (offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { - ArrowBuf tempOffset = allocateOffsetBuffer(offsetBuffer.writerIndex()); - tempOffset.writerIndex(offsetBuffer.writerIndex()); - result.add(tempOffset); - } else { - result.add(offsetBuffer); + long writerIdx = offsetBuffer.writerIndex(); + offsetBuffer.getReferenceManager().release(); + offsetBuffer = allocateOffsetBuffer(offsetAllocationSizeInBytes); + offsetBuffer.writerIndex(writerIdx); } + result.add(offsetBuffer); return result; } diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java index 9e10d80671..e2ec852093 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java @@ -236,12 +236,12 @@ public List getFieldBuffers() { setReaderAndWriterIndex(); result.add(validityBuffer); if (offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { - ArrowBuf tempOffset = allocateOffsetBuffer(offsetBuffer.writerIndex()); - tempOffset.writerIndex(offsetBuffer.writerIndex()); - result.add(tempOffset); - } else { - result.add(offsetBuffer); + long writerIdx = offsetBuffer.writerIndex(); + offsetBuffer.getReferenceManager().release(); + offsetBuffer = allocateOffsetBuffer(offsetAllocationSizeInBytes); + offsetBuffer.writerIndex(writerIdx); } + result.add(offsetBuffer); return result; } diff --git a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java index 4dc7559d3d..8dfbd8f8f6 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java @@ -1137,7 +1137,7 @@ public void testEmptyLargeListOffsetBufferWithoutAllocate() { offsetBuf.readableBytes() >= LargeListVector.OFFSET_WIDTH, "Returned offset buffer should have readableBytes >= " + LargeListVector.OFFSET_WIDTH); assertEquals(0L, offsetBuf.getLong(0)); - offsetBuf.close(); + // Vector owns the buffer — no manual close needed } } diff --git a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java index 8e393f9928..a425086fd2 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java @@ -1403,8 +1403,8 @@ public void testEmptyListOffsetBuffer() { public void testEmptyListOffsetBufferWithoutAllocate() { // Regression test for the Arrow 19 IOOBE: a never-allocated ListVector must produce a valid // offset buffer from getFieldBuffers() even when allocateNew() was never called. - // getFieldBuffers() substitutes a properly-sized temp buffer when the vector's own offset - // buffer has capacity 0 but writerIndex > 0 (the inconsistent state from Arrow 19). + // getFieldBuffers() allocates a real offset buffer when the vector's own offset buffer has + // capacity 0 but writerIndex > 0 (the inconsistent state from Arrow 19). try (ListVector list = ListVector.empty("list", allocator)) { list.addOrGetVector(FieldType.nullable(MinorType.INT.getType())); list.setValueCount(0); @@ -1419,8 +1419,7 @@ public void testEmptyListOffsetBufferWithoutAllocate() { "Returned offset buffer should have readableBytes >= " + BaseRepeatedValueVector.OFFSET_WIDTH); assertEquals(0, offsetBuf.getInt(0)); - // Release the temp buffer allocated by getFieldBuffers() - offsetBuf.close(); + // Vector owns the buffer — no manual close needed } } From 0d89f0a7f39f088b143ca73d768051885cfc6783 Mon Sep 17 00:00:00 2001 From: Prashanth Badari <102688956+prashanthbdremio@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:11:43 -0400 Subject: [PATCH 08/11] Use (valueCount + 1) * OFFSET_WIDTH instead of offsetAllocationSizeInBytes Addresses review feedback from bodduv: LargeListVector.clear() sets offsetAllocationSizeInBytes = offsetBuffer.capacity(), which is 0 after the buffer is released. Using offsetAllocationSizeInBytes would allocate a 0-byte buffer in that case. (valueCount + 1) * OFFSET_WIDTH is always the correct minimum size (at least OFFSET_WIDTH when valueCount == 0) and matches the writerIndex that setReaderAndWriterIndex() just set. Co-Authored-By: Claude Opus 4.6 (1M context) Change-Id: Iae6b0a2578deb0f7fd8ca7ae36a0a7e3fc595765 --- .../java/org/apache/arrow/vector/complex/LargeListVector.java | 2 +- .../main/java/org/apache/arrow/vector/complex/ListVector.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java index 8f72d8a879..b8c758e403 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java @@ -280,7 +280,7 @@ public List getFieldBuffers() { if (offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { long writerIdx = offsetBuffer.writerIndex(); offsetBuffer.getReferenceManager().release(); - offsetBuffer = allocateOffsetBuffer(offsetAllocationSizeInBytes); + offsetBuffer = allocateOffsetBuffer((long) (valueCount + 1) * OFFSET_WIDTH); offsetBuffer.writerIndex(writerIdx); } result.add(offsetBuffer); diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java index e2ec852093..292a498f68 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java @@ -238,7 +238,7 @@ public List getFieldBuffers() { if (offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { long writerIdx = offsetBuffer.writerIndex(); offsetBuffer.getReferenceManager().release(); - offsetBuffer = allocateOffsetBuffer(offsetAllocationSizeInBytes); + offsetBuffer = allocateOffsetBuffer((long) (valueCount + 1) * OFFSET_WIDTH); offsetBuffer.writerIndex(writerIdx); } result.add(offsetBuffer); From e0979e8dcbea36dffce28cfd0f30e0c64746e490 Mon Sep 17 00:00:00 2001 From: Prashanth Badari <102688956+prashanthbdremio@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:35:42 -0400 Subject: [PATCH 09/11] Allocate default-sized offset buffer to avoid validate-then-write regression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (valueCount + 1) * OFFSET_WIDTH produces a 4-byte buffer for valueCount=0, which is too small when validateFull() triggers getFieldBuffers() before the vector is populated — subsequent setVector() writes at index 4+ fail. Use max(required, INITIAL_VALUE_ALLOCATION * OFFSET_WIDTH) to match the default allocateNew() size. Use allocator.buffer() directly instead of allocateOffsetBuffer() to avoid mutating offsetAllocationSizeInBytes. Co-Authored-By: Claude Opus 4.6 (1M context) Change-Id: Iccb00d4775b196f7978b34cbc98234f50ede370a --- .../org/apache/arrow/vector/complex/LargeListVector.java | 6 +++++- .../java/org/apache/arrow/vector/complex/ListVector.java | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java index b8c758e403..d26fbee956 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java @@ -280,7 +280,11 @@ public List getFieldBuffers() { if (offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { long writerIdx = offsetBuffer.writerIndex(); offsetBuffer.getReferenceManager().release(); - offsetBuffer = allocateOffsetBuffer((long) (valueCount + 1) * OFFSET_WIDTH); + long allocSize = + Math.max((long) (valueCount + 1) * OFFSET_WIDTH, INITIAL_VALUE_ALLOCATION * OFFSET_WIDTH); + offsetBuffer = allocator.buffer(allocSize); + offsetBuffer.setZero(0, offsetBuffer.capacity()); + offsetBuffer.readerIndex(0); offsetBuffer.writerIndex(writerIdx); } result.add(offsetBuffer); diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java index 292a498f68..3ea2ac581b 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java @@ -238,7 +238,11 @@ public List getFieldBuffers() { if (offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { long writerIdx = offsetBuffer.writerIndex(); offsetBuffer.getReferenceManager().release(); - offsetBuffer = allocateOffsetBuffer((long) (valueCount + 1) * OFFSET_WIDTH); + long allocSize = + Math.max((long) (valueCount + 1) * OFFSET_WIDTH, INITIAL_VALUE_ALLOCATION * OFFSET_WIDTH); + offsetBuffer = allocator.buffer(allocSize); + offsetBuffer.setZero(0, offsetBuffer.capacity()); + offsetBuffer.readerIndex(0); offsetBuffer.writerIndex(writerIdx); } result.add(offsetBuffer); From 2cd5472e92581638b8c79fea5e07d20b9f413091 Mon Sep 17 00:00:00 2001 From: Prashanth Badari <102688956+prashanthbdremio@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:39:06 -0400 Subject: [PATCH 10/11] MINOR: Scope empty list offset buffer fix to empty vectors --- .../java/org/apache/arrow/vector/complex/LargeListVector.java | 2 +- .../main/java/org/apache/arrow/vector/complex/ListVector.java | 2 +- .../src/test/java/org/apache/arrow/vector/TestListVector.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java index d26fbee956..f5f2737d93 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java @@ -277,7 +277,7 @@ public List getFieldBuffers() { List result = new ArrayList<>(2); setReaderAndWriterIndex(); result.add(validityBuffer); - if (offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { + if (valueCount == 0 && offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { long writerIdx = offsetBuffer.writerIndex(); offsetBuffer.getReferenceManager().release(); long allocSize = diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java index 3ea2ac581b..c859916c0c 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java @@ -235,7 +235,7 @@ public List getFieldBuffers() { List result = new ArrayList<>(2); setReaderAndWriterIndex(); result.add(validityBuffer); - if (offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { + if (valueCount == 0 && offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { long writerIdx = offsetBuffer.writerIndex(); offsetBuffer.getReferenceManager().release(); long allocSize = diff --git a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java index a425086fd2..2118636959 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java @@ -1403,8 +1403,8 @@ public void testEmptyListOffsetBuffer() { public void testEmptyListOffsetBufferWithoutAllocate() { // Regression test for the Arrow 19 IOOBE: a never-allocated ListVector must produce a valid // offset buffer from getFieldBuffers() even when allocateNew() was never called. - // getFieldBuffers() allocates a real offset buffer when the vector's own offset buffer has - // capacity 0 but writerIndex > 0 (the inconsistent state from Arrow 19). + // getFieldBuffers() allocates a real offset buffer for an empty vector whose own offset buffer + // has capacity 0 but writerIndex > 0 (the inconsistent state from Arrow 19). try (ListVector list = ListVector.empty("list", allocator)) { list.addOrGetVector(FieldType.nullable(MinorType.INT.getType())); list.setValueCount(0); From 00b69a226d83218e72c7a5d41ea24ffb843b72bb Mon Sep 17 00:00:00 2001 From: Prashanth Badari <102688956+prashanthbdremio@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:59:52 -0400 Subject: [PATCH 11/11] MINOR: Reset shared empty validity buffer for empty lists --- .../org/apache/arrow/vector/complex/LargeListVector.java | 8 +++++++- .../java/org/apache/arrow/vector/complex/ListVector.java | 8 +++++++- .../java/org/apache/arrow/vector/TestLargeListVector.java | 5 +++++ .../test/java/org/apache/arrow/vector/TestListVector.java | 5 +++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java index f5f2737d93..23d0380df7 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/LargeListVector.java @@ -279,7 +279,13 @@ public List getFieldBuffers() { result.add(validityBuffer); if (valueCount == 0 && offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { long writerIdx = offsetBuffer.writerIndex(); - offsetBuffer.getReferenceManager().release(); + ArrowBuf oldOffsetBuffer = offsetBuffer; + if (validityBuffer == oldOffsetBuffer) { + validityBuffer.readerIndex(0); + validityBuffer.writerIndex(0); + } else { + oldOffsetBuffer.getReferenceManager().release(); + } long allocSize = Math.max((long) (valueCount + 1) * OFFSET_WIDTH, INITIAL_VALUE_ALLOCATION * OFFSET_WIDTH); offsetBuffer = allocator.buffer(allocSize); diff --git a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java index c859916c0c..5f3518ce1d 100644 --- a/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java +++ b/vector/src/main/java/org/apache/arrow/vector/complex/ListVector.java @@ -237,7 +237,13 @@ public List getFieldBuffers() { result.add(validityBuffer); if (valueCount == 0 && offsetBuffer.capacity() == 0 && offsetBuffer.writerIndex() > 0) { long writerIdx = offsetBuffer.writerIndex(); - offsetBuffer.getReferenceManager().release(); + ArrowBuf oldOffsetBuffer = offsetBuffer; + if (validityBuffer == oldOffsetBuffer) { + validityBuffer.readerIndex(0); + validityBuffer.writerIndex(0); + } else { + oldOffsetBuffer.getReferenceManager().release(); + } long allocSize = Math.max((long) (valueCount + 1) * OFFSET_WIDTH, INITIAL_VALUE_ALLOCATION * OFFSET_WIDTH); offsetBuffer = allocator.buffer(allocSize); diff --git a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java index 8dfbd8f8f6..ade8478e06 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestLargeListVector.java @@ -1129,6 +1129,11 @@ public void testEmptyLargeListOffsetBufferWithoutAllocate() { list.setValueCount(0); List buffers = list.getFieldBuffers(); + ArrowBuf validityBuf = buffers.get(0); + assertEquals(0, validityBuf.readerIndex()); + assertEquals(0, validityBuf.writerIndex()); + assertEquals(0, validityBuf.readableBytes()); + ArrowBuf offsetBuf = buffers.get(1); assertTrue( offsetBuf.capacity() >= LargeListVector.OFFSET_WIDTH, diff --git a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java index 2118636959..6dcd8437f0 100644 --- a/vector/src/test/java/org/apache/arrow/vector/TestListVector.java +++ b/vector/src/test/java/org/apache/arrow/vector/TestListVector.java @@ -1410,6 +1410,11 @@ public void testEmptyListOffsetBufferWithoutAllocate() { list.setValueCount(0); List buffers = list.getFieldBuffers(); + ArrowBuf validityBuf = buffers.get(0); + assertEquals(0, validityBuf.readerIndex()); + assertEquals(0, validityBuf.writerIndex()); + assertEquals(0, validityBuf.readableBytes()); + ArrowBuf offsetBuf = buffers.get(1); assertTrue( offsetBuf.capacity() >= BaseRepeatedValueVector.OFFSET_WIDTH,