import java.util.*; /** * CWE-407 simulation tests for Vim defects. * * vim-0001: ins_compl_add() O(N^2) duplicate completion check * vim-0002: sign_placelist() → buf_addsign() O(N^2) sign placement */ public class VimTest { // ======================================================================== // vim-0001: ins_compl_add duplicate check — linked-list scan vs hash set // ======================================================================== /** * DEFECTIVE: O(N^2) — each insertion scans the full list for duplicates. */ static int complAddDefective(String[] candidates) { List matches = new ArrayList<>(); int ops = 0; for (String candidate : candidates) { // Linear scan for duplicate — mirrors Vim's do { STRNCMP } while loop boolean found = false; for (String existing : matches) { ops++; if (existing.equals(candidate)) { found = true; break; } } if (!found) { matches.add(candidate); } } return ops; } /** * PATCHED: O(N) — hash set for O(1) amortised duplicate detection. */ static int complAddPatched(String[] candidates) { Set seen = new HashSet<>(); List matches = new ArrayList<>(); int ops = 0; for (String candidate : candidates) { ops++; // hash lookup if (seen.add(candidate)) { matches.add(candidate); } } return ops; } static void testVim0001() { System.out.println("=== vim-0001: ins_compl_add duplicate check ==="); // All unique candidates — worst case for duplicate scan int N = 1000; String[] candidates = new String[N]; for (int i = 0; i < N; i++) { candidates[i] = "completion_candidate_" + i; } int opsDefective = complAddDefective(candidates); int opsPatched = complAddPatched(candidates); double ratio = (double) opsDefective / opsPatched; System.out.printf(" N=%d candidates (all unique)%n", N); System.out.printf(" Defective ops: %,d%n", opsDefective); System.out.printf(" Patched ops: %,d%n", opsPatched); System.out.printf(" Ratio: %.1fx%n", ratio); // Defective: sum(0..N-1) = N*(N-1)/2 = 499,500 // Patched: N = 1,000 assert opsDefective >= N * (N - 1) / 2 : "Defective should be O(N^2)"; assert opsPatched == N : "Patched should be O(N)"; assert ratio > 100 : "Ratio should exceed 100x, got " + ratio; System.out.println(" PASS"); } // ======================================================================== // vim-0002: sign_placelist O(N^2) — linear walk per sign placement // ======================================================================== /** Simulates a sign entry in a buffer's sign linked list. */ static class SignEntry { int id; int lnum; String group; SignEntry next; SignEntry(int id, int lnum, String group) { this.id = id; this.lnum = lnum; this.group = group; } } /** * DEFECTIVE: O(N^2) — each sign placement walks from head of list. * Mirrors buf_addsign() walking FOR_ALL_SIGNS_IN_BUF for each placement. */ static int signPlaceDefective(int[] lineNumbers) { SignEntry head = null; int ops = 0; for (int i = 0; i < lineNumbers.length; i++) { int lnum = lineNumbers[i]; int id = i + 1; // Walk from head to find insertion point (sorted by lnum) SignEntry prev = null; SignEntry curr = head; while (curr != null) { ops++; if (lnum < curr.lnum) break; prev = curr; curr = curr.next; } SignEntry newSign = new SignEntry(id, lnum, "default"); newSign.next = curr; if (prev == null) head = newSign; else prev.next = newSign; } return ops; } /** * PATCHED: O(N log N) — sort input, then single-pass insertion with cursor. * Mirrors the proposed fix: sort by line number, advance cursor forward. */ static int signPlacePatched(int[] lineNumbers) { // Sort input by line number int[] sorted = lineNumbers.clone(); Arrays.sort(sorted); SignEntry head = null; SignEntry tail = null; int ops = 0; for (int i = 0; i < sorted.length; i++) { ops++; // single step: append at tail (sorted order) SignEntry newSign = new SignEntry(i + 1, sorted[i], "default"); if (tail == null) { head = newSign; tail = newSign; } else { tail.next = newSign; tail = newSign; } } return ops; } static void testVim0002() { System.out.println("=== vim-0002: sign_placelist linear walk ==="); int N = 500; // Signs placed on sequential lines (sorted input = worst case for defective) int[] lines = new int[N]; for (int i = 0; i < N; i++) { lines[i] = i + 1; } int opsDefective = signPlaceDefective(lines); int opsPatched = signPlacePatched(lines); double ratio = (double) opsDefective / opsPatched; System.out.printf(" N=%d signs%n", N); System.out.printf(" Defective ops: %,d%n", opsDefective); System.out.printf(" Patched ops: %,d%n", opsPatched); System.out.printf(" Ratio: %.1fx%n", ratio); // Defective: sum(0..N-1) = N*(N-1)/2 = 124,750 // Patched: N = 500 assert opsDefective >= N * (N - 1) / 2 : "Defective should be O(N^2)"; assert opsPatched == N : "Patched should be O(N)"; assert ratio > 100 : "Ratio should exceed 100x, got " + ratio; System.out.println(" PASS"); } // ======================================================================== // Main // ======================================================================== public static void main(String[] args) { testVim0001(); testVim0002(); System.out.println("\nAll Vim tests PASSED."); } }