package unit; import java.util.*; /** * SpringFrameworkTest — CWE-407 benchmark for spring-framework defects. * * spring-framework-0001: CorsConfiguration.checkHeaders() O(R×A) * checkHeaders() does a nested loop: for each of R request headers, * it scans A allowedHeaders via equalsIgnoreCase(). Total O(R×A). * Fix: build a case-insensitive Set once O(A), then O(1) lookup * per request header → O(R+A) total. * * spring-framework-0002: AcceptHeaderLocaleResolver.findSupportedLocale() O(R×S) * For each of R Accept-Language locales, calls supportedLocales.contains(locale) * on an ArrayList — O(S) linear scan. Total O(R×S) per request. * Same defect in AcceptHeaderLocaleContextResolver (reactive). * Fix: use LinkedHashSet for O(1) full-locale match → O(R+S) total. * * spring-framework-0003: EncodedResourceResolver.contentCodings ArrayList O(A×C) * resolveResourceInternal() iterates acceptedCodings from the request header, * calling this.contentCodings.contains(acceptedCoding) on an ArrayList → O(C) * per accepted coding. Total O(A×C) per static resource request. * Fix: change contentCodings to LinkedHashSet → O(1) contains → O(A) total. */ public class SpringFrameworkTest { // ----------------------------------------------------------------------- // spring-framework-0001: CorsConfiguration.checkHeaders O(R×A) // ----------------------------------------------------------------------- /** Unpatched: inner for-loop scan of allowedHeaders for each request header. */ static long checkHeadersUnpatched(int numRequestHeaders, int numAllowedHeaders) { List requestHeaders = new ArrayList<>(numRequestHeaders); for (int i = 0; i < numRequestHeaders; i++) requestHeaders.add("x-custom-req-" + i); List allowedHeaders = new ArrayList<>(numAllowedHeaders); for (int i = 0; i < numAllowedHeaders; i++) allowedHeaders.add("x-allowed-" + i); long ops = 0; List result = new ArrayList<>(); for (String requestHeader : requestHeaders) { for (String allowedHeader : allowedHeaders) { ops++; // equalsIgnoreCase probe if (requestHeader.equalsIgnoreCase(allowedHeader)) { result.add(requestHeader); break; } } } return ops; } /** Patched: build lowercase Set once, O(1) lookup per request header. */ static long checkHeadersPatched(int numRequestHeaders, int numAllowedHeaders) { List requestHeaders = new ArrayList<>(numRequestHeaders); for (int i = 0; i < numRequestHeaders; i++) requestHeaders.add("x-custom-req-" + i); List allowedHeaders = new ArrayList<>(numAllowedHeaders); for (int i = 0; i < numAllowedHeaders; i++) allowedHeaders.add("x-allowed-" + i); long ops = 0; Set allowedSet = new LinkedHashSet<>(numAllowedHeaders * 2); for (String h : allowedHeaders) { allowedSet.add(h.toLowerCase(Locale.ROOT)); ops++; // set build: O(A) } List result = new ArrayList<>(); for (String requestHeader : requestHeaders) { ops++; // O(1) hash lookup if (allowedSet.contains(requestHeader.toLowerCase(Locale.ROOT))) { result.add(requestHeader); } } return ops; } // ----------------------------------------------------------------------- // spring-framework-0002: AcceptHeaderLocaleResolver O(R×S) // ----------------------------------------------------------------------- /** * Unpatched: ArrayList.contains() = O(S) linear scan per request locale. * Tests the primary hot path: full-locale exact match via contains(). * Uses country-code locales so there is no language-only fallback hit. */ static long resolveLocaleUnpatched(int numRequestLocales, int numSupportedLocales) { // Request locales: zh-XX0, zh-XX1, ... (no match with supported set) List requestLocales = new ArrayList<>(numRequestLocales); for (int i = 0; i < numRequestLocales; i++) { requestLocales.add(new Locale("zh", "XX" + i)); } // Supported locales: en-ZZ0, en-ZZ1, ... (different language, no overlap) List supportedLocales = new ArrayList<>(numSupportedLocales); for (int i = 0; i < numSupportedLocales; i++) { supportedLocales.add(new Locale("en", "ZZ" + i)); } long ops = 0; for (Locale locale : requestLocales) { // Simulates ArrayList.contains(locale) — O(S) scan (no early exit, no match) for (Locale s : supportedLocales) { ops++; if (s.equals(locale)) break; } } return ops; } /** * Patched: LinkedHashSet.contains() = O(1) per request locale. * Build the set once O(S), then O(1) per request locale. */ static long resolveLocalePatched(int numRequestLocales, int numSupportedLocales) { List requestLocales = new ArrayList<>(numRequestLocales); for (int i = 0; i < numRequestLocales; i++) { requestLocales.add(new Locale("zh", "XX" + i)); } List supportedLocalesList = new ArrayList<>(numSupportedLocales); for (int i = 0; i < numSupportedLocales; i++) { supportedLocalesList.add(new Locale("en", "ZZ" + i)); } long ops = 0; // Build set once — O(S) Set supportedSet = new LinkedHashSet<>(supportedLocalesList); ops += numSupportedLocales; for (Locale locale : requestLocales) { ops++; // O(1) set lookup boolean found = supportedSet.contains(locale); } return ops; } // ----------------------------------------------------------------------- // spring-framework-0003: EncodedResourceResolver.contentCodings O(A×C) // ----------------------------------------------------------------------- /** Unpatched: ArrayList.contains() = O(C) scan per accepted-encoding token. */ static long encodedResolverUnpatched(int numAcceptedCodings, int numContentCodings) { List acceptedCodings = new ArrayList<>(numAcceptedCodings); for (int i = 0; i < numAcceptedCodings; i++) acceptedCodings.add("enc-acc-" + i); List contentCodings = new ArrayList<>(numContentCodings); for (int i = 0; i < numContentCodings; i++) contentCodings.add("enc-cfg-" + i); long ops = 0; for (String accepted : acceptedCodings) { // Simulates ArrayList.contains(accepted) — O(C) scan for (String supported : contentCodings) { ops++; if (supported.equals(accepted)) break; } } return ops; } /** Patched: LinkedHashSet.contains() = O(1) per accepted-encoding token. */ static long encodedResolverPatched(int numAcceptedCodings, int numContentCodings) { List acceptedCodings = new ArrayList<>(numAcceptedCodings); for (int i = 0; i < numAcceptedCodings; i++) acceptedCodings.add("enc-acc-" + i); Set contentCodings = new LinkedHashSet<>(numContentCodings * 2); for (int i = 0; i < numContentCodings; i++) contentCodings.add("enc-cfg-" + i); long ops = 0; for (String accepted : acceptedCodings) { ops++; // O(1) hash lookup boolean found = contentCodings.contains(accepted); } return ops; } // ----------------------------------------------------------------------- // main // ----------------------------------------------------------------------- public static void main(String[] args) { int pass = 0, total = 0; // spring-framework-0001: R=20, A=50 — worst case no match (all custom headers differ) { total++; int R = 20, A = 50; long slow = checkHeadersUnpatched(R, A); long fast = checkHeadersPatched(R, A); double ratio = (double) slow / Math.max(fast, 1); boolean ok = ratio > 10.0 && slow >= (long) R * A; if (ok) pass++; System.out.printf(" %s spring-framework-0001: checkHeaders ArrayList O(R×A) → Set O(R+A)" + " R=%d A=%d slow=%,d fast=%,d ratio=%.0fx%n", ok ? "PASS" : "FAIL", R, A, slow, fast, ratio); if (!ok) { System.err.printf(" FAIL spring-framework-0001: expected ratio>10x and slow>=%d, got ratio=%.1f slow=%d%n", (long) R * A, ratio, slow); } } // spring-framework-0002: R=15, S=20 — worst case no match { total++; int R = 15, S = 20; long slow = resolveLocaleUnpatched(R, S); long fast = resolveLocalePatched(R, S); double ratio = (double) slow / Math.max(fast, 1); // Unpatched worst case: R×S full scans; patched: S+R ops boolean ok = ratio > 5.0 && slow >= (long) R * S; if (ok) pass++; System.out.printf(" %s spring-framework-0002: localeResolver ArrayList O(R×S) → Set O(R+S)" + " R=%d S=%d slow=%,d fast=%,d ratio=%.0fx%n", ok ? "PASS" : "FAIL", R, S, slow, fast, ratio); if (!ok) { System.err.printf(" FAIL spring-framework-0002: expected ratio>5x and slow>=%d, got ratio=%.1f slow=%d%n", (long) R * S, ratio, slow); } } // spring-framework-0003: A=10 accepted codings (exaggerated), C=8 configured codings { total++; int A = 10, C = 8; long slow = encodedResolverUnpatched(A, C); long fast = encodedResolverPatched(A, C); double ratio = (double) slow / Math.max(fast, 1); // Unpatched worst case: A×C; patched: A ops boolean ok = ratio > 3.0 && slow >= (long) A * C; if (ok) pass++; System.out.printf(" %s spring-framework-0003: encodedResolver ArrayList O(A×C) → Set O(A)" + " A=%d C=%d slow=%,d fast=%,d ratio=%.0fx%n", ok ? "PASS" : "FAIL", A, C, slow, fast, ratio); if (!ok) { System.err.printf(" FAIL spring-framework-0003: expected ratio>3x and slow>=%d, got ratio=%.1f slow=%d%n", (long) A * C, ratio, slow); } } System.out.println(); System.out.printf("%d/%d PASS%n", pass, total); if (pass < total) { System.out.println("FAIL"); System.exit(1); } System.out.println("ALL PASS"); } }