package unit; import java.util.*; /** * Unit test for lldb-0001: SerializedBreakpointMatchesNames O(B×N²) vector scan. * * Models the defect: for each breakpoint in a serialized session file, check each * of the breakpoint's names against a filter list using linear vector scan. * Fix: convert the filter list to a HashSet before the loop. */ public class LldbSerializedBpNamesAlgorithm { // --- SLOW: llvm::is_contained on vector (models Breakpoint.cpp:247) --- static class SlowResult { long ops; int matched; SlowResult(long ops, int matched) { this.ops = ops; this.matched = matched; } } /** * O(B * N_bp * F): for each breakpoint, for each name on the breakpoint, * linearly scan the filter list. * * @param bpNames list of breakpoints; each element is the list of names on that bp * @param filter filter list as ArrayList (models std::vector) */ static SlowResult slowMatchesNames(List> bpNames, List filter) { long ops = 0; int matched = 0; for (List bpNameList : bpNames) { // O(B) boolean found = false; for (String name : bpNameList) { // O(N_bp) for (String f : filter) { // O(F) - llvm::is_contained ops++; if (f.equals(name)) { found = true; break; } } if (found) break; } if (found) matched++; } return new SlowResult(ops, matched); } // --- FAST: convert filter to HashSet once, then O(1) lookup --- static class FastResult { long ops; int matched; FastResult(long ops, int matched) { this.ops = ops; this.matched = matched; } } /** * O(B * N_bp): convert filter to HashSet once, then O(1) per name check. */ static FastResult fastMatchesNames(List> bpNames, List filter) { Set filterSet = new HashSet<>(filter); // O(F) once long ops = 0; int matched = 0; for (List bpNameList : bpNames) { // O(B) boolean found = false; for (String name : bpNameList) { // O(N_bp) ops++; if (filterSet.contains(name)) { // O(1) found = true; break; } } if (found) matched++; } return new FastResult(ops, matched); } // --- Test cases --- static boolean runTest(String label, int B, int N_bp, int F, double minRatio) { // Build filter list: F names like "bp-filter-NNN" List filter = new ArrayList<>(); for (int i = 0; i < F; i++) { filter.add("bp-filter-" + i); } // Build breakpoint name lists: each bp has N_bp names // Every other bp matches (its first name is in the filter) List> bpNames = new ArrayList<>(); for (int b = 0; b < B; b++) { List names = new ArrayList<>(); for (int n = 0; n < N_bp; n++) { if (b % 2 == 0 && n == N_bp - 1) { // match: last name in list is a filter entry (worst case - full scan) names.add("bp-filter-" + (b % F)); } else { names.add("bp-name-" + b + "-" + n); } } bpNames.add(names); } SlowResult slow = slowMatchesNames(bpNames, filter); FastResult fast = fastMatchesNames(bpNames, filter); // Verify correctness if (slow.matched != fast.matched) { System.out.printf(" FAIL %s: match count mismatch slow=%d fast=%d%n", label, slow.matched, fast.matched); return false; } double ratio = (double) slow.ops / fast.ops; boolean pass = ratio >= minRatio; System.out.printf(" %s %s: B=%d N_bp=%d F=%d | SLOW=%d ops FAST=%d ops ratio=%.1fx%n", pass ? "PASS" : "FAIL", label, B, N_bp, F, slow.ops, fast.ops, ratio); return pass; } public static void main(String[] args) { int pass = 0, total = 0; // Test 1: B=100 breakpoints, N_bp=10 names each, F=20 filter names total++; if (runTest("B=100,Nbp=10,F=20", 100, 10, 20, 5.0)) pass++; // Test 2: B=200, N_bp=15, F=50 - more filter entries amplifies ratio total++; if (runTest("B=200,Nbp=15,F=50", 200, 15, 50, 10.0)) pass++; // Test 3: B=500, N_bp=20, F=100 - representative large session restore total++; if (runTest("B=500,Nbp=20,F=100", 500, 20, 100, 20.0)) pass++; // Test 4: B=50, N_bp=5, F=10 - small case, ratio still >= 5x total++; if (runTest("B=50,Nbp=5,F=10", 50, 5, 10, 5.0)) pass++; System.out.printf("%n%d/%d PASS%n", pass, total); if (pass < total) System.exit(1); } }