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 for O(1) membership check. */ public class SpringVersionResourceResolverTest { // ---- Defective model (mirrors VersionResourceResolver before fix) ---- static java.util.List addFixedVersionStrategyDefective(String version, String... pathPatterns) { java.util.List patternsList = java.util.Arrays.asList(pathPatterns); // plain List — O(N) contains java.util.List 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 addFixedVersionStrategyFixed(String version, String... pathPatterns) { java.util.Set patternsSet = new java.util.HashSet<>(java.util.Arrays.asList(pathPatterns)); java.util.List 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 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 defective = addFixedVersionStrategyDefective("1.5", patterns); java.util.List 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)"); } }