diff --git a/src/main/java/org/apache/commons/lang3/ArrayFill.java b/src/main/java/org/apache/commons/lang3/ArrayFill.java index 7b9617e6320..e6b9d95e596 100644 --- a/src/main/java/org/apache/commons/lang3/ArrayFill.java +++ b/src/main/java/org/apache/commons/lang3/ArrayFill.java @@ -29,6 +29,22 @@ */ public final class ArrayFill { + /** + * Fills and returns the given array, assigning {@code 0} to each element of the array. + *
+     * ArrayFill.fill(a, (byte) 0);
+     * 
+ * + * @param a the array to fill (may be null). + * @return the given array. + * @see Arrays#fill(byte[],byte) + * @see ArrayFill#fill(byte[],byte) + * @since 3.21.0 + */ + public static byte[] clear(final byte[] a) { + return fill(a, (byte) 0); + } + /** * Fills and returns the given array, assigning {@code '\0'} to each element of the array. *

@@ -50,19 +66,23 @@ public static char[] clear(final char[] a) { } /** - * Fills and returns the given array, assigning {@code 0} to each element of the array. + * Fills and returns the given array, assigning {@code '\0'} to each element of the array. + *

+ * Equivalent to: + *

*
-     * ArrayFill.fill(a, (byte) 0);
+     * ArrayFill.fill(a, fromIndex, toIndex, '\0'); // and not '0'!
      * 
* - * @param a the array to fill (may be null). + * @param a the array to fill (may be null). + * @param fromIndex the index of the first element (inclusive) to be filled with {@code '\0'}. + * @param toIndex the index of the last element (exclusive) to be filled with {@code '\0'}. * @return the given array. - * @see Arrays#fill(byte[],byte) - * @see ArrayFill#fill(byte[],byte) + * @see Arrays#fill(char[], int, int, char) * @since 3.21.0 */ - public static byte[] clear(final byte[] a) { - return fill(a, (byte) 0); + public static char[] clear(char[] a, int fromIndex, int toIndex) { + return fill(a, fromIndex, toIndex, CharUtils.NUL); } /** @@ -111,6 +131,24 @@ public static char[] fill(final char[] a, final char val) { return a; } + /** + * Fills and returns the given array, assigning the given {@code char} value to each element of the array. + * + * @param a the array to fill (may be null). + * @param val the value to store in all elements of the array. + * @param fromIndex the index of the first element (inclusive) to be filled with the specified value. + * @param toIndex the index of the last element (exclusive) to be filled with the specified value. + * @return the given array. + * @see Arrays#fill(char[], int, int, char) + * @since 3.21.0 + */ + public static char[] fill(char[] a, int fromIndex, int toIndex, char val) { + if (a != null) { + Arrays.fill(a, fromIndex, toIndex, val); + } + return a; + } + /** * Fills and returns the given array, assigning the given {@code double} value to each element of the array. * diff --git a/src/main/java/org/apache/commons/lang3/text/StrBuilder.java b/src/main/java/org/apache/commons/lang3/text/StrBuilder.java index a72c2274563..3f692c8e0d3 100644 --- a/src/main/java/org/apache/commons/lang3/text/StrBuilder.java +++ b/src/main/java/org/apache/commons/lang3/text/StrBuilder.java @@ -26,8 +26,8 @@ import java.util.List; import java.util.Objects; +import org.apache.commons.lang3.ArrayFill; import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.CharUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Strings; @@ -1614,7 +1614,7 @@ public char charAt(final int index) { */ public StrBuilder clear() { size = 0; - Arrays.fill(buffer, CharUtils.NUL); + ArrayFill.clear(buffer); return this; } @@ -1810,7 +1810,7 @@ public StrBuilder deleteFirst(final StrMatcher matcher) { private void deleteImpl(final int startIndex, final int endIndex, final int len) { System.arraycopy(buffer, endIndex, buffer, startIndex, size - endIndex); size -= len; - Arrays.fill(buffer, size, size + len, CharUtils.NUL); + ArrayFill.clear(buffer, size, size + len); } /** @@ -1920,6 +1920,15 @@ public boolean equalsIgnoreCase(final StrBuilder other) { return true; } + /** + * Gets the internal buffer for testing. + * + * @return the internal buffer. + */ + char[] getBuffer() { + return buffer; + } + /** * Copies the character array into the specified array. * @@ -2682,14 +2691,14 @@ public StrBuilder replaceFirst(final StrMatcher matcher, final String replaceStr } /** - * Internal method to delete a range without validation. + * Internal method to replace a range without validation. * - * @param startIndex the start index, must be valid - * @param endIndex the end index (exclusive), must be valid - * @param removeLen the length to remove (endIndex - startIndex), must be valid - * @param insertStr the string to replace with, null means delete range - * @param insertLen the length of the insert string, must be valid - * @throws IndexOutOfBoundsException if any index is invalid + * @param startIndex the start index (inclusive), must be valid. + * @param endIndex the end index (exclusive), must be valid. + * @param removeLen the length to remove (endIndex - startIndex), must be valid. + * @param insertStr the string to replace with, null means delete range. + * @param insertLen the length of the insert string, must be valid. + * @throws IndexOutOfBoundsException if any index is invalid. */ private void replaceImpl(final int startIndex, final int endIndex, final int removeLen, final String insertStr, final int insertLen) { final int newSize = size - removeLen + insertLen; @@ -2815,12 +2824,12 @@ public StrBuilder setLength(final int length) { throw new StringIndexOutOfBoundsException(length); } if (length < size) { - size = length; + ArrayFill.clear(buffer, length, size); } else if (length > size) { ensureCapacity(length); - Arrays.fill(buffer, size, length, CharUtils.NUL); - size = length; + ArrayFill.clear(buffer, size, length); } + size = length; return this; } diff --git a/src/test/java/org/apache/commons/lang3/ArrayFillTest.java b/src/test/java/org/apache/commons/lang3/ArrayFillTest.java index 26e738d8057..9888228a655 100644 --- a/src/test/java/org/apache/commons/lang3/ArrayFillTest.java +++ b/src/test/java/org/apache/commons/lang3/ArrayFillTest.java @@ -66,6 +66,24 @@ void testClearCharArrayNull() { assertSame(array, actual); } + @Test + void testClearCharArrayRange() { + final char[] array = {'A', 'B', 'C', 'D', 'E'}; + final char[] actual = ArrayFill.clear(array, 1, 4); + assertSame(array, actual); + assertEquals('A', actual[0]); + assertEquals('\0', actual[1]); + assertEquals('\0', actual[2]); + assertEquals('\0', actual[3]); + assertEquals('E', actual[4]); + } + + @Test + void testClearCharArrayRangeNull() { + final char[] actual = ArrayFill.clear(null, 0, 0); + assertNull(actual); + } + @Test void testFillBooleanArray() { final boolean[] array = new boolean[3]; @@ -123,6 +141,33 @@ void testFillCharArrayNull() { assertSame(array, actual); } + @Test + void testFillCharArrayRange() { + final char[] array = {'A', 'B', 'C', 'D', 'E'}; + final char val = 'Z'; + final char[] actual = ArrayFill.fill(array, 1, 4, val); + assertSame(array, actual); + assertEquals('A', actual[0]); + assertEquals('Z', actual[1]); + assertEquals('Z', actual[2]); + assertEquals('Z', actual[3]); + assertEquals('E', actual[4]); + } + + @Test + void testFillCharArrayRangeEmpty() { + final char[] array = {'A', 'B', 'C'}; + final char[] actual = ArrayFill.fill(array, 1, 1, 'Z'); + assertSame(array, actual); + assertArrayEquals(new char[] {'A', 'B', 'C'}, actual); + } + + @Test + void testFillCharArrayRangeNull() { + final char[] actual = ArrayFill.fill(null, 0, 0, 'Z'); + assertNull(actual); + } + @Test void testFillDoubleArray() { final double[] array = new double[3]; diff --git a/src/test/java/org/apache/commons/lang3/text/StrBuilderClearTest.java b/src/test/java/org/apache/commons/lang3/text/StrBuilderClearTest.java index 9765362b050..e499a1d20f0 100644 --- a/src/test/java/org/apache/commons/lang3/text/StrBuilderClearTest.java +++ b/src/test/java/org/apache/commons/lang3/text/StrBuilderClearTest.java @@ -17,7 +17,9 @@ package org.apache.commons.lang3.text; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -25,6 +27,7 @@ import java.io.Reader; import java.nio.charset.StandardCharsets; +import org.apache.commons.lang3.CharUtils; import org.apache.commons.lang3.SerializationUtils; import org.junit.jupiter.api.Test; @@ -159,4 +162,24 @@ public void testStaleCharsNotLeakedAfterTruncate() throws Exception { // sb now logically contains "top_se" assertFalse(containsUtf16Be(SerializationUtils.serialize(sb), "secret_key_material")); } + + @Test + void testSetLengthShrinkLeavesResidual() throws Exception { + final String string = "CONFIDENTIAL_TOKEN_VALUE"; + final int len = string.length(); + final StrBuilder sb = new StrBuilder(string); + assertEquals(len, sb.length()); + // setLength(5) shrinks: size = 5, but [5..24) is NOT cleared. + sb.setLength(5); + assertEquals(5, sb.length()); + assertEquals("CONFI", sb.toString()); + final char[] buf = sb.getBuffer(); + assertTrue(buf.length >= len); + // Probe offset 10: original was 'L' (CONFIDENTIA*L*_TOKEN_VALUE). + assertEquals(CharUtils.NUL, buf[10]); + final StringBuilder dump = new StringBuilder(); + for (int i = 5; i < len; i++) { + assertEquals(CharUtils.NUL, buf[i]); + } + } }