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 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 target, String[] source) { long ops = 0; Set 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 slowTarget = new ArrayList<>(); List 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 slowTarget = new ArrayList<>(); List 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 slowTarget = new ArrayList<>(); List 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 slowT = new ArrayList<>(Arrays.asList("a", "b")); List fastT = new ArrayList<>(Arrays.asList("a", "b")); String[] src = {"b", "c", "d"}; slowExtendDedup(slowT, src); fastExtendDedup(fastT, src); List 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); } }