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.
This commit is contained in:
parent
ae2eaf3be9
commit
178d9c03db
7 changed files with 496 additions and 0 deletions
188
defects/vim/unit/VimTest.java
Normal file
188
defects/vim/unit/VimTest.java
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
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.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue