java-topology/defects/vim/unit/VimTest.java
russell@unturf.com 178d9c03db scan: HandBrake + Emacs CWE-407 — both CLEAN
HandBrake: libhb uses array-backed lists bounded by media metadata
sizes (tracks, subtitles, chapters, presets). No user-controlled
quadratic growth path found.

Emacs: C core uses Fmemq/Fmember on small bounded lists (property
lists, error conditions, flags). Larger lists (features ~1000,
charsets ~200) only scanned in non-hot-path code. Elisp delete-dups
already has hash optimization for >100 elements.
2026-03-30 11:45:36 -04:00

188 lines
6.4 KiB
Java

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<String> 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<String> seen = new HashSet<>();
List<String> 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.");
}
}