java-topology/defects/cmake-0005/unit/MergeOptionsAlgorithm.java
russell@unturf.com fb1ff685c1 cmake+mpd: 5-MOAD scan; 3 new CWE-407 defects, MPD CLEAN all 5 MOADs
cmake-0005: cmQtAutoGen MergeOptions std::find over baseOpts in newOpts loop, O(N*M), 31.5x at N=M=50
cmake-0006: cmVisualStudio10TargetGenerator FinishWritingSource writtenSettings O(S^2), 15.3x at S=30
cmake-0007: cmGeneratorExpressionNode TargetRuntimeDllDirsNode dllDirs O(D^2), 10.3x at D=100

MPD: all 5 MOADs CLEAN; updated CLEAN.md with MOAD-0002 through MOAD-0005 analysis.
Unit tests: 3/3 PASS.
2026-03-31 22:31:15 -04:00

126 lines
4.8 KiB
Java

import java.util.*;
/**
* Unit test for cmake-0005: MergeOptions O(N*M) -> O(N) via hash set.
*
* Models cmQtAutoGen::MergeOptions() dedup behaviour:
* - options present in baseOpts are updated in place (not re-added)
* - options absent from baseOpts are appended as extraOpts
*
* Defect: std::find over baseOpts (O(M)) inside loop over newOpts (O(N)) = O(N*M).
* Fix: unordered_set for membership check = O(1), total O(N).
*/
public class MergeOptionsAlgorithm {
// --- Defective implementation (O(N*M)) ---
static List<String> mergeOptionsDefective(List<String> baseOpts, List<String> newOpts) {
List<String> base = new ArrayList<>(baseOpts);
List<String> extra = new ArrayList<>();
for (String newOpt : newOpts) {
int idx = base.indexOf(newOpt); // O(M) linear scan -- the defect
if (idx >= 0) {
// option already present, update value-option successor if any
// (simplified: just mark as seen)
} else {
extra.add(newOpt);
}
}
base.addAll(extra);
return base;
}
// --- Fixed implementation (O(N)) ---
static List<String> mergeOptionsFixed(List<String> baseOpts, List<String> newOpts) {
List<String> base = new ArrayList<>(baseOpts);
Set<String> baseSet = new HashSet<>(baseOpts); // O(M) build, O(1) lookup
List<String> extra = new ArrayList<>();
for (String newOpt : newOpts) {
if (!baseSet.contains(newOpt)) {
extra.add(newOpt);
}
}
base.addAll(extra);
return base;
}
// --- Correctness test ---
static void assertEq(List<String> a, List<String> b, String msg) {
if (!a.equals(b)) throw new AssertionError(msg + ": " + a + " != " + b);
}
static void testCorrectness() {
List<String> base = Arrays.asList("-fPIC", "-O2", "-Wall");
List<String> newOpts = Arrays.asList("-O2", "-Wextra", "-fPIC");
List<String> defectResult = mergeOptionsDefective(base, newOpts);
List<String> fixedResult = mergeOptionsFixed(base, newOpts);
// Both should keep original base options and only add truly new ones
// -Wextra is new; -O2 and -fPIC already exist
List<String> expected = Arrays.asList("-fPIC", "-O2", "-Wall", "-Wextra");
assertEq(defectResult, expected, "defective correctness");
assertEq(fixedResult, expected, "fixed correctness");
System.out.println("PASS testCorrectness");
}
static void testEmptyNewOpts() {
List<String> base = Arrays.asList("-fPIC", "-O2");
List<String> newOpts = Collections.emptyList();
List<String> expected = Arrays.asList("-fPIC", "-O2");
assertEq(mergeOptionsFixed(base, newOpts), expected, "empty newOpts");
System.out.println("PASS testEmptyNewOpts");
}
static void testAllNew() {
List<String> base = Arrays.asList("-fPIC");
List<String> newOpts = Arrays.asList("-O2", "-Wall");
List<String> result = mergeOptionsFixed(base, newOpts);
if (!result.contains("-O2") || !result.contains("-Wall")) {
throw new AssertionError("all-new options missing: " + result);
}
System.out.println("PASS testAllNew");
}
// --- Performance benchmark ---
static long benchmarkOps(int baseSize, int newSize) {
List<String> base = new ArrayList<>();
for (int i = 0; i < baseSize; i++) base.add("-opt" + i);
List<String> newOpts = new ArrayList<>();
// Half overlap, half new
for (int i = 0; i < newSize / 2; i++) newOpts.add("-opt" + i);
for (int i = 0; i < newSize / 2; i++) newOpts.add("-new" + i);
// Defective: count linear scan operations
long defectiveOps = 0;
for (String newOpt : newOpts) {
for (String b : base) { // simulates indexOf
defectiveOps++;
if (b.equals(newOpt)) break;
}
}
return defectiveOps;
}
static void testBenchmark() {
int BASE = 50;
int NEW = 50;
long defectiveOps = benchmarkOps(BASE, NEW);
// Fixed: O(N) = NEW hash lookups
long fixedOps = NEW;
double ratio = (double) defectiveOps / fixedOps;
System.out.printf("BENCH mergeOptions base=%d new=%d: defective=%d ops, fixed=%d ops, ratio=%.1fx%n",
BASE, NEW, defectiveOps, fixedOps, ratio);
if (ratio < 5.0) {
throw new AssertionError("Expected ratio >= 5x, got " + ratio);
}
System.out.println("PASS testBenchmark (ratio >= 5x confirmed)");
}
public static void main(String[] args) {
testCorrectness();
testEmptyNewOpts();
testAllNew();
testBenchmark();
System.out.println("ALL PASS cmake-0005 MergeOptions");
}
}