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.
188 lines
6.4 KiB
Java
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.");
|
|
}
|
|
}
|