Repository navigation
MINOR: Bounds check delta dictionary data copy in VectorAppender #1364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -119,6 +119,8 @@ public ValueVector visit(BaseVariableWidthVector deltaVector, Void value) { | |
| return targetVector; // nothing to append, return | ||
| } | ||
|
|
||
| validateDelta(deltaVector); | ||
|
|
||
| int newValueCount = targetVector.getValueCount() + deltaVector.getValueCount(); | ||
|
|
||
| int targetDataSize = | ||
|
|
@@ -134,7 +136,7 @@ public ValueVector visit(BaseVariableWidthVector deltaVector, Void value) { | |
| .getOffsetBuffer() | ||
| .getInt((long) deltaVector.getValueCount() * BaseVariableWidthVector.OFFSET_WIDTH) | ||
| - deltaDataStart; | ||
| int newValueCapacity = targetDataSize + deltaDataSize; | ||
| int newValueCapacity = Math.addExact(targetDataSize, deltaDataSize); | ||
|
|
||
| // make sure there is enough capacity | ||
| while (targetVector.getValueCapacity() < newValueCount) { | ||
|
|
@@ -153,10 +155,9 @@ public ValueVector visit(BaseVariableWidthVector deltaVector, Void value) { | |
| targetVector.getValidityBuffer()); | ||
|
|
||
| // append data buffer | ||
| MemoryUtil.copyMemory( | ||
| deltaVector.getDataBuffer().memoryAddress() + deltaDataStart, | ||
| targetVector.getDataBuffer().memoryAddress() + targetDataSize, | ||
| deltaDataSize); | ||
| targetVector | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, only the data copy is checked now. The validity copy via Is it worth validating those buffer sizes up front too?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, worth it. |
||
| .getDataBuffer() | ||
| .setBytes(targetDataSize, deltaVector.getDataBuffer(), deltaDataStart, deltaDataSize); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It would be good to guard
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. It walks the offsets once and requires |
||
|
|
||
| // copy offset buffer | ||
| MemoryUtil.copyMemory( | ||
|
|
@@ -194,6 +195,8 @@ public ValueVector visit(BaseLargeVariableWidthVector deltaVector, Void value) { | |
| return targetVector; // nothing to append, return | ||
| } | ||
|
|
||
| validateDelta(deltaVector); | ||
|
|
||
| int newValueCount = targetVector.getValueCount() + deltaVector.getValueCount(); | ||
|
|
||
| long targetDataSize = | ||
|
|
@@ -210,7 +213,7 @@ public ValueVector visit(BaseLargeVariableWidthVector deltaVector, Void value) { | |
| .getLong( | ||
| (long) deltaVector.getValueCount() * BaseLargeVariableWidthVector.OFFSET_WIDTH) | ||
| - deltaDataStart; | ||
| long newValueCapacity = targetDataSize + deltaDataSize; | ||
| long newValueCapacity = Math.addExact(targetDataSize, deltaDataSize); | ||
|
|
||
| // make sure there is enough capacity | ||
| while (targetVector.getValueCapacity() < newValueCount) { | ||
|
|
@@ -229,10 +232,9 @@ public ValueVector visit(BaseLargeVariableWidthVector deltaVector, Void value) { | |
| targetVector.getValidityBuffer()); | ||
|
|
||
| // append data buffer | ||
| MemoryUtil.copyMemory( | ||
| deltaVector.getDataBuffer().memoryAddress() + deltaDataStart, | ||
| targetVector.getDataBuffer().memoryAddress() + targetDataSize, | ||
| deltaDataSize); | ||
| targetVector | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same comments applies here 😄
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same changes on the large path, with long offsets and |
||
| .getDataBuffer() | ||
| .setBytes(targetDataSize, deltaVector.getDataBuffer(), deltaDataStart, deltaDataSize); | ||
|
|
||
| // copy offset buffer | ||
| MemoryUtil.copyMemory( | ||
|
|
@@ -261,6 +263,70 @@ public ValueVector visit(BaseLargeVariableWidthVector deltaVector, Void value) { | |
| return targetVector; | ||
| } | ||
|
|
||
| /** | ||
| * Checks that the delta's validity and offset buffers cover its value count, and that its offsets | ||
| * are non-negative, non-decreasing and within its data buffer. This runs before the target is | ||
| * touched, so a malformed delta leaves the target unchanged. | ||
| */ | ||
| private static void validateDelta(BaseVariableWidthVector deltaVector) { | ||
| int valueCount = deltaVector.getValueCount(); | ||
| checkCapacity( | ||
| deltaVector.getValidityBuffer(), | ||
| BitVectorHelper.getValidityBufferSizeFromCount(valueCount), | ||
| "validity"); | ||
| ArrowBuf offsets = deltaVector.getOffsetBuffer(); | ||
| checkCapacity(offsets, (valueCount + 1L) * BaseVariableWidthVector.OFFSET_WIDTH, "offset"); | ||
| int prev = offsets.getInt(0); | ||
| if (prev < 0) { | ||
| throw new IndexOutOfBoundsException("Delta vector has negative start offset " + prev); | ||
| } | ||
| for (int i = 1; i <= valueCount; i++) { | ||
| int cur = offsets.getInt((long) i * BaseVariableWidthVector.OFFSET_WIDTH); | ||
| if (cur < prev) { | ||
| throw new IndexOutOfBoundsException( | ||
| "Delta vector offsets decrease at index " + i + ": " + prev + " > " + cur); | ||
| } | ||
| prev = cur; | ||
| } | ||
| checkCapacity(deltaVector.getDataBuffer(), prev, "data"); | ||
| } | ||
|
|
||
| /** See {@link #validateDelta(BaseVariableWidthVector)}. */ | ||
| private static void validateDelta(BaseLargeVariableWidthVector deltaVector) { | ||
| int valueCount = deltaVector.getValueCount(); | ||
| checkCapacity( | ||
| deltaVector.getValidityBuffer(), | ||
| BitVectorHelper.getValidityBufferSizeFromCount(valueCount), | ||
| "validity"); | ||
| ArrowBuf offsets = deltaVector.getOffsetBuffer(); | ||
| checkCapacity(offsets, (valueCount + 1L) * BaseLargeVariableWidthVector.OFFSET_WIDTH, "offset"); | ||
| long prev = offsets.getLong(0); | ||
| if (prev < 0) { | ||
| throw new IndexOutOfBoundsException("Delta vector has negative start offset " + prev); | ||
| } | ||
| for (int i = 1; i <= valueCount; i++) { | ||
| long cur = offsets.getLong((long) i * BaseLargeVariableWidthVector.OFFSET_WIDTH); | ||
| if (cur < prev) { | ||
| throw new IndexOutOfBoundsException( | ||
| "Delta vector offsets decrease at index " + i + ": " + prev + " > " + cur); | ||
| } | ||
| prev = cur; | ||
| } | ||
| checkCapacity(deltaVector.getDataBuffer(), prev, "data"); | ||
| } | ||
|
|
||
| private static void checkCapacity(ArrowBuf buffer, long required, String name) { | ||
| if (buffer.capacity() < required) { | ||
| throw new IndexOutOfBoundsException( | ||
| "Delta vector " | ||
| + name | ||
| + " buffer has capacity " | ||
| + buffer.capacity() | ||
| + ", needs " | ||
| + required); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public ValueVector visit(BaseVariableWidthViewVector deltaVector, Void value) { | ||
| Preconditions.checkArgument( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice improvement, this turns a raw out-of-bounds native copy into a checked one. One concern is ordering. By the time this
setBytesruns, the target's data buffer has been reallocated fordeltaDataSizeandconcatBitshas already written the delta's validity bits into the target.If the check throws, the target is left with extra validity bits while
valueCount, offsets andlastSetare unchanged.Could we validate the delta (last offset against its data buffer capacity) before any realloc or write, so a rejected delta leaves the target untouched?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Added a
validateDeltapass at the top of both visits that runs before any realloc orconcatBits, so a rejected delta now leaves the target as it was. The test checks that too (see below).