package unit; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.TreeSet; /** * love2d-0002: Window::getFullscreenSizes O(n²) dedup — CWE-407 * love2d-0003: Filesystem::allowMountingForPath O(n) per call — CWE-407 * * Two defects in one test class, each with slow/fast variants. */ public class Love2dWindowSizeDedupTest { // Represents {width, height} — SDL DisplayMode size key static final class WindowSize { final int width, height; WindowSize(int w, int h) { this.width = w; this.height = h; } @Override public boolean equals(Object o) { if (!(o instanceof WindowSize)) return false; WindowSize ws = (WindowSize) o; return width == ws.width && height == ws.height; } @Override public int hashCode() { return width * 31 + height; } @Override public String toString() { return width + "x" + height; } } // --- love2d-0002 --- /** * Slow: mirrors love2d Window::getFullscreenSizes(). * Builds a fake SDL mode list with duplicates and deduplicates with std::find. * Returns total comparison count. */ static long slowGetFullscreenSizes(List modes) { List sizes = new ArrayList<>(); long comparisons = 0; for (WindowSize w : modes) { // std::find: iterate over existing sizes — O(n) boolean found = false; for (WindowSize s : sizes) { comparisons++; if (s.equals(w)) { found = true; break; } } if (!found) sizes.add(w); } return comparisons; } /** * Fast: set-based dedup — O(1) amortized per lookup. * Returns number of set.contains() calls (each O(1)). */ static long fastGetFullscreenSizes(List modes) { HashSet seen = new HashSet<>(); // FIX love2d-0002 List sizes = new ArrayList<>(); long probes = 0; for (WindowSize w : modes) { probes++; // O(1) hash lookup if (seen.add(w)) sizes.add(w); } return probes; } // --- love2d-0003 --- /** * Slow: mirrors Filesystem::allowMountingForPath — O(n) list scan per add. * Returns total comparison count across all N insertions. */ static long slowAllowMountPaths(List paths) { List allowedMountPaths = new ArrayList<>(); long comparisons = 0; for (String path : paths) { // std::find on vector: O(n) — CWE-407 boolean found = false; for (String existing : allowedMountPaths) { comparisons++; if (existing.equals(path)) { found = true; break; } } if (!found) allowedMountPaths.add(path); } return comparisons; } /** * Fast: unordered_set for O(1) membership — FIX love2d-0003. * Returns number of set.add() calls (each O(1) amortized). */ static long fastAllowMountPaths(List paths) { HashSet allowedMountPaths = new HashSet<>(); // FIX love2d-0003 long probes = 0; for (String path : paths) { probes++; // O(1) amortized allowedMountPaths.add(path); } return probes; } // Helper: build display mode list with D duplicates per unique size static List buildModeList(int uniqueCount, int dupsPerSize) { List modes = new ArrayList<>(); for (int i = 0; i < uniqueCount; i++) { int w = 640 + i * 4; int h = 480 + i * 3; for (int d = 0; d < dupsPerSize; d++) { modes.add(new WindowSize(w, h)); } } return modes; } public static void main(String[] args) { int passed = 0; int total = 0; // --- love2d-0002 tests --- // Test 1: small mode list (50 unique × 3 dup = 150 entries) { total++; List modes = buildModeList(50, 3); long slow = slowGetFullscreenSizes(modes); long fast = fastGetFullscreenSizes(modes); boolean ok = slow > fast * 10; System.out.printf("[0002] Test 1 (50 unique × 3 dup): slow=%d, fast=%d, ratio=%.1fx — %s%n", slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Test 2: large mode list (200 unique × 5 dup = 1000 entries) { total++; List modes = buildModeList(200, 5); long slow = slowGetFullscreenSizes(modes); long fast = fastGetFullscreenSizes(modes); boolean ok = slow > fast * 100; System.out.printf("[0002] Test 2 (200 unique × 5 dup): slow=%d, fast=%d, ratio=%.1fx — %s%n", slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Test 3: worst case — all same size (100% duplicates) { total++; List modes = new ArrayList<>(); for (int i = 0; i < 500; i++) modes.add(new WindowSize(1920, 1080)); long slow = slowGetFullscreenSizes(modes); long fast = fastGetFullscreenSizes(modes); // Slow: each of 500 entries scans [0..1) already-found sizes (always 1 entry) // Actually worst case is when unique sizes accumulate — but this is degenerate // slow = 0+1+1+...+1 = 499; fast = 500 probes. Different pattern. // For fully-duplicate list slow scans the 1-element seen list each time = 499 scans // fast = 500 probes each O(1). Not a huge ratio but slow still > fast. boolean ok = fast == 500 && slow == 499; System.out.printf("[0002] Test 3 (all same): slow=%d, fast=%d — %s%n", slow, fast, ok ? "PASS" : "FAIL"); if (ok) passed++; } // --- love2d-0003 tests --- // Test 4: 100 unique paths + 50 repeated { total++; List paths = new ArrayList<>(); for (int i = 0; i < 100; i++) paths.add("/game/mod/pack" + i + ".zip"); for (int i = 0; i < 50; i++) paths.add("/game/mod/pack" + i + ".zip"); // duplicates long slow = slowAllowMountPaths(paths); long fast = fastAllowMountPaths(paths); boolean ok = slow > fast * 20; System.out.printf("[0003] Test 4 (100 unique+50 dup paths): slow=%d, fast=%d, ratio=%.1fx — %s%n", slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL"); if (ok) passed++; } // Test 5: 500 unique paths — growing O(n²) vs O(n) { total++; List paths = new ArrayList<>(); for (int i = 0; i < 500; i++) paths.add("/usr/share/love/mods/pack" + i); long slow = slowAllowMountPaths(paths); long fast = fastAllowMountPaths(paths); boolean ok = slow > fast * 100; System.out.printf("[0003] Test 5 (500 unique paths): slow=%d, fast=%d, ratio=%.1fx — %s%n", slow, fast, (double) slow / fast, ok ? "PASS" : "FAIL"); if (ok) passed++; } System.out.printf("%n%d/%d PASS%n", passed, total); if (passed != total) System.exit(1); } }