From a420fd09fe87df35653774fcd0f76cd434a2930f Mon Sep 17 00:00:00 2001 From: Daniel Roudnitsky Date: Sun, 19 Oct 2025 12:54:45 -0400 Subject: [PATCH] HBASE-29675 Add bounds check/descriptive OffsetOutOfBoundsException to BinaryComponentComparator --- .../filter/BinaryComponentComparator.java | 38 ++++++++++++++++--- ...tFiltersWithBinaryComponentComparator.java | 17 +++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/BinaryComponentComparator.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/BinaryComponentComparator.java index 6e0ff7edf527..e6a7f68ddf01 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/BinaryComponentComparator.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/filter/BinaryComponentComparator.java @@ -27,8 +27,10 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.ComparatorProtos; /** - * A comparator which compares against a specified byte array, but only compares specific portion of - * the byte array. For the rest it is similar to {@link BinaryComparator}. + * A binary comparator which lexicographically compares against the specified byte array similar to + * {@link BinaryComparator} but starts the comparison from the specified offset. Will throw a + * runtime exception if a comparison is attempted with a byte array that is too short for the + * specified offest. See HBASE-22969 for examples on how to use this comparator */ @InterfaceAudience.Public @SuppressWarnings("ComparableType") @@ -36,22 +38,48 @@ public class BinaryComponentComparator extends ByteArrayComparable { private int offset; // offset of component from beginning. /** - * Constructor - * @param value value of the component - * @param offset offset of the component from begining + * Exception thrown when the offset provided to BinaryComponentComparator at construction time + * exceeds the bounds of an input byte array provided to `compareTo` + */ + public static class OffsetOutOfBoundsException extends ArrayIndexOutOfBoundsException { + public OffsetOutOfBoundsException(String message) { + super(message); + } + } + + /** + * @param value the byte array to compare against + * @param offset the starting position (0-based) from which to start comparisons Must be less than + * the length of input arrays being passed for comparison, otherwise an + * {@link OffsetOutOfBoundsException} will be thrown on comparison. */ public BinaryComponentComparator(byte[] value, int offset) { super(value); this.offset = offset; } + /** + * @throws OffsetOutOfBoundsException if input byte array is too small for the offset provided to + * comparator when it was constructed + */ @Override public int compareTo(byte[] value) { return compareTo(value, 0, value.length); } + /** + * @throws OffsetOutOfBoundsException if input byte array is too small for the offset provided to + * comparator when it was constructed + */ @Override public int compareTo(byte[] value, int offset, int length) { + if (offset + this.offset >= value.length) { + String message = String.format( + "A byte array was encountered with a length %d that is too" + + " short/incompatible with the offset value %d provided to BinaryComponentComparator", + value.length, offset + this.offset); + throw new OffsetOutOfBoundsException(message); + } return Bytes.compareTo(this.value, 0, this.value.length, value, offset + this.offset, this.value.length); } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFiltersWithBinaryComponentComparator.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFiltersWithBinaryComponentComparator.java index 2e148e41c55c..d9d47a2d1874 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFiltersWithBinaryComponentComparator.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/filter/TestFiltersWithBinaryComponentComparator.java @@ -17,6 +17,7 @@ */ package org.apache.hadoop.hbase.filter; +import static org.hamcrest.Matchers.isA; import static org.junit.Assert.assertTrue; import java.io.IOException; @@ -43,6 +44,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.ExpectedException; import org.junit.rules.TestName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -148,6 +150,21 @@ public void testRowAndValueFilterWithBinaryComponentComparator() throws IOExcept ht.close(); } + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testThrowsExceptionOnOffsetOutOfBounds() { + thrown.expect(isA(BinaryComponentComparator.OffsetOutOfBoundsException.class)); + + byte[] componentValue = new byte[] { (byte) 'A' }; + int offsetValue = 10; + byte[] valueTooShortForOffset = new byte[offsetValue]; + BinaryComponentComparator comparator = + new BinaryComponentComparator(componentValue, offsetValue); + comparator.compareTo(valueTooShortForOffset); + } + /** * Since we are trying to emulate SQL: SELECT * from table where a = 1 and b > 10 and b < 20 and c * > 90 and c < 100 and d = 1 We are generating rows with: a = 1, b >=9 and b < 22, c >= 89 and c