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