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 7d325c2a5 Keep RandomStringUtils.random rejection cap from failing 
valid ranges (#1843)
7d325c2a5 is described below

commit 7d325c2a5bfb61991020f46585ae78a34546527e
Author: alhuda <[email protected]>
AuthorDate: Wed Oct 28 16:26:59 2026 +0530

    Keep RandomStringUtils.random rejection cap from failing valid ranges 
(#1843)
    
    The cap on consecutive rejected draws was Math.max(64, gap * 10), but each
    draw takes gapBits random bits, so a valid range with sparse acceptable
    values accepts a draw with probability as low as 1 / 2^gapBits. With 63
    of 64 chars rejected by the letters filter, random(100, 0, 64, true,
    false, chars, rng) threw IllegalArgumentException in about 45% of calls.
    
    Scale the cap with the sample space (64L << gapBits) so a range with at
    least one acceptable value exhausts it with probability below e^-64 per
    character, while a range with none still throws.
---
 .../java/org/apache/commons/lang3/RandomStringUtils.java  | 12 +++++++-----
 .../org/apache/commons/lang3/RandomStringUtilsTest.java   | 15 +++++++++++++++
 2 files changed, 22 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/apache/commons/lang3/RandomStringUtils.java 
b/src/main/java/org/apache/commons/lang3/RandomStringUtils.java
index 97874e7d3..03ce42d71 100644
--- a/src/main/java/org/apache/commons/lang3/RandomStringUtils.java
+++ b/src/main/java/org/apache/commons/lang3/RandomStringUtils.java
@@ -395,11 +395,13 @@ public static String random(int count, int start, int 
end, final boolean letters
         final CachedRandomBits arb = new CachedRandomBits(cacheSize, random);
         // Bound rejection retries so a range that rejects every sample
         // (for example, entirely UNASSIGNED/PRIVATE_USE/SURROGATE) raises an
-        // IllegalArgumentException instead of looping indefinitely. Cap is
-        // (end - start) * 10 with a small floor so tiny gaps still get a
-        // reasonable budget. The counter resets on every accepted code point.
-        final int maxRejections = Math.max(64, gap * 10);
-        int rejections = 0;
+        // IllegalArgumentException instead of looping indefinitely. Each draw
+        // takes gapBits bits, so a range holding a single acceptable value
+        // accepts a draw with probability 1 / 2^gapBits; scaling the cap by
+        // that sample space keeps a valid request from exhausting it (chance
+        // below e^-64 per character). The counter resets on every accepted 
code point.
+        final long maxRejections = 64L << gapBits;
+        long rejections = 0;
         while (count-- != 0) {
             // Generate a random value between start (included) and end 
(excluded)
             final int randomValue = arb.nextBits(gapBits) + start;
diff --git a/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java 
b/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java
index 190faffc8..1a9a87089 100644
--- a/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java
@@ -29,6 +29,7 @@
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
+import java.util.Arrays;
 import java.util.Random;
 import java.util.function.IntFunction;
 import java.util.stream.Stream;
@@ -492,6 +493,20 @@ void testOnlyRejectedCodePoints() {
                 () -> assertThrows(IllegalArgumentException.class, () -> 
RandomStringUtils.random(1, 0xE000, 0xF900, false, false, null, new 
Random(42))));
     }
 
+    /**
+     * A range with a single acceptable value is valid and must not trip the 
rejection cap. Here 63 of the 64 chars are rejected by the letters filter and
+     * each draw takes 7 bits, so a draw is accepted with probability 1/128.
+     */
+    @Test
+    void testSparseAcceptableValuesDoNotTripRejectionCap() {
+        final char[] chars = new char[64];
+        Arrays.fill(chars, '-');
+        chars[63] = 'x';
+        for (int seed = 0; seed < 20; seed++) {
+            assertEquals(StringUtils.repeat('x', 100), 
RandomStringUtils.random(100, 0, 64, true, false, chars, new Random(seed)));
+        }
+    }
+
     /**
      * Make sure boundary alpha characters are generated by randomAlphabetic 
This test will fail randomly with probability = 4 * (51/52)**1000 ~ 1.58E-8
      */

Reply via email to