This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push:
new 13d93944f [LANG-1824] Fix Strings.replace overflow on large
replacements (#1716)
13d93944f is described below
commit 13d93944ff76c6ca3e140d15478111b18acad0e6
Author: Dhruv Aggarwal <[email protected]>
AuthorDate: Sun Jun 21 17:03:54 2026 +0530
[LANG-1824] Fix Strings.replace overflow on large replacements (#1716)
Co-authored-by: Dhruv Aggarwal <[email protected]>
---
.../java/org/apache/commons/lang3/Strings.java | 36 ++++++++++++++++++++--
.../java/org/apache/commons/lang3/StringsTest.java | 12 ++++++++
2 files changed, 45 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/apache/commons/lang3/Strings.java
b/src/main/java/org/apache/commons/lang3/Strings.java
index 434510ef7..8c552b561 100644
--- a/src/main/java/org/apache/commons/lang3/Strings.java
+++ b/src/main/java/org/apache/commons/lang3/Strings.java
@@ -1298,9 +1298,8 @@ public String replace(final String text, final String
searchString, final String
return text;
}
final int replLength = searchString.length();
- int increase = Math.max(replacement.length() - replLength, 0);
- increase *= max < 0 ? 16 : Math.min(max, 64);
- final StringBuilder buf = new StringBuilder(text.length() + increase);
+ final int initialCapacity = computeInitialCapacity(text.length(),
replLength, replacement.length(), max);
+ final StringBuilder buf = new StringBuilder(initialCapacity);
while (end != INDEX_NOT_FOUND) {
buf.append(text, start, end).append(replacement);
start = end + replLength;
@@ -1313,6 +1312,37 @@ public String replace(final String text, final String
searchString, final String
return buf.toString();
}
+ /**
+ * Computes a safe initial capacity for the {@link StringBuilder} used by
+ * {@link #replace(String, String, String, int)}.
+ *
+ * <p>
+ * Uses {@code long} arithmetic so that the estimated growth cannot
overflow
+ * {@code int} when {@code replacementLength} is much greater than
+ * {@code searchLength}, and clamps the result to
+ * {@link ArrayUtils#SAFE_MAX_ARRAY_LENGTH} so that
+ * {@code new StringBuilder(int)} is never invoked with a value that
exceeds
+ * the VM's array-size limit.
+ * </p>
+ *
+ * <p>
+ * The estimated number of matches is {@code 16} when {@code max} is
negative
+ * (unbounded), otherwise {@code Math.min(max, 64)}. These multipliers
+ * preserve the historical behavior of the inlined estimate.
+ * </p>
+ *
+ * @param textLength the length of the input text, in characters.
+ * @param searchLength the length of the search string, in characters.
+ * @param replacementLength the length of the replacement string, in
characters.
+ * @param max the maximum number of replacements, or {@code
-1} for no maximum.
+ * @return a non-negative initial capacity, never greater than {@link
ArrayUtils#SAFE_MAX_ARRAY_LENGTH}.
+ */
+ static int computeInitialCapacity(final int textLength, final int
searchLength, final int replacementLength, final int max) {
+ final long perReplacementGrowth = Math.max((long) replacementLength -
searchLength, 0L);
+ final long totalGrowth = perReplacementGrowth * (max < 0 ? 16 :
Math.min(max, 64));
+ return (int) Math.min((long) textLength + totalGrowth,
ArrayUtils.SAFE_MAX_ARRAY_LENGTH);
+ }
+
/**
* Replaces a String with another String inside a larger String, once.
*
diff --git a/src/test/java/org/apache/commons/lang3/StringsTest.java
b/src/test/java/org/apache/commons/lang3/StringsTest.java
index 4eaea90b9..86cc467a3 100644
--- a/src/test/java/org/apache/commons/lang3/StringsTest.java
+++ b/src/test/java/org/apache/commons/lang3/StringsTest.java
@@ -72,6 +72,18 @@ void testCaseInsensitiveReplaceLengthChangingLowerCase() {
assertEquals("X", Strings.CI.replaceOnce("İ", "İ", "X"));
}
+ @Test
+ void testComputeInitialCapacityReplacementGrowthDoesNotOverflowInt() {
+ final int capacity = Strings.computeInitialCapacity(0, 0, 50_000_000,
64);
+ assertEquals(ArrayUtils.SAFE_MAX_ARRAY_LENGTH, capacity);
+ }
+
+ @Test
+ void testComputeInitialCapacityNeverOverflowsForMaxValueInputs() {
+ final int capacity = Strings.computeInitialCapacity(Integer.MAX_VALUE,
0, Integer.MAX_VALUE, 64);
+ assertEquals(ArrayUtils.SAFE_MAX_ARRAY_LENGTH, capacity);
+ }
+
/**
* Expanding the existing test group {@link
StringUtilsStartsEndsWithTest#testStartsWithAny()} to include case-insensitive
cases
*/