package unit; import java.util.ArrayList; import java.util.List; /** * Vlc0002SubtitleDeduplicateTest — CWE-407 vlc-0002 * * Models subtitles_Detect() .sub/.idx dedup in src/input/subtitles.c: * slow() = O(N²) nested scan of all slaves for each .sub entry (defect) * fast() = O(N) pre-filter .idx list, O(N) outer scan (patch) * * Assert: slowOps > fastOps * Nx at N=100 subtitle files. */ public class Vlc0002SubtitleDeduplicateTest { static long slowOps; static long fastOps; /** * Represents a subtitle slave entry (simplified). * uri format: "movie_name.ext" */ static class Slave { String uri; boolean rejected; Slave(String uri) { this.uri = uri; this.rejected = false; } } /** Returns the extension (lowercase) of a filename, or "" if none. */ static String getExt(String uri) { int dot = uri.lastIndexOf('.'); return (dot >= 0) ? uri.substring(dot + 1).toLowerCase() : ""; } /** Returns the base name (before last dot). */ static String getBase(String uri) { int dot = uri.lastIndexOf('.'); return (dot >= 0) ? uri.substring(0, dot) : uri; } /** * slow: O(N²) — models original subtitles.c nested loop. * For each .sub entry (outer), scan all slaves (inner) for matching .idx. */ static void deduplicateSlow(Slave[] slaves) { int n = slaves.length; for (int i = 0; i < n; i++) { if (slaves[i] == null || slaves[i].rejected) continue; String ext = getExt(slaves[i].uri); if (!ext.equals("sub")) continue; String subBase = getBase(slaves[i].uri); // Inner O(N) scan — the defect for (int j = 0; j < n; j++) { slowOps++; if (slaves[j] == null || slaves[j].rejected) continue; if (!getBase(slaves[j].uri).equals(subBase)) continue; if (getExt(slaves[j].uri).equals("idx")) { slaves[i].rejected = true; break; } } } } /** * fast: O(N) — models patch: pre-collect .idx bases, then scan .sub. */ static void deduplicateFast(Slave[] slaves) { int n = slaves.length; // Pre-collect .idx base names in O(N) List idxBases = new ArrayList<>(); for (int j = 0; j < n; j++) { fastOps++; if (slaves[j] == null || slaves[j].rejected) continue; if (getExt(slaves[j].uri).equals("idx")) { idxBases.add(getBase(slaves[j].uri)); } } // Scan .sub entries, check against pre-collected .idx bases in O(N_idx) for (int i = 0; i < n; i++) { if (slaves[i] == null || slaves[i].rejected) continue; String ext = getExt(slaves[i].uri); if (!ext.equals("sub")) continue; String subBase = getBase(slaves[i].uri); for (String idxBase : idxBases) { fastOps++; if (idxBase.equals(subBase)) { slaves[i].rejected = true; break; } } } } /** Build a test set: N subtitle files, half .sub, half .idx, all paired. */ static Slave[] buildSlaves(int n) { Slave[] slaves = new Slave[n]; for (int i = 0; i < n / 2; i++) { slaves[i * 2] = new Slave("movie_" + i + ".sub"); slaves[i * 2 + 1] = new Slave("movie_" + i + ".idx"); } return slaves; } static Slave[] cloneSlaves(Slave[] src) { Slave[] dst = new Slave[src.length]; for (int i = 0; i < src.length; i++) { dst[i] = (src[i] != null) ? new Slave(src[i].uri) : null; } return dst; } public static void main(String[] args) { final int NX = 5; final int N = 100; // 50 .sub + 50 .idx pairs Slave[] template = buildSlaves(N); // Warm up slowOps = 0; fastOps = 0; deduplicateSlow(cloneSlaves(template)); deduplicateFast(cloneSlaves(template)); // Measure slowOps = 0; fastOps = 0; final int RUNS = 100; Slave[] slowFinal = null, fastFinal = null; for (int r = 0; r < RUNS; r++) { Slave[] s = cloneSlaves(template); deduplicateSlow(s); if (r == RUNS - 1) slowFinal = s; Slave[] f = cloneSlaves(template); deduplicateFast(f); if (r == RUNS - 1) fastFinal = f; } // Count rejected in final run int slowRejected = 0, fastRejected = 0; for (Slave s : slowFinal) { if (s != null && s.rejected) slowRejected++; } for (Slave s : fastFinal) { if (s != null && s.rejected) fastRejected++; } boolean correctnessOk = (slowRejected == fastRejected && slowRejected == N / 2); boolean speedupOk = slowOps > fastOps * NX; System.out.printf("N=%d slaves (%d .sub + %d .idx pairs), RUNS=%d%n", N, N/2, N/2, RUNS); System.out.printf("slow (nested scan) ops: %d%n", slowOps); System.out.printf("fast (pre-filter) ops: %d%n", fastOps); System.out.printf("speedup ratio: %.1fx (required >%dx)%n", (double) slowOps / fastOps, NX); System.out.printf("rejected: slow=%d fast=%d (expected %d)%n", slowRejected, fastRejected, N / 2); int passed = 0, total = 2; if (correctnessOk) { System.out.printf("1/2 PASS correctness: both rejected %d .sub entries%n", N/2); passed++; } else { System.out.printf("1/2 FAIL correctness: slow=%d fast=%d expected=%d%n", slowRejected, fastRejected, N/2); } if (speedupOk) { System.out.printf("2/2 PASS speedup: %d > %d * %d%n", slowOps, fastOps, NX); passed++; } else { System.out.printf("2/2 FAIL speedup: %d not > %d * %d%n", slowOps, fastOps, NX); } System.out.printf("%d/%d PASS%n", passed, total); if (passed < total) System.exit(1); } }