Copilot commented on code in PR #1813:
URL: https://github.com/apache/commons-lang/pull/1813#discussion_r4270760617


##########
src/main/java/org/apache/commons/lang3/StringUtils.java:
##########
@@ -7089,13 +7109,20 @@ public static String rightPad(final String str, final 
int size, String padStr) {
         }
         final int padLen = padStr.length();
         final int strLen = str.length();
-        final int pads = size - strLen;
+        int pads = size - strLen;
         if (pads <= 0) {
             return str; // returns original String when possible
         }
         if (padLen == 1 && pads <= PAD_LIMIT) {
             return rightPad(str, size, padStr.charAt(0));
         }
+        // keep the cut off the middle of a surrogate pair so the result is 
never left holding a lone surrogate
+        if (splitsSurrogatePair(padStr, pads % padLen)) {

Review Comment:
   Same issue as in `leftPad`: `pads % padLen` may be `0`. If 
`splitsSurrogatePair` doesn’t explicitly handle index `0`, this can throw at 
runtime. Add a `splitPos != 0` guard (or equivalent) before calling 
`splitsSurrogatePair`.



##########
src/main/java/org/apache/commons/lang3/StringUtils.java:
##########
@@ -649,6 +649,9 @@ public static String center(String str, final int size, 
final char padChar) {
      *
      * <p>
      * If the size is less than the String length, the String is returned. A 
{@code null} String returns {@code null}. A negative size is treated as zero.
+     * If the requested padding length on either side would split a Unicode 
surrogate pair in {@code padStr}, the padding is shortened by one {@code char}
+     * (via delegation to {@link #leftPad(String, int, String)} and {@link 
#rightPad(String, int, String)}) so the result never contains an unpaired
+     * surrogate. In that case, the returned String has a length of {@code 
size - 1}.

Review Comment:
   For `center(String, int, String)`, the Javadoc claims the result length 
becomes `size - 1` in the surrogate-pair case. Since centering pads both sides 
via `leftPad` and `rightPad`, it’s possible (depending on the computed 
left/right pad sizes and `padStr` length) for both sides to hit the 
surrogate-split adjustment, producing `size - 2`. Recommend rewording to 
something like “may be shorter than `size` (by 1 on either side where a split 
would occur)” or explicitly document the possible range so callers don’t rely 
on `size - 1` being the only shortened outcome.



##########
src/test/java/org/apache/commons/lang3/StringUtilsTest.java:
##########
@@ -340,6 +340,41 @@ void testCenter_StringIntString() {
         assertEquals("  abc  ", StringUtils.center("abc", 7, ""));
     }
 
+    private void assertNoLoneSurrogates(final String result) {
+        for (int i = 0; i < result.length(); i++) {
+            final char ch = result.charAt(i);
+            if (Character.isHighSurrogate(ch)) {
+                assertTrue(i + 1 < result.length() && 
Character.isLowSurrogate(result.charAt(i + 1)),
+                        "lone high surrogate in: " + result);
+                i++; // skip the paired low surrogate
+            } else {
+                assertFalse(Character.isLowSurrogate(ch), "lone low surrogate 
in: " + result);
+            }
+        }
+    }
+
+    @Test
+    void testCenterSurrogatePair() {
+        final String grin = "😀";
+        assertEquals("a", StringUtils.center("a", 2, grin));
+        assertEquals("a😀", StringUtils.center("a", 3, grin));
+        assertEquals("a😀", StringUtils.center("a", 4, grin));
+        assertEquals("😀a😀", StringUtils.center("a", 5, grin));
+        assertEquals("😀a😀", StringUtils.center("a", 6, grin));
+        assertEquals("😀a😀😀", StringUtils.center("a", 7, grin));
+
+        final String[] testStrings = {"", "a", "hello", grin, "a" + grin + 
"b"};
+        final String[] padStrings = {grin, "a" + grin, grin + "b", "a" + grin 
+ "b", grin + "🎉"};

Review Comment:
   These tests embed non-BMP characters directly as source literals. If the 
build/tooling ever runs with a non-UTF-8 source encoding (or misconfigured 
compiler/source encoding), this can lead to compilation/runtime mismatches. 
Using explicit surrogate escapes (e.g., `"\uD83D\uDE00"`) or `new 
String(Character.toChars(0x1F600))` makes the tests encoding-robust across 
environments.



##########
src/test/java/org/apache/commons/lang3/StringUtilsTest.java:
##########
@@ -340,6 +340,41 @@ void testCenter_StringIntString() {
         assertEquals("  abc  ", StringUtils.center("abc", 7, ""));
     }
 
+    private void assertNoLoneSurrogates(final String result) {
+        for (int i = 0; i < result.length(); i++) {
+            final char ch = result.charAt(i);
+            if (Character.isHighSurrogate(ch)) {
+                assertTrue(i + 1 < result.length() && 
Character.isLowSurrogate(result.charAt(i + 1)),
+                        "lone high surrogate in: " + result);
+                i++; // skip the paired low surrogate
+            } else {
+                assertFalse(Character.isLowSurrogate(ch), "lone low surrogate 
in: " + result);
+            }
+        }
+    }
+
+    @Test
+    void testCenterSurrogatePair() {
+        final String grin = "😀";
+        assertEquals("a", StringUtils.center("a", 2, grin));
+        assertEquals("a😀", StringUtils.center("a", 3, grin));
+        assertEquals("a😀", StringUtils.center("a", 4, grin));
+        assertEquals("😀a😀", StringUtils.center("a", 5, grin));
+        assertEquals("😀a😀", StringUtils.center("a", 6, grin));
+        assertEquals("😀a😀😀", StringUtils.center("a", 7, grin));
+
+        final String[] testStrings = {"", "a", "hello", grin, "a" + grin + 
"b"};
+        final String[] padStrings = {grin, "a" + grin, grin + "b", "a" + grin 
+ "b", grin + "🎉"};

Review Comment:
   These tests embed non-BMP characters directly as source literals. If the 
build/tooling ever runs with a non-UTF-8 source encoding (or misconfigured 
compiler/source encoding), this can lead to compilation/runtime mismatches. 
Using explicit surrogate escapes (e.g., `"\uD83D\uDE00"`) or `new 
String(Character.toChars(0x1F600))` makes the tests encoding-robust across 
environments.



##########
src/test/java/org/apache/commons/lang3/StringUtilsTest.java:
##########
@@ -340,6 +340,41 @@ void testCenter_StringIntString() {
         assertEquals("  abc  ", StringUtils.center("abc", 7, ""));
     }
 
+    private void assertNoLoneSurrogates(final String result) {
+        for (int i = 0; i < result.length(); i++) {
+            final char ch = result.charAt(i);
+            if (Character.isHighSurrogate(ch)) {
+                assertTrue(i + 1 < result.length() && 
Character.isLowSurrogate(result.charAt(i + 1)),
+                        "lone high surrogate in: " + result);
+                i++; // skip the paired low surrogate
+            } else {
+                assertFalse(Character.isLowSurrogate(ch), "lone low surrogate 
in: " + result);
+            }
+        }
+    }
+
+    @Test
+    void testCenterSurrogatePair() {
+        final String grin = "😀";
+        assertEquals("a", StringUtils.center("a", 2, grin));
+        assertEquals("a😀", StringUtils.center("a", 3, grin));
+        assertEquals("a😀", StringUtils.center("a", 4, grin));
+        assertEquals("😀a😀", StringUtils.center("a", 5, grin));
+        assertEquals("😀a😀", StringUtils.center("a", 6, grin));
+        assertEquals("😀a😀😀", StringUtils.center("a", 7, grin));
+
+        final String[] testStrings = {"", "a", "hello", grin, "a" + grin + 
"b"};
+        final String[] padStrings = {grin, "a" + grin, grin + "b", "a" + grin 
+ "b", grin + "🎉"};
+        for (final String str : testStrings) {
+            for (final String padStr : padStrings) {
+                for (int size = 0; size <= 15; size++) {
+                    final String result = StringUtils.center(str, size, 
padStr);
+                    assertNoLoneSurrogates(result);
+                }
+            }
+        }

Review Comment:
   Very similar nested-loop “no lone surrogates” fuzz logic is duplicated 
across the new `center`, `leftPad`, and `rightPad` tests. Consider extracting a 
small private helper (e.g., takes a `BiFunction<String,Integer,String>`/lambda 
or an enum indicating which API to call) to reduce duplication and keep future 
additions (more sizes/strings) consistent across all three cases.



##########
src/main/java/org/apache/commons/lang3/StringUtils.java:
##########
@@ -5275,13 +5284,20 @@ public static String leftPad(final String str, final 
int size, String padStr) {
         }
         final int padLen = padStr.length();
         final int strLen = str.length();
-        final int pads = size - strLen;
+        int pads = size - strLen;
         if (pads <= 0) {
             return str; // returns original String when possible
         }
         if (padLen == 1 && pads <= PAD_LIMIT) {
             return leftPad(str, size, padStr.charAt(0));
         }
+        // keep the cut off the middle of a surrogate pair so the result is 
never left holding a lone surrogate
+        if (splitsSurrogatePair(padStr, pads % padLen)) {

Review Comment:
   `pads % padLen` can be `0` when the padding length is an exact multiple of 
`padLen`. If `splitsSurrogatePair` assumes the index is within `1..padLen-1` (a 
common implementation checks `pos-1` and `pos`), passing `0` can cause an 
out-of-bounds access. Consider guarding the call (e.g., compute `final int 
splitPos = pads % padLen;` and only call `splitsSurrogatePair` when `splitPos 
!= 0`). This avoids a potential runtime exception while keeping the behavior 
unchanged when no cut occurs.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to