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-text.git


The following commit(s) were added to refs/heads/master by this push:
     new c07e17cc StrBuilder.replaceImpl shrink-branch leaves residual chars in 
buffer tail (#744)
c07e17cc is described below

commit c07e17cccd67f87d45eb6339659ebb3da6692a44
Author: Gary Gregory <[email protected]>
AuthorDate: Sun May 24 14:41:43 2026 -0400

    StrBuilder.replaceImpl shrink-branch leaves residual chars in buffer tail 
(#744)
    
    StrBuilder.setLength(int) shrink-branch leaves residual chars in buffer
    tail.
---
 .../java/org/apache/commons/text/StrBuilder.java   | 16 +++++++-
 .../apache/commons/text/StrBuilderClearTest.java   | 45 ++++++++++++++++++++++
 2 files changed, 59 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/commons/text/StrBuilder.java 
b/src/main/java/org/apache/commons/text/StrBuilder.java
index 02b18cf2..7cec4313 100644
--- a/src/main/java/org/apache/commons/text/StrBuilder.java
+++ b/src/main/java/org/apache/commons/text/StrBuilder.java
@@ -1819,6 +1819,15 @@ public class StrBuilder implements CharSequence, 
Appendable, Serializable, Build
         return toString();
     }
 
+    /**
+     * Gets the internal buffer for testing.
+     *
+     * @return the internal buffer.
+     */
+    char[] getBuffer() {
+        return buffer;
+    }
+
     /**
      * Copies the character array into the specified array.
      *
@@ -2608,6 +2617,9 @@ public class StrBuilder implements CharSequence, 
Appendable, Serializable, Build
         if (insertLen != removeLen) {
             ensureCapacity(newSize);
             System.arraycopy(buffer, endIndex, buffer, startIndex + insertLen, 
size - endIndex);
+            if (size > newSize) {
+                Arrays.fill(buffer, newSize, size, CharUtils.NUL);
+            }
             size = newSize;
         }
         if (insertLen > 0) {
@@ -2720,12 +2732,12 @@ public class StrBuilder implements CharSequence, 
Appendable, Serializable, Build
             throw new StringIndexOutOfBoundsException(length);
         }
         if (length < size) {
-            size = length;
+            Arrays.fill(buffer, length, size, CharUtils.NUL);
         } else if (length > size) {
             ensureCapacity(length);
             Arrays.fill(buffer, size, length, CharUtils.NUL);
-            size = length;
         }
+        size = length;
         return this;
     }
 
diff --git a/src/test/java/org/apache/commons/text/StrBuilderClearTest.java 
b/src/test/java/org/apache/commons/text/StrBuilderClearTest.java
index 4cd901ee..b7708992 100644
--- a/src/test/java/org/apache/commons/text/StrBuilderClearTest.java
+++ b/src/test/java/org/apache/commons/text/StrBuilderClearTest.java
@@ -17,7 +17,9 @@
 
 package org.apache.commons.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.ObjectInputStream;
 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;
 
@@ -138,6 +141,48 @@ public class StrBuilderClearTest {
         }
     }
 
+    @Test
+    void testReplaceImplLeavesResidue() throws Exception {
+        final String string = "SECRET_PASSWORD_DATA";
+        final StrBuilder sb = new StrBuilder(string);
+        assertEquals(20, sb.length());
+        // Shrink: replace [0,20) with "X" => removeLen=20, insertLen=1, 
newSize=1.
+        sb.replace(0, 20, "X");
+        assertEquals(1, sb.length());
+        assertEquals("X", sb.toString());
+        final char[] buf = sb.getBuffer();
+        assertTrue(buf.length >= 20);
+        // Tail [1..20) should be cleared but isn't on baseline => residue 
persists.
+        // Probe offset 5: original was '_' (underscore from "SECRET_..."). 
After
+        // arraycopy(buf, endIndex=20, buf, startIndex+insertLen=1, 
size-endIndex=0)
+        // the shift is a no-op, so buf[5] retains the original 'T' from 
"SECRET_".
+        // Either way it is non-NUL.
+        assertEquals(CharUtils.NUL, buf[5]);
+        // Dump the visible residue at the logical-unused tail.
+        for (int i = 1; i < 20; i++) {
+            assertEquals(CharUtils.NUL, buf[i]);
+        }
+    }
+
+    @Test
+    void testSetLengthShrinkLeavesResidue() 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]);
+        for (int i = 5; i < len; i++) {
+            assertEquals(CharUtils.NUL, buf[i]);
+        }
+    }
+
     @Test
     public void testStaleCharsNotLeakedAfterClear() throws Exception {
         final StrBuilder sb = new StrBuilder("secret_password_xyzzy_leak");

Reply via email to