135 lines
4.8 KiB
Java
135 lines
4.8 KiB
Java
package unit;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* ElixirMixTopoSortAlgorithm — CWE-407 test for elixir-0001
|
|
*
|
|
* Models Mix.Dep.Converger.topological_sort/1:
|
|
* slow: Enum.map(apps, fn app -> Enum.find(deps, ...) end) — O(N²)
|
|
* fast: Map.new(deps, ...) then Enum.map(apps, dep_index[app]) — O(N)
|
|
*
|
|
* Test: for N deps, slow path does N*N list scans; fast path does N map lookups.
|
|
*/
|
|
public class ElixirMixTopoSortAlgorithm {
|
|
|
|
// Simulate a Mix.Dep struct — just an app name (atom) and an index
|
|
static class MixDep {
|
|
final String app;
|
|
MixDep(String app) { this.app = app; }
|
|
}
|
|
|
|
// --- SLOW: O(N²) ---
|
|
// Enum.map(apps, fn app -> Enum.find(deps, fn dep -> dep.app == app end) end)
|
|
static class SlowTopoSort {
|
|
long comparisons = 0;
|
|
|
|
List<MixDep> sort(List<String> apps, List<MixDep> deps) {
|
|
List<MixDep> result = new ArrayList<>(apps.size());
|
|
for (String app : apps) {
|
|
// Enum.find — linear scan
|
|
MixDep found = null;
|
|
for (MixDep dep : deps) {
|
|
comparisons++;
|
|
if (dep.app.equals(app)) {
|
|
found = dep;
|
|
break;
|
|
}
|
|
}
|
|
result.add(found);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// --- FAST: O(N) ---
|
|
// dep_index = Map.new(deps, fn dep -> {dep.app, dep} end)
|
|
// Enum.map(apps, fn app -> dep_index[app] end)
|
|
static class FastTopoSort {
|
|
long lookups = 0;
|
|
|
|
List<MixDep> sort(List<String> apps, List<MixDep> deps) {
|
|
// Build index: O(N)
|
|
Map<String, MixDep> index = new HashMap<>(deps.size() * 2);
|
|
for (MixDep dep : deps) {
|
|
index.put(dep.app, dep);
|
|
}
|
|
// Map lookup: O(1) each
|
|
List<MixDep> result = new ArrayList<>(apps.size());
|
|
for (String app : apps) {
|
|
lookups++;
|
|
result.add(index.get(app));
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
static List<MixDep> makeDeps(int n) {
|
|
List<MixDep> deps = new ArrayList<>(n);
|
|
for (int i = 0; i < n; i++) deps.add(new MixDep("dep_" + i));
|
|
return deps;
|
|
}
|
|
|
|
static List<String> makeApps(List<MixDep> deps) {
|
|
// Simulate topsort returning atoms in some order
|
|
List<String> apps = new ArrayList<>(deps.size());
|
|
for (MixDep d : deps) apps.add(d.app);
|
|
Collections.shuffle(apps, new Random(42));
|
|
return apps;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
int[] sizes = {50, 100, 200, 400};
|
|
System.out.println("ElixirMixTopoSortAlgorithm — elixir-0001");
|
|
System.out.println(" Pattern: Enum.map + Enum.find(deps) — O(N²) vs Map index — O(N)");
|
|
System.out.println();
|
|
|
|
int passed = 0;
|
|
int total = 0;
|
|
|
|
for (int n : sizes) {
|
|
List<MixDep> deps = makeDeps(n);
|
|
List<String> apps = makeApps(deps);
|
|
|
|
SlowTopoSort slow = new SlowTopoSort();
|
|
FastTopoSort fast = new FastTopoSort();
|
|
|
|
List<MixDep> slowResult = slow.sort(apps, deps);
|
|
List<MixDep> fastResult = fast.sort(apps, deps);
|
|
|
|
// Verify both produce same order
|
|
boolean same = true;
|
|
for (int i = 0; i < apps.size(); i++) {
|
|
if (!slowResult.get(i).app.equals(fastResult.get(i).app)) {
|
|
same = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// slow should do ~N*N comparisons (worst case); fast should do exactly N lookups
|
|
long slowCmp = slow.comparisons;
|
|
long fastLkp = fast.lookups;
|
|
|
|
// For random order, average Enum.find scan is N/2 per element → ~N²/2 total
|
|
// Fast is always N lookups
|
|
boolean slowIsQuadratic = slowCmp >= (long) n * n / 4;
|
|
boolean fastIsLinear = fastLkp == n;
|
|
|
|
total += 3;
|
|
if (same) { System.out.println("PASS N=" + n + ": results match"); passed++; }
|
|
else { System.out.println("FAIL N=" + n + ": result mismatch"); }
|
|
|
|
if (slowIsQuadratic) { System.out.println("PASS N=" + n + ": slow O(N²) comparisons=" + slowCmp + " >= N²/4=" + (n * n / 4)); passed++; }
|
|
else { System.out.println("FAIL N=" + n + ": slow not quadratic, comparisons=" + slowCmp); }
|
|
|
|
if (fastIsLinear) { System.out.println("PASS N=" + n + ": fast O(N) lookups=" + fastLkp + " == N=" + n); passed++; }
|
|
else { System.out.println("FAIL N=" + n + ": fast not linear, lookups=" + fastLkp); }
|
|
}
|
|
|
|
System.out.println();
|
|
System.out.println(passed + "/" + total + " PASS");
|
|
if (passed != total) {
|
|
throw new AssertionError(passed + "/" + total + " tests passed");
|
|
}
|
|
}
|
|
}
|