regex: java.util.regex companion code (groups, lookarounds, replaceAll lambdas, ReDoS and timeouts)
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01KqJyCidz3ZgRyHABv2GVJh
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
matches() = true
|
||||
group(0) = 2026-03-14 ERROR disk /dev/sda1 is 97% full
|
||||
group(2) = ERROR
|
||||
level = ERROR
|
||||
start(msg) = 17, end(msg) = 43
|
||||
namedGroups = {date=1, level=2, msg=3}
|
||||
|
||||
matches() on "order-42 shipped" = false
|
||||
lookingAt() on "order-42 shipped" = false
|
||||
find() on "order-42 shipped" = true
|
||||
|
||||
results() over "a1 b22 c333":
|
||||
a1 -> group(1)=1
|
||||
b22 -> group(1)=22
|
||||
c333 -> group(1)=333
|
||||
|
||||
no flags : 0
|
||||
CASE_INSENSITIVE : 0
|
||||
MULTILINE : 1
|
||||
MULTILINE|CASE_INS. : 3
|
||||
embedded (?im) : 3
|
||||
'.' vs newline : 0 / DOTALL 1
|
||||
COMMENTS : 1
|
||||
@@ -0,0 +1,12 @@
|
||||
\d+(?=px) -> "120", "48"
|
||||
foo(?!bar) -> "foo", "foo"
|
||||
(?<=\$)\d+(?:\.\d\d)? -> "19.99", "23"
|
||||
(?<![\d$.])\d+ -> "3"
|
||||
policy abcdefgh -> false
|
||||
policy Abcdefg1 -> true
|
||||
policy Ab1 -> false
|
||||
policy ABCDEFG1x -> true
|
||||
1,234,567
|
||||
compiled (?<=a+)b
|
||||
compiled (?<=a{1,9})b
|
||||
rejected (?<=(?:ab)+)c : Look-behind group does not have an obvious maximum length
|
||||
@@ -0,0 +1,9 @@
|
||||
price: 5 dollars, fee: 12 dollars
|
||||
price: USD 5, fee: USD 12
|
||||
replaceAll("$") -> IllegalArgumentException: Illegal group reference: group index is missing
|
||||
price: 5 $, fee: 12 $
|
||||
price: 10 USD, fee: 24 USD
|
||||
unquoted lambda -> IndexOutOfBoundsException: No group 5
|
||||
hello ankur, you owe $5
|
||||
hi ${nobody}
|
||||
rEgUlAr ExprEssIOns
|
||||
@@ -0,0 +1,28 @@
|
||||
cap per run: 2000 ms; input = n letters 'a' followed by '!' (no match possible)
|
||||
|
||||
pattern (a+)+b
|
||||
n=20 0 ms matches=false
|
||||
n=36 0 ms matches=false
|
||||
n=500 1 ms matches=false
|
||||
n=1000 23 ms matches=false
|
||||
n=2000 63 ms matches=false
|
||||
n=4000 161 ms matches=false
|
||||
|
||||
pattern (a+)+\1?b
|
||||
n=20 27 ms matches=false
|
||||
n=24 690 ms matches=false
|
||||
n=26 ABORTED after 2036 ms (cap)
|
||||
|
||||
pattern (?:(?:a+)+)+b
|
||||
n=16 3 ms matches=false
|
||||
n=18 13 ms matches=false
|
||||
n=20 101 ms matches=false
|
||||
n=22 896 ms matches=false
|
||||
n=24 ABORTED after 2000 ms (cap)
|
||||
|
||||
pattern a*a*a*a*b
|
||||
n=50 2 ms matches=false
|
||||
n=100 69 ms matches=false
|
||||
n=150 285 ms matches=false
|
||||
n=200 824 ms matches=false
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
input: 40 x 'a' + '!' budget 1000 ms
|
||||
vulnerable (?:(?:a+)+)+b ABORTED after 1019 ms
|
||||
rewritten a+b 0 ms matches=false
|
||||
possessive (?:a++)++b 7 ms matches=false
|
||||
atomic (?>(?:a+)+)b 0 ms matches=false
|
||||
|
||||
find() a+b n=5000 139 ms found=false
|
||||
find() a+b n=10000 489 ms found=false
|
||||
find() a+b n=20000 1906 ms found=false
|
||||
find() (?<!a)a+b n=5000 6 ms found=false
|
||||
find() (?<!a)a+b n=10000 6 ms found=false
|
||||
find() (?<!a)a+b n=20000 15 ms found=false
|
||||
@@ -0,0 +1,6 @@
|
||||
(?:a|b)*c length 1000 -> true
|
||||
(?:a|b)*c length 5000 -> StackOverflowError
|
||||
(?:a|b)*c length 20000 -> StackOverflowError
|
||||
(?:a|b)*c length 100000 -> StackOverflowError
|
||||
[ab]*c length 100000 -> true
|
||||
(?:a|b)*+c length 100000 -> true
|
||||
@@ -0,0 +1,22 @@
|
||||
$ java -version
|
||||
openjdk version "25.0.4.1" 2026-08-18 LTS
|
||||
OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)
|
||||
OpenJDK 64-Bit Server VM Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS, mixed mode, sharing)
|
||||
|
||||
$ unzip -p lib/src.zip java.base/java/util/regex/Pattern.java | sed -n "/Optimize the greedy Loop/,/^ }$/p"
|
||||
// Optimize the greedy Loop to prevent exponential backtracking, IF there
|
||||
// is no group ref in this pattern. With a non-negative localTCNCount value,
|
||||
// the greedy type Loop, Curly will skip the backtracking for any starting
|
||||
// position "i" that failed in the past.
|
||||
if (!hasGroupRef) {
|
||||
for (Node node : topClosureNodes) {
|
||||
if (node instanceof Loop) {
|
||||
// non-deterministic-greedy-group
|
||||
((Loop)node).posIndex = localTCNCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ grep -ci timeout Pattern.java Matcher.java
|
||||
Pattern.java: 0
|
||||
Matcher.java: 0
|
||||
@@ -0,0 +1,4 @@
|
||||
-------------------------------------------------------------------------------
|
||||
Test set: com.ankurm.regex.RegexClaimsTest
|
||||
-------------------------------------------------------------------------------
|
||||
Tests run: 11, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.131 s -- in com.ankurm.regex.RegexClaimsTest
|
||||
Reference in New Issue
Block a user