java-topology/defects/systemd/unit/SystemdStrvExtendDedupTest.java

174 lines
6.8 KiB
Java

package unit;
import java.util.*;
/**
* CWE-407 unit test: systemd-0001
*
* strv_extend_strv() with filter_duplicates=true calls strv_contains() (O(N)
* linear scan) inside a loop over every element of the input array.
* Total cost: O(N^2).
*
* Fix: replace the growing-array scan with a HashSet for O(1) membership.
*/
public class SystemdStrvExtendDedupTest {
// -----------------------------------------------------------------------
// SLOW path: simulates strv_extend_strv with filter_duplicates
// Uses List.contains() (O(N)) inside the append loop — O(N^2) total.
// -----------------------------------------------------------------------
static long slowExtendDedup(List<String> target, String[] source) {
long ops = 0;
for (String s : source) {
// O(|target|) scan — strv_contains / strv_find equivalent
ops += target.size(); // count every comparison in the scan
if (!target.contains(s)) {
target.add(s);
}
}
return ops;
}
// -----------------------------------------------------------------------
// FAST path: dedup via HashSet — O(N) total.
// -----------------------------------------------------------------------
static long fastExtendDedup(List<String> target, String[] source) {
long ops = 0;
Set<String> seen = new HashSet<>(target);
for (String s : source) {
ops++; // one hash lookup
if (seen.add(s)) {
target.add(s);
}
}
return ops;
}
// -----------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------
static String[] makeUnique(int n) {
String[] a = new String[n];
for (int i = 0; i < n; i++) a[i] = "unit-" + i + ".service";
return a;
}
static String[] makeDuplicates(int n) {
// All the same string — worst case for dedup scan
String[] a = new String[n];
Arrays.fill(a, "duplicate.service");
return a;
}
// -----------------------------------------------------------------------
// Tests
// -----------------------------------------------------------------------
static int pass = 0;
static int fail = 0;
static void check(String name, boolean condition) {
if (condition) {
System.out.println("PASS: " + name);
pass++;
} else {
System.out.println("FAIL: " + name);
fail++;
}
}
static void testUniqueStrings(int n) {
// Unique strings: slow does N*(N-1)/2 comparisons, fast does N.
List<String> slowTarget = new ArrayList<>();
List<String> fastTarget = new ArrayList<>();
String[] source = makeUnique(n);
long slowOps = slowExtendDedup(slowTarget, source);
long fastOps = fastExtendDedup(fastTarget, source);
check("unique[N=" + n + "]: slow result size == fast result size",
slowTarget.size() == fastTarget.size());
check("unique[N=" + n + "]: result contains all source elements",
slowTarget.containsAll(Arrays.asList(source)));
double ratio = (double) slowOps / fastOps;
System.out.printf(" ops: slow=%d fast=%d ratio=%.1fx%n", slowOps, fastOps, ratio);
check("unique[N=" + n + "]: slowOps/fastOps >= 5x", ratio >= 5.0);
}
static void testAllDuplicates(int n) {
// Growing target: n elements already present, then n more unique elements appended.
// The slow path scans all n existing entries for each new element: O(N^2).
// The fast path does O(1) hash lookup per new element.
List<String> slowTarget = new ArrayList<>();
List<String> fastTarget = new ArrayList<>();
// Pre-fill target with n entries (these already exist, won't be added again)
for (int i = 0; i < n; i++) {
slowTarget.add("existing-" + i + ".service");
fastTarget.add("existing-" + i + ".service");
}
// Source: n new unique strings (none are duplicates of target, so all get added)
String[] source = new String[n];
for (int i = 0; i < n; i++) source[i] = "new-" + i + ".service";
long slowOps = slowExtendDedup(slowTarget, source);
long fastOps = fastExtendDedup(fastTarget, source);
check("growing-target[N=" + n + "]: both add all N new elements",
slowTarget.size() == 2 * n && fastTarget.size() == 2 * n);
check("growing-target[N=" + n + "]: slow and fast agree",
slowTarget.equals(fastTarget));
double ratio = (double) slowOps / fastOps;
System.out.printf(" ops: slow=%d fast=%d ratio=%.1fx%n", slowOps, fastOps, ratio);
check("growing-target[N=" + n + "]: slowOps/fastOps >= 5x", ratio >= 5.0);
}
static void testMixed(int n) {
// Half unique, half duplicate
List<String> slowTarget = new ArrayList<>();
List<String> fastTarget = new ArrayList<>();
String[] source = new String[n];
for (int i = 0; i < n; i++) {
source[i] = (i % 2 == 0) ? "even-" + i + ".service" : "odd.service";
}
long slowOps = slowExtendDedup(slowTarget, source);
long fastOps = fastExtendDedup(fastTarget, source);
check("mixed[N=" + n + "]: slow and fast agree on result",
slowTarget.equals(fastTarget));
double ratio = (double) slowOps / fastOps;
System.out.printf(" ops: slow=%d fast=%d ratio=%.1fx%n", slowOps, fastOps, ratio);
check("mixed[N=" + n + "]: slowOps/fastOps >= 5x", ratio >= 5.0);
}
static void testCorrectnessSmall() {
// Correctness: strv_extend_strv({"a","b"}, {"b","c","d"}, true) -> {"a","b","c","d"}
List<String> slowT = new ArrayList<>(Arrays.asList("a", "b"));
List<String> fastT = new ArrayList<>(Arrays.asList("a", "b"));
String[] src = {"b", "c", "d"};
slowExtendDedup(slowT, src);
fastExtendDedup(fastT, src);
List<String> expected = Arrays.asList("a", "b", "c", "d");
check("correctness: slow result", slowT.equals(expected));
check("correctness: fast result", fastT.equals(expected));
}
// -----------------------------------------------------------------------
// Main
// -----------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("=== systemd-0001: strv_extend_strv dedup O(N^2) → O(N) ===");
testCorrectnessSmall();
testUniqueStrings(500);
testAllDuplicates(500);
testMixed(500);
testUniqueStrings(1000);
System.out.println();
System.out.printf("%d/%d PASS%n", pass, pass + fail);
if (fail > 0) System.exit(1);
}
}