259 lines
10 KiB
Java
259 lines
10 KiB
Java
package unit;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* MavenReactorManagerTest
|
|
*
|
|
* Models two CWE-407 defects in Apache Maven:
|
|
*
|
|
* maven-0006: ReactorManager.blackList
|
|
* File: impl/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java
|
|
* Defective: private List<String> blackList = new ArrayList<>()
|
|
* blackList(String id) calls blackList.contains(id) — O(N) linear scan.
|
|
* Recursive cascade over N projects: contains() at step k costs k comparisons
|
|
* → total O(N²) comparisons.
|
|
* Fixed: private Set<String> blackList = new HashSet<>()
|
|
* blackList.add(id) is O(1) per call; total O(N).
|
|
*
|
|
* maven-0007: DefaultMavenExecutionRequest.addPluginGroup / addPluginGroups
|
|
* File: impl/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
|
|
* Defective: private List<String> pluginGroups = new ArrayList<>()
|
|
* addPluginGroup() calls ArrayList.contains() — O(G) per call.
|
|
* addPluginGroups() loops over G groups → O(G²) total comparisons.
|
|
* Fixed: private Set<String> pluginGroups = new LinkedHashSet<>()
|
|
* Each add() is O(1); total O(G).
|
|
*
|
|
* Comparison counts (not wall-clock) are measured by simulating the ArrayList
|
|
* linear scan cost explicitly: contains() on a list of size k costs k comparisons.
|
|
*/
|
|
public class MavenReactorManagerTest {
|
|
|
|
// =========================================================================
|
|
// Models for maven-0006: ReactorManager ArrayList blackList
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Defective: ArrayList.contains() requires scanning the list linearly.
|
|
* We count comparisons explicitly: checking a list of size k costs k ops.
|
|
*/
|
|
static class DefectiveReactorManager {
|
|
final List<String> blackList = new ArrayList<>();
|
|
final Map<String, List<String>> dependents;
|
|
long comparisons = 0;
|
|
|
|
DefectiveReactorManager(Map<String, List<String>> dependents) {
|
|
this.dependents = dependents;
|
|
}
|
|
|
|
boolean listContains(String id) {
|
|
// Simulate ArrayList.contains() scan cost: O(size) comparisons
|
|
comparisons += blackList.size();
|
|
return blackList.contains(id);
|
|
}
|
|
|
|
void blackList(String id) {
|
|
if (!listContains(id)) {
|
|
blackList.add(id);
|
|
List<String> deps = dependents.getOrDefault(id, List.of());
|
|
for (String dep : deps) {
|
|
blackList(dep);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fixed: HashSet.contains() / add() is O(1) — 1 comparison per call.
|
|
*/
|
|
static class FixedReactorManager {
|
|
final Set<String> blackList = new HashSet<>();
|
|
final Map<String, List<String>> dependents;
|
|
long comparisons = 0;
|
|
|
|
FixedReactorManager(Map<String, List<String>> dependents) {
|
|
this.dependents = dependents;
|
|
}
|
|
|
|
void blackList(String id) {
|
|
comparisons++; // HashSet.add() is O(1)
|
|
if (blackList.add(id)) {
|
|
List<String> deps = dependents.getOrDefault(id, List.of());
|
|
for (String dep : deps) {
|
|
blackList(dep);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build a linear dependency chain: p0 -> p1 -> p2 -> ... -> p(N-1).
|
|
* Blacklisting p0 triggers a recursive cascade over all N projects.
|
|
*/
|
|
static Map<String, List<String>> buildLinearChain(int n) {
|
|
Map<String, List<String>> deps = new HashMap<>();
|
|
for (int i = 0; i < n - 1; i++) {
|
|
deps.put("p" + i, List.of("p" + (i + 1)));
|
|
}
|
|
return deps;
|
|
}
|
|
|
|
static long[] runBlacklistComparisons(int n) {
|
|
Map<String, List<String>> deps = buildLinearChain(n);
|
|
|
|
DefectiveReactorManager defective = new DefectiveReactorManager(deps);
|
|
defective.blackList("p0");
|
|
long defectiveCmp = defective.comparisons;
|
|
|
|
FixedReactorManager fixed = new FixedReactorManager(deps);
|
|
fixed.blackList("p0");
|
|
long fixedCmp = fixed.comparisons;
|
|
|
|
// Defective: at step k the blackList has k elements, so contains() costs k.
|
|
// For N projects: sum(0, 1, ..., N-1) = N*(N-1)/2 comparisons → O(N²).
|
|
// Fixed: N calls each costing 1 → O(N).
|
|
return new long[]{ defectiveCmp, fixedCmp };
|
|
}
|
|
|
|
// =========================================================================
|
|
// Models for maven-0007: DefaultMavenExecutionRequest ArrayList pluginGroups
|
|
// =========================================================================
|
|
|
|
static class DefectivePluginGroupRequest {
|
|
List<String> pluginGroups = new ArrayList<>();
|
|
long comparisons = 0;
|
|
|
|
void addPluginGroup(String group) {
|
|
// Simulate ArrayList.contains() cost = list size
|
|
comparisons += pluginGroups.size();
|
|
if (!pluginGroups.contains(group)) {
|
|
pluginGroups.add(group);
|
|
}
|
|
}
|
|
|
|
void addPluginGroups(List<String> groups) {
|
|
for (String g : groups) addPluginGroup(g);
|
|
}
|
|
}
|
|
|
|
static class FixedPluginGroupRequest {
|
|
LinkedHashSet<String> pluginGroups = new LinkedHashSet<>();
|
|
long comparisons = 0;
|
|
|
|
void addPluginGroup(String group) {
|
|
comparisons++; // HashSet.add() is O(1)
|
|
pluginGroups.add(group);
|
|
}
|
|
|
|
void addPluginGroups(List<String> groups) {
|
|
for (String g : groups) addPluginGroup(g);
|
|
}
|
|
}
|
|
|
|
static long[] runPluginGroupComparisons(int g) {
|
|
List<String> groups = new ArrayList<>();
|
|
for (int i = 0; i < g; i++) groups.add("org.apache.plugin" + i);
|
|
|
|
DefectivePluginGroupRequest defective = new DefectivePluginGroupRequest();
|
|
defective.addPluginGroups(groups);
|
|
long defCmp = defective.comparisons;
|
|
|
|
FixedPluginGroupRequest fixed = new FixedPluginGroupRequest();
|
|
fixed.addPluginGroups(groups);
|
|
long fixCmp = fixed.comparisons;
|
|
|
|
// Defective: at step k list has k elements, contains() costs k.
|
|
// sum(0..G-1) = G*(G-1)/2 → O(G²).
|
|
// Fixed: G calls each O(1) → O(G).
|
|
return new long[]{ defCmp, fixCmp };
|
|
}
|
|
|
|
// =========================================================================
|
|
// Test runner
|
|
// =========================================================================
|
|
|
|
public static void main(String[] args) {
|
|
int pass = 0;
|
|
int fail = 0;
|
|
|
|
System.out.println("=== maven-0006: ReactorManager ArrayList blackList ===");
|
|
|
|
for (int n : new int[]{ 10, 50, 100, 200 }) {
|
|
long[] r = runBlacklistComparisons(n);
|
|
long slow = r[0], fast = r[1];
|
|
long expectedSlow = (long) n * (n - 1) / 2; // N*(N-1)/2
|
|
boolean slowMatchesQuadratic = slow == expectedSlow;
|
|
boolean fastMatchesLinear = fast == n;
|
|
boolean slowWorse = slow > fast;
|
|
|
|
boolean ok = slowMatchesQuadratic && fastMatchesLinear && slowWorse;
|
|
if (ok) pass++; else fail++;
|
|
String s = ok ? "PASS" : "FAIL";
|
|
System.out.printf(
|
|
" N=%-3d | slow=%6d cmp (expect %6d=N²/2) | fast=%3d cmp (expect %3d=N) | %s%n",
|
|
n, slow, expectedSlow, fast, n, s);
|
|
}
|
|
|
|
// Verify ratio grows quadratically
|
|
long[] r10 = runBlacklistComparisons(10);
|
|
long[] r100 = runBlacklistComparisons(100);
|
|
// At N=10: slow/fast = 45/10 = 4.5; at N=100: 4950/100 = 49.5 → ratio*10 at 100
|
|
boolean ratioGrows = (r100[0] * r10[1]) > (r10[0] * r100[1]);
|
|
if (ratioGrows) pass++; else fail++;
|
|
System.out.printf(
|
|
" ratio@N=10=%.1f ratio@N=100=%.1f — grows with N: %s%n",
|
|
(double) r10[0] / r10[1], (double) r100[0] / r100[1],
|
|
ratioGrows ? "PASS" : "FAIL");
|
|
|
|
System.out.println("\n=== maven-0007: DefaultMavenExecutionRequest ArrayList pluginGroups ===");
|
|
|
|
for (int g : new int[]{ 10, 50, 100, 200 }) {
|
|
long[] r = runPluginGroupComparisons(g);
|
|
long slow = r[0], fast = r[1];
|
|
long expectedSlow = (long) g * (g - 1) / 2;
|
|
boolean slowMatchesQuadratic = slow == expectedSlow;
|
|
boolean fastMatchesLinear = fast == g;
|
|
boolean slowWorse = slow > fast;
|
|
|
|
boolean ok = slowMatchesQuadratic && fastMatchesLinear && slowWorse;
|
|
if (ok) pass++; else fail++;
|
|
String s = ok ? "PASS" : "FAIL";
|
|
System.out.printf(
|
|
" G=%-3d | slow=%6d cmp (expect %6d=G²/2) | fast=%3d (expect %3d=G) | %s%n",
|
|
g, slow, expectedSlow, fast, g, s);
|
|
}
|
|
|
|
// Verify deduplication still works in fixed version
|
|
FixedPluginGroupRequest dedup = new FixedPluginGroupRequest();
|
|
List<String> dupes = new ArrayList<>();
|
|
for (int i = 0; i < 50; i++) dupes.add("org.apache.plugin" + (i % 10));
|
|
dedup.addPluginGroups(dupes);
|
|
boolean dedupCorrect = dedup.pluginGroups.size() == 10;
|
|
if (dedupCorrect) pass++; else fail++;
|
|
System.out.printf(" Dedup: 50 adds (10 unique) → size=%d == 10: %s%n",
|
|
dedup.pluginGroups.size(), dedupCorrect ? "PASS" : "FAIL");
|
|
|
|
// Verify insertion order preserved in fixed version
|
|
FixedPluginGroupRequest ordered = new FixedPluginGroupRequest();
|
|
ordered.addPluginGroup("alpha");
|
|
ordered.addPluginGroup("beta");
|
|
ordered.addPluginGroup("gamma");
|
|
ordered.addPluginGroup("alpha"); // duplicate — must be ignored
|
|
List<String> orderedList = new ArrayList<>(ordered.pluginGroups);
|
|
boolean orderCorrect = orderedList.equals(List.of("alpha", "beta", "gamma"));
|
|
if (orderCorrect) pass++; else fail++;
|
|
System.out.printf(" Order [alpha, beta, gamma]: %s%n", orderCorrect ? "PASS" : "FAIL");
|
|
|
|
System.out.println("\n=== Summary ===");
|
|
System.out.printf(" %d/%d PASS%n", pass, pass + fail);
|
|
if (fail > 0) {
|
|
throw new AssertionError(fail + " test(s) FAILED");
|
|
}
|
|
}
|
|
}
|