undefect. CWE-407 — 63 sites patched across 27 ecosystems

Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
This commit is contained in:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,140 @@
/*
* Copyright 2020 Google Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.util.regex;
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Benchmarks of Patterns that exhibit O(2^N) performance due to catastrophic
* backtracking, **when implemented naively**.
*
* See: jdk/test/java/util/regex/RegExTest.java#expoBacktracking
* commit b45ea8903ec290ab194d9ebe040bc43edd5dd0a3
* Author: Xueming Shen <sherman@openjdk.org>
* Date: Tue May 10 21:19:25 2016 -0700
*
* Here's a way to compare the per-char cost:
*
* (cd $(git rev-parse --show-toplevel) && for size in 16 128 1024; do make test TEST='micro:java.util.regex.Exponential' MICRO="FORK=1;WARMUP_ITER=1;ITER=4;OPTIONS=-opi $size -p size=$size" |& perl -ne 'print if /^Benchmark/ .. /^Finished running test/'; done)
*
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(2)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
public class Exponential {
/** Run length of non-matching consecutive whitespace chars. */
@Param({"16", "128", "1024"})
// 2048+ runs into StackOverflowError; see JDK-8260866
int size;
public String justXs;
public String notJustXs;
// Patterns that match justXs but not notJustXs
public Pattern pat1;
public Pattern pat2;
public Pattern pat3;
public Pattern pat4;
Pattern compile(String regex) {
Pattern pat = Pattern.compile(regex);
// ad hoc correctness checking
if (! pat.matcher(justXs).matches()
|| pat.matcher(notJustXs).matches()) {
throw new AssertionError("unexpected matching: " + regex);
}
return pat;
}
@Setup(Level.Trial)
public void setup() {
justXs = "X".repeat(size);
notJustXs = justXs + "!";
// Will (or should) the engine optimize (?:X|X) to X ?
pat1 = compile("(?:X|X)*");
// Tougher to optimize than pat1
pat2 = compile("(?:[XY]|[XZ])*");
pat3 = compile("(X+)+");
pat4 = compile("^(X+)+$");
}
/** O(N) */
@Benchmark
public boolean pat1_justXs() {
return pat1.matcher(justXs).matches();
}
/** O(N) */
@Benchmark
public boolean pat1_notJustXs() {
return pat1.matcher(notJustXs).matches();
}
/** O(N) */
@Benchmark
public boolean pat2_justXs() {
return pat2.matcher(justXs).matches();
}
/** O(N) */
@Benchmark
public boolean pat2_notJustXs() {
return pat2.matcher(notJustXs).matches();
}
/** O(1) - very surprising! */
@Benchmark
public boolean pat3_justXs() {
return pat3.matcher(justXs).matches();
}
/** O(N^2) - surprising! O(N) seems very achievable. */
@Benchmark
public boolean pat3_notJustXs() {
return pat3.matcher(notJustXs).matches();
}
/** O(1) - very surprising! */
@Benchmark
public boolean pat4_justXs() {
return pat4.matcher(justXs).matches();
}
/** O(N^2) - surprising! O(N) seems very achievable. */
@Benchmark
public boolean pat4_notJustXs() {
return pat4.matcher(notJustXs).matches();
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.util.regex;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value=1, jvmArgs= {"-showversion", "-XX:+UseSerialGC"})
@Warmup(iterations = 1, time = 10, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.SECONDS)
public class FindPattern {
@Param({"[^A-Za-z0-9]", "[A-Za-z0-9]"})
static String patternString;
@Param({"abcdefghijklmnop1234567890ABCDEFGHIJKLMNOP",
",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"})
static String text;
static Pattern pattern;
@Setup(Level.Trial)
public void setupTrial() {
pattern = Pattern.compile(patternString);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
public int testFind() {
int counter = 0;
Matcher m = pattern.matcher(text);
while (m.find()) {
counter++;
}
return counter;
}
}

View file

@ -0,0 +1,116 @@
/*
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.util.regex;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(2)
@Warmup(iterations = 4, time = 2, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 4, time = 2, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
public class PatternBench {
public String fileTestString;
public String flagsString;
public Pattern graphemePattern;
public Pattern graphemeBoundaryPattern;
public Pattern jmodPattern;
public Pattern jmodCanonicalPattern;
public String charPatternRegex;
public String[] charPatternStrings;
public Pattern charPattern;
@Setup
public void setup() {
flagsString = "\ud83c\udde6\ud83c\uddec\ud83c\uddec\ud83c\udde6\ud83c\uddfa\ud83c\uddf8\ud83c\uddeb\ud83c\uddf7";
fileTestString = "META-INF/providers/org.openjdk.foo_hotspot_nodes_PluginFactory_EndLockScopeNode";
graphemePattern = Pattern.compile("\\X");
graphemeBoundaryPattern = Pattern.compile("\\b{g}");
String jmodRegex = "^.*(?:(?:_the\\.[^/]*)|(?:_[^/]*\\.marker)|(?:[^/]*\\.diz)|(?:[^/]*\\.debuginfo)|(?:[^/]*\\.dSYM/.*)|(?:[^/]*\\.dSYM)|(?:[^/]*\\.pdb)|(?:[^/]*\\.map))$";
jmodCanonicalPattern = Pattern.compile(jmodRegex, Pattern.CANON_EQ);
jmodPattern = Pattern.compile(jmodRegex);
charPatternRegex = "[ a-zA-Z]*foo[ a-zA-Z0-9]*bar[ a-z]*";
charPatternStrings = new String[] {"avaaafooddddddbariiii", "lorem ipsum dolor foo bar", "fpp brr lorem ipsum dolor foo bar %", "lorem ipsum dolor foo bar lorem ipsum dolor foo bar lorem ipsum dolor foo bar /"};
charPattern = Pattern.compile(charPatternRegex);
}
@Benchmark
public long longStringGraphemeMatches() {
return graphemePattern.matcher(flagsString.repeat(3)).results().count();
}
@Benchmark
public int splitFlags() {
return graphemeBoundaryPattern.split(flagsString).length;
}
@Benchmark
public boolean canonicalJmodMatch() {
return jmodCanonicalPattern.matcher(fileTestString).matches();
}
@Benchmark
public boolean normalJmodMatch() {
return jmodPattern.matcher(fileTestString).matches();
}
@Benchmark
public boolean charPatternMatch() {
return charPattern.matcher(charPatternStrings[0]).matches()
&& charPattern.matcher(charPatternStrings[1]).matches()
&& charPattern.matcher(charPatternStrings[2]).matches();
}
@Benchmark
public boolean charPatternMatchWithCompile() {
Pattern p = Pattern.compile(charPatternRegex);
return p.matcher(charPatternStrings[0]).matches()
&& p.matcher(charPatternStrings[1]).matches()
&& p.matcher(charPatternStrings[2]).matches();
}
@Benchmark
public Pattern charPatternCompile() {
return Pattern.compile(charPatternRegex);
}
}

View file

@ -0,0 +1,106 @@
/*
* Copyright 2020 Google Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.util.regex;
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
/**
* Abusing regexes for fun primality testing.
* Famous among regex enthusiasts.
* https://stackoverflow.com/q/3296050/625403
*
* Prime numbers exhibit O(N^2) performance with all variants, due to exhaustive
* backtracking.
*
* Powers of two exhibit O(N) performance with all variants, with reluctant
* quantifiers doing somewhat better.
*
* Here's a way to compare the per-input-char cost:
*
* (cd $(git rev-parse --show-toplevel) && for n in 16 17 256 257 4096 4099; do make test TEST='micro:java.util.regex.Primality' MICRO="FORK=1;WARMUP_ITER=1;ITER=4;OPTIONS=-opi $n -p n=$n" |& perl -ne 'print if /^Benchmark/ .. /^Finished running test/'; done)
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@Warmup(iterations = 2, time = 3, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 4, time = 3, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
public class Primality {
/** Number to be primality tested. */
@Param({"16", "17", /* "256", "257", */ "4096", "4099"})
// "64", "67", "1024", "1031", "16384", "16411"})
int n;
/** Unary numeral representation of int n */
public String unary;
// Patterns that match composite numbers represented as unary numerals.
public Pattern reluctant1;
public Pattern reluctant2;
public Pattern greedy1;
public Pattern greedy2;
Pattern compile(String regex) {
Pattern pat = Pattern.compile(regex);
// ad hoc correctness checking
boolean isPrime1 = ! pat.matcher(unary).matches();
boolean isPrime2 = java.math.BigInteger.valueOf(n).isProbablePrime(100);
if (isPrime1 != isPrime2) {
throw new AssertionError("regex=" + regex + ", n=" + n);
}
return pat;
}
@Setup(Level.Trial)
public void setup() {
unary = "1".repeat(n);
reluctant1 = compile("^(11+?)\\1+$");
reluctant2 = compile("^(1{2,}?)\\1+$");
greedy1 = compile("^(11+)\\1+$");
greedy2 = compile("^(1{2,})\\1+$");
}
@Benchmark
public boolean reluctant1() {
return reluctant1.matcher(unary).matches();
}
@Benchmark
public boolean reluctant2() {
return reluctant2.matcher(unary).matches();
}
@Benchmark
public boolean greedy1() {
return greedy1.matcher(unary).matches();
}
@Benchmark
public boolean greedy2() {
return greedy2.matcher(unary).matches();
}
}

