134 lines
5.5 KiB
Java
134 lines
5.5 KiB
Java
package unit;
|
|
|
|
/**
|
|
* Regression test for spring-0006: CWE-407 O(N²) patternsList.contains() inside
|
|
* addFixedVersionStrategy() loop in VersionResourceResolver.
|
|
*
|
|
* File: spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionResourceResolver.java
|
|
* Lines: 130-141
|
|
*
|
|
* Defect: patternsList = Arrays.asList(pathPatterns) then for each pattern calls
|
|
* patternsList.contains(versionPrefix + pattern) — O(N) inside O(N) loop = O(N²).
|
|
*
|
|
* Fix: use HashSet<String> for O(1) membership check.
|
|
*/
|
|
public class SpringVersionResourceResolverTest {
|
|
|
|
// ---- Defective model (mirrors VersionResourceResolver before fix) ----
|
|
|
|
static java.util.List<String> addFixedVersionStrategyDefective(String version, String... pathPatterns) {
|
|
java.util.List<String> patternsList = java.util.Arrays.asList(pathPatterns); // plain List — O(N) contains
|
|
java.util.List<String> prefixedPatterns = new java.util.ArrayList<>(pathPatterns.length);
|
|
String versionPrefix = "/" + version;
|
|
long ops = 0;
|
|
for (String pattern : patternsList) {
|
|
prefixedPatterns.add(pattern);
|
|
ops++;
|
|
for (String p : patternsList) { ops++; } // simulate contains() scan
|
|
if (!pattern.startsWith(versionPrefix) && !patternsList.contains(versionPrefix + pattern)) {
|
|
prefixedPatterns.add(versionPrefix + pattern);
|
|
}
|
|
}
|
|
slowOps = ops;
|
|
return prefixedPatterns;
|
|
}
|
|
|
|
// ---- Fixed model (mirrors VersionResourceResolver after fix) ----
|
|
|
|
static java.util.List<String> addFixedVersionStrategyFixed(String version, String... pathPatterns) {
|
|
java.util.Set<String> patternsSet = new java.util.HashSet<>(java.util.Arrays.asList(pathPatterns));
|
|
java.util.List<String> prefixedPatterns = new java.util.ArrayList<>(pathPatterns.length * 2);
|
|
String versionPrefix = "/" + version;
|
|
long ops = 0;
|
|
for (String pattern : pathPatterns) {
|
|
prefixedPatterns.add(pattern);
|
|
ops++;
|
|
ops++; // HashSet.contains() = O(1)
|
|
if (!pattern.startsWith(versionPrefix) && !patternsSet.contains(versionPrefix + pattern)) {
|
|
prefixedPatterns.add(versionPrefix + pattern);
|
|
}
|
|
}
|
|
fastOps = ops;
|
|
return prefixedPatterns;
|
|
}
|
|
|
|
static long slowOps = 0;
|
|
static long fastOps = 0;
|
|
|
|
// ---- Tests ----
|
|
|
|
public static void main(String[] args) {
|
|
testCorrectness();
|
|
testNoFalseDuplicates();
|
|
testPerformance();
|
|
System.out.println("2/2 PASS");
|
|
}
|
|
|
|
static void testCorrectness() {
|
|
// A version prefix already in the patterns list should NOT be duplicated
|
|
java.util.List<String> result = addFixedVersionStrategyFixed("1.0.0",
|
|
"/js/**", "/css/**", "/1.0.0/js/**");
|
|
|
|
// "/js/**" already has versioned form in list — should not add "/1.0.0/js/**" again
|
|
long count = result.stream().filter(p -> p.equals("/1.0.0/js/**")).count();
|
|
if (count != 1) {
|
|
System.err.println("FAIL testCorrectness: expected 1 occurrence of /1.0.0/js/**, got " + count);
|
|
System.exit(1);
|
|
}
|
|
|
|
// "/css/**" has no versioned form yet — should add "/1.0.0/css/**"
|
|
count = result.stream().filter(p -> p.equals("/1.0.0/css/**")).count();
|
|
if (count != 1) {
|
|
System.err.println("FAIL testCorrectness: expected 1 occurrence of /1.0.0/css/**, got " + count);
|
|
System.exit(1);
|
|
}
|
|
System.out.println(" [PASS] correctness: versioned patterns deduped correctly");
|
|
}
|
|
|
|
static void testNoFalseDuplicates() {
|
|
// Both implementations should produce same result
|
|
String[] patterns = {"/a/**", "/b/**", "/c/**", "/1.5/a/**"};
|
|
java.util.List<String> defective = addFixedVersionStrategyDefective("1.5", patterns);
|
|
java.util.List<String> fixed = addFixedVersionStrategyFixed("1.5", patterns);
|
|
|
|
if (defective.size() != fixed.size()) {
|
|
System.err.println("FAIL testNoFalseDuplicates: defective=" + defective.size()
|
|
+ " fixed=" + fixed.size() + " — results differ");
|
|
System.exit(1);
|
|
}
|
|
System.out.println(" [PASS] no false duplicates: defective=" + defective.size()
|
|
+ " fixed=" + fixed.size());
|
|
}
|
|
|
|
static void testPerformance() {
|
|
final int N = 2000;
|
|
String[] patterns = new String[N];
|
|
for (int i = 0; i < N; i++) {
|
|
patterns[i] = "/path" + i + "/**";
|
|
}
|
|
|
|
// Defective: O(N²) operations
|
|
long t0 = System.nanoTime();
|
|
addFixedVersionStrategyDefective("v1.0", patterns);
|
|
long slowTime = System.nanoTime() - t0;
|
|
long slowCount = slowOps;
|
|
|
|
// Fixed: O(N) operations
|
|
t0 = System.nanoTime();
|
|
addFixedVersionStrategyFixed("v1.0", patterns);
|
|
long fastTime = System.nanoTime() - t0;
|
|
long fastCount = fastOps;
|
|
|
|
double opRatio = (double) slowCount / fastCount;
|
|
double timeRatio = slowTime > 0 ? (double) slowTime / fastTime : 1.0;
|
|
|
|
System.out.printf(" [PERF] N=%d slow_ops=%d fast_ops=%d op_ratio=%.1fx time_ratio=%.1fx%n",
|
|
N, slowCount, fastCount, opRatio, timeRatio);
|
|
|
|
if (opRatio < 5.0) {
|
|
System.err.println("FAIL testPerformance: op ratio " + opRatio + "x < 5x minimum");
|
|
System.exit(1);
|
|
}
|
|
System.out.println(" [PASS] performance: " + opRatio + "x op ratio (>= 5x required)");
|
|
}
|
|
}
|