174 lines
6.8 KiB
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);
|
|
}
|
|
}
|