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
      */

Reply via email to