View file

@ -0,0 +1,182 @@
/*
* Copyright 2020 Google Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.util.regex;
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Detecting trailing whitespace is a very common problem that many programmers
* have solved, but it's surprisingly difficult to avoid O(N^2) performance
* when the input contains a long run of consecutive whitespace. For
* example, attempts to trim such whitespace caused a Stack Exchange outage.
* https://stackstatus.net/post/147710624694/outage-postmortem-july-20-2016
*
* We use "[ \t]" as our definition of whitespace (easy, but not too easy!).
*
* The use of Matcher#find (instead of Matcher#matches) is very convenient, but
* introduces an implicit O(N) loop over the input, or alternatively, a
* non-possessive "^.*?" prefix in the regex. In order for the entire search
* operation to not be O(N^2), most of the regex match operations while
* scanning the input need to be O(1), which may require the use of less-obvious
* constructs like lookbehind. The use of possessive quantifiers in the regex
* itself is sadly **insufficient**.
*
* When the subpattern following a possessive quantifier is as cheap as the
* subpattern governed by the quantifier (e.g. \s++$), the possessive quantifier
* gives you at most 2x speedup, reducing two linear scans to one.
*
* An explicit loop with find() using two matchers and possessive quantifiers is
* the most efficient, since there is no backtracking. But that cannot work with
* simple APIs that take a regex as an argument, like grep(1) does.
*
* Here's a way to compare the per-char cost:
*
* (cd $(git rev-parse --show-toplevel) && for size in 16 256 4096; do make test TEST='micro:java.util.regex.Trim' MICRO="FORK=1;WARMUP_ITER=1;ITER=4;OPTIONS=-opi $size -p size=$size" |& perl -ne 'print if /^Benchmark/ .. /^Finished running test/'; done)
*
* some jdk17 numbers:
*
* Benchmark (size) Mode Cnt Score Error Units
* Trim.find_loop_two_matchers 1024 avgt 8 2.252 ? 0.013 ns/op
* Trim.find_loop_usePattern 1024 avgt 8 2.328 ? 0.116 ns/op
* Trim.lookBehind_find 1024 avgt 8 21.740 ? 0.040 ns/op
* Trim.possessive2_find 1024 avgt 8 7151.592 ? 17.860 ns/op
* Trim.possessive2_matches 1024 avgt 8 2.625 ? 0.008 ns/op
* Trim.possessive3_find 1024 avgt 8 28.532 ? 1.889 ns/op
* Trim.possessive_find 1024 avgt 8 3113.776 ? 9.996 ns/op
* Trim.simple_find 1024 avgt 8 4199.480 ? 13.410 ns/op
*
* TODO: why is simple_find faster than possessive_find, for size below 512 ?
*
* (cd $(git rev-parse --show-toplevel) && for size in 128 256 512 1024 2048; do make test TEST='micro:java.util.regex.Trim.\\\(simple_find\\\|possessive_find\\\)' MICRO="FORK=2;WARMUP_ITER=1;ITER=4;OPTIONS=-opi $size -p size=$size" |& perl -ne 'print if /^Benchmark/ .. /^Finished running test/'; done)
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(2)
@Warmup(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
@State(Scope.Thread)
public class Trim {
/** Run length of non-matching consecutive whitespace chars. */
@Param({"16", "256", "4096"})
int size;
/** String containing long interior run of whitespace */
public String noMatch;
public Pattern whitespaceRunPattern;
public Pattern eolPattern;
public Pattern simplePattern;
public Pattern possessivePattern;
public Pattern possessivePattern2;
public Pattern possessivePattern3;
public Pattern lookBehindPattern;
Pattern compile(String regex) {
Pattern pat = Pattern.compile(regex);
// ad hoc correctness checking
if (pat.matcher(noMatch).find()) {
throw new AssertionError("unexpected matching: " + regex);
}
return pat;
}
@Setup(Level.Trial)
public void setup() {
noMatch = "xx" + " \t".repeat(size) + "yy";
simplePattern = compile("[ \t]+$");
possessivePattern = compile("[ \t]++$");
possessivePattern2 = compile("(.*+[^ \t]|^)([ \t]++)$");
possessivePattern3 = compile("(?:[^ \t]|^)([ \t]++)$");
lookBehindPattern = compile("(?<![ \t])[ \t]++$");
whitespaceRunPattern = Pattern.compile("[ \t]++");
eolPattern = Pattern.compile("$", Pattern.MULTILINE);
// more ad hoc correctness checking
if (possessive2_matches()) throw new AssertionError();
if (find_loop_two_matchers()) throw new AssertionError();
if (find_loop_usePattern()) throw new AssertionError();
}
@Benchmark
public boolean simple_find() {
return simplePattern.matcher(noMatch).find();
}
@Benchmark
public boolean possessive_find() {
return possessivePattern.matcher(noMatch).find();
}
@Benchmark
public boolean possessive2_find() {
return possessivePattern2.matcher(noMatch).find();
}
@Benchmark
public boolean possessive2_matches() {
return possessivePattern2.matcher(noMatch).matches();
}
@Benchmark
public boolean possessive3_find() {
return possessivePattern3.matcher(noMatch).find();
}
@Benchmark
public boolean lookBehind_find() {
return lookBehindPattern.matcher(noMatch).find();
}
@Benchmark
public boolean find_loop_two_matchers() {
Matcher m = whitespaceRunPattern.matcher(noMatch);
int endOfString = m.regionEnd();
while (m.find()) {
if (eolPattern.matcher(noMatch).region(m.end(), endOfString).lookingAt())
return true;
}
return false;
}
@Benchmark
public boolean find_loop_usePattern() {
Matcher m = whitespaceRunPattern.matcher(noMatch);
int endOfString = m.regionEnd();
while (m.find()) {
m.region(m.end(), endOfString);
m.usePattern(eolPattern);
if (m.lookingAt())
return true;
m.usePattern(whitespaceRunPattern);
}
return false;
}
}