undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
This commit is contained in:
parent
0a580b313d
commit
db29a08762
1311 changed files with 371202 additions and 1188 deletions
373
defects/maven/unit/MavenGraphBuilderTest.java
Normal file
373
defects/maven/unit/MavenGraphBuilderTest.java
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
package unit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MavenGraphBuilderTest
|
||||
*
|
||||
* Models two CWE-407 defects in Apache Maven:
|
||||
*
|
||||
* maven-0004: DefaultGraphBuilder (trimProjectsToRequest / trimSelectedProjects /
|
||||
* includeAlsoMakeTransitively)
|
||||
* Defective: result.sort(comparing(sortedProjects::indexOf)) — each indexOf call
|
||||
* is O(N) → sort comparator is O(N) → total sort is O(N² log N).
|
||||
* Fixed: build an index Map once in O(N); each comparator call is O(1)
|
||||
* → sort is O(N log N).
|
||||
*
|
||||
* maven-0005: BuildPlanLogger
|
||||
* Defective: stream sorted with Comparator.comparingInt(plan.sortedNodes()::indexOf)
|
||||
* — sortedNodes() called once per comparison; indexOf is O(N) per call
|
||||
* → O(N²) total for sorting N steps.
|
||||
* Fixed: build an IdentityHashMap<BuildStep, Integer> index once in O(N);
|
||||
* comparator uses indexMap.getOrDefault(step, MAX_VALUE) → O(1) per call.
|
||||
*
|
||||
* Operation counts are instrumented explicitly — no wall-clock timing — to isolate
|
||||
* the algorithmic difference.
|
||||
*/
|
||||
public class MavenGraphBuilderTest {
|
||||
|
||||
// =========================================================================
|
||||
// Models for maven-0004: sortedProjects.indexOf() inside sort comparator
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Simulates a single sort using indexOf-based comparator.
|
||||
*
|
||||
* During Java's TimSort, a list of size N is sorted using O(N log N) comparisons.
|
||||
* Each comparison calls indexOf on a list of size N → O(N) per comparison.
|
||||
* Total: O(N² log N) indexOf probes.
|
||||
*
|
||||
* We instrument by counting the number of indexOf-equivalent scans that would
|
||||
* be needed. We run an actual sort and count how many comparator invocations
|
||||
* occur, then multiply by the average scan length (N/2 on average for a random
|
||||
* hit, N for a miss — we use N to represent worst-case indexOf cost).
|
||||
*
|
||||
* @param n size of the list to sort
|
||||
* @return total element comparisons (simulated indexOf cost)
|
||||
*/
|
||||
static long defectiveSortIndexOfCost(int n) {
|
||||
// Build sortedProjects list (reference ordering)
|
||||
List<Integer> sortedProjects = new ArrayList<>(n);
|
||||
for (int i = 0; i < n; i++) sortedProjects.add(i);
|
||||
|
||||
// Build result list in reverse order (worst case for sort)
|
||||
List<Integer> result = new ArrayList<>(n);
|
||||
for (int i = n - 1; i >= 0; i--) result.add(i);
|
||||
|
||||
// Defective comparator: O(N) indexOf per invocation
|
||||
long[] comparisons = {0L};
|
||||
result.sort((a, b) -> {
|
||||
// Each indexOf call scans up to N elements
|
||||
int ia = 0;
|
||||
for (int i = 0; i < sortedProjects.size(); i++) {
|
||||
comparisons[0]++;
|
||||
if (sortedProjects.get(i).equals(a)) { ia = i; break; }
|
||||
}
|
||||
int ib = 0;
|
||||
for (int i = 0; i < sortedProjects.size(); i++) {
|
||||
comparisons[0]++;
|
||||
if (sortedProjects.get(i).equals(b)) { ib = i; break; }
|
||||
}
|
||||
return Integer.compare(ia, ib);
|
||||
});
|
||||
return comparisons[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the fixed sort using a pre-built index map.
|
||||
*
|
||||
* Build an orderMap in O(N) once; comparator does O(1) map.get().
|
||||
* We count map lookups (each is O(1)).
|
||||
*
|
||||
* @param n size of the list to sort
|
||||
* @return total map lookups (one pair per comparator invocation)
|
||||
*/
|
||||
static long fixedSortIndexMapCost(int n) {
|
||||
// Build sortedProjects list
|
||||
List<Integer> sortedProjects = new ArrayList<>(n);
|
||||
for (int i = 0; i < n; i++) sortedProjects.add(i);
|
||||
|
||||
// CWE-407 fix: build orderMap in O(N)
|
||||
Map<Integer, Integer> orderMap = new HashMap<>(n * 2);
|
||||
for (int i = 0; i < n; i++) {
|
||||
orderMap.put(sortedProjects.get(i), i);
|
||||
}
|
||||
|
||||
// Build result list in reverse order
|
||||
List<Integer> result = new ArrayList<>(n);
|
||||
for (int i = n - 1; i >= 0; i--) result.add(i);
|
||||
|
||||
long[] lookups = {0L};
|
||||
result.sort((a, b) -> {
|
||||
lookups[0] += 2; // two O(1) map.get() calls per comparison
|
||||
return Integer.compare(orderMap.get(a), orderMap.get(b));
|
||||
});
|
||||
return lookups[0];
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Models for maven-0005: BuildPlanLogger sortedNodes().indexOf() in stream sort
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Simulates the defective BuildPlanLogger sort:
|
||||
* .sorted(Comparator.comparingInt(plan.sortedNodes()::indexOf))
|
||||
*
|
||||
* sortedNodes() returns a list of N BuildStep objects.
|
||||
* indexOf is O(N) per comparison.
|
||||
* Sorting M steps using this comparator costs O(M log M) comparisons × O(N) each.
|
||||
* When M ≈ N this is O(N² log N).
|
||||
*
|
||||
* We model steps as Integers and sortedNodes as a List<Integer>.
|
||||
* We count individual element comparisons inside the indexOf simulation.
|
||||
*
|
||||
* @param nodeCount total nodes in the plan (N)
|
||||
* @param stepCount steps being sorted for one project (M, typically ≤ N)
|
||||
* @return total element comparisons (simulated indexOf cost)
|
||||
*/
|
||||
static long defectiveBuildPlanLoggerCost(int nodeCount, int stepCount) {
|
||||
// sortedNodes: list of node ids 0..nodeCount-1
|
||||
List<Integer> sortedNodes = new ArrayList<>(nodeCount);
|
||||
for (int i = 0; i < nodeCount; i++) sortedNodes.add(i);
|
||||
|
||||
// steps for this project: every other node, in reverse order (stress sort)
|
||||
List<Integer> steps = new ArrayList<>(stepCount);
|
||||
for (int i = stepCount - 1; i >= 0; i--) steps.add(i * (nodeCount / stepCount));
|
||||
|
||||
long[] comparisons = {0L};
|
||||
steps.sort((a, b) -> {
|
||||
// Defect: O(N) indexOf per call — simulated here with explicit scan
|
||||
int ia = 0;
|
||||
for (int i = 0; i < sortedNodes.size(); i++) {
|
||||
comparisons[0]++;
|
||||
if (sortedNodes.get(i).equals(a)) { ia = i; break; }
|
||||
}
|
||||
int ib = 0;
|
||||
for (int i = 0; i < sortedNodes.size(); i++) {
|
||||
comparisons[0]++;
|
||||
if (sortedNodes.get(i).equals(b)) { ib = i; break; }
|
||||
}
|
||||
return Integer.compare(ia, ib);
|
||||
});
|
||||
return comparisons[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the fixed BuildPlanLogger sort:
|
||||
* build indexMap once, sort with indexMap.getOrDefault(step, MAX_VALUE).
|
||||
*
|
||||
* @param nodeCount total nodes in the plan (N)
|
||||
* @param stepCount steps being sorted (M)
|
||||
* @return total map lookups (two per comparator invocation)
|
||||
*/
|
||||
static long fixedBuildPlanLoggerCost(int nodeCount, int stepCount) {
|
||||
// sortedNodes
|
||||
List<Integer> sortedNodes = new ArrayList<>(nodeCount);
|
||||
for (int i = 0; i < nodeCount; i++) sortedNodes.add(i);
|
||||
|
||||
// CWE-407 fix: build indexMap using IdentityHashMap equivalent (HashMap here
|
||||
// since Integer objects are pooled for small values; semantics are identical)
|
||||
Map<Integer, Integer> indexMap = new IdentityHashMap<>(nodeCount * 2);
|
||||
for (int i = 0; i < nodeCount; i++) {
|
||||
indexMap.put(sortedNodes.get(i), i);
|
||||
}
|
||||
|
||||
// steps (same as defective version)
|
||||
List<Integer> steps = new ArrayList<>(stepCount);
|
||||
for (int i = stepCount - 1; i >= 0; i--) steps.add(i * (nodeCount / stepCount));
|
||||
|
||||
long[] lookups = {0L};
|
||||
steps.sort((a, b) -> {
|
||||
lookups[0] += 2; // two O(1) getOrDefault() calls per comparison
|
||||
int ia = indexMap.getOrDefault(a, Integer.MAX_VALUE);
|
||||
int ib = indexMap.getOrDefault(b, Integer.MAX_VALUE);
|
||||
return Integer.compare(ia, ib);
|
||||
});
|
||||
return lookups[0];
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Test 1 — maven-0004: defect vs fixed operation count at N=100
|
||||
// =========================================================================
|
||||
|
||||
static void test1_sortIndexOfVsIndexMap() {
|
||||
int n = 100;
|
||||
long defectOps = defectiveSortIndexOfCost(n);
|
||||
long fixedOps = fixedSortIndexMapCost(n);
|
||||
|
||||
System.out.printf(
|
||||
"test1: n=%d defect_comparisons=%d fixed_lookups=%d%n",
|
||||
n, defectOps, fixedOps);
|
||||
|
||||
// Defect must do significantly more work: O(N log N) × O(N) vs O(N log N) × O(1)
|
||||
assert defectOps > fixedOps
|
||||
: "defect must do more work than fix at n=" + n;
|
||||
// Conservative lower bound: at least N comparator calls, each doing 2 indexOf
|
||||
// scans of at least 1 element each → total ≥ N*(N-1) (triangular sum lower bound)
|
||||
long lowerBound = (long) n * (n - 1);
|
||||
assert defectOps >= lowerBound
|
||||
: "defect comparisons=" + defectOps + " expected >= N*(N-1)=" + lowerBound;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Test 2 — maven-0004: doubling N grows defect super-quadratically,
|
||||
// fixed sub-quadratically
|
||||
// =========================================================================
|
||||
|
||||
static void test2_sortScalingDefectVsFixed() {
|
||||
int n1 = 60;
|
||||
int n2 = 120;
|
||||
|
||||
long d1 = defectiveSortIndexOfCost(n1);
|
||||
long d2 = defectiveSortIndexOfCost(n2);
|
||||
long f1 = fixedSortIndexMapCost(n1);
|
||||
long f2 = fixedSortIndexMapCost(n2);
|
||||
|
||||
double defectGrowth = (double) d2 / Math.max(1, d1);
|
||||
double fixedGrowth = (double) f2 / Math.max(1, f1);
|
||||
|
||||
System.out.printf(
|
||||
"test2: defect_growth=%.2fx (n 2x) fixed_growth=%.2fx%n",
|
||||
defectGrowth, fixedGrowth);
|
||||
|
||||
// Defect is O(N² log N): doubling N → ~4× growth (the log N factor is minor)
|
||||
assert defectGrowth > 3.0
|
||||
: "defect should grow at least 3x with 2x N (O(N² log N)), got " + defectGrowth;
|
||||
// Fixed is O(N log N): doubling N → ~2× growth
|
||||
assert fixedGrowth <= 3.0
|
||||
: "fixed should grow at most 3x with 2x N (O(N log N)), got " + fixedGrowth;
|
||||
assert defectGrowth > fixedGrowth
|
||||
: "defect growth must exceed fixed growth";
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Test 3 — maven-0004: sorted result is correct for both implementations
|
||||
// =========================================================================
|
||||
|
||||
static void test3_sortCorrectnessCheck() {
|
||||
int n = 50;
|
||||
// Build sortedProjects (reference order 0..n-1)
|
||||
List<Integer> sortedProjects = new ArrayList<>(n);
|
||||
for (int i = 0; i < n; i++) sortedProjects.add(i);
|
||||
|
||||
// result list in arbitrary order (reverse)
|
||||
List<Integer> defResult = new ArrayList<>(n);
|
||||
List<Integer> fixResult = new ArrayList<>(n);
|
||||
for (int i = n - 1; i >= 0; i--) {
|
||||
defResult.add(i);
|
||||
fixResult.add(i);
|
||||
}
|
||||
|
||||
// Defective sort
|
||||
defResult.sort((a, b) -> Integer.compare(sortedProjects.indexOf(a), sortedProjects.indexOf(b)));
|
||||
|
||||
// Fixed sort
|
||||
Map<Integer, Integer> orderMap = new HashMap<>(n * 2);
|
||||
for (int i = 0; i < n; i++) orderMap.put(sortedProjects.get(i), i);
|
||||
fixResult.sort((a, b) -> Integer.compare(orderMap.get(a), orderMap.get(b)));
|
||||
|
||||
System.out.printf("test3: n=%d correctness check defResult[0]=%d fixResult[0]=%d last=%d%n",
|
||||
n, defResult.get(0), fixResult.get(0), defResult.get(n - 1));
|
||||
|
||||
// Both should produce the same ordering
|
||||
assert defResult.equals(fixResult)
|
||||
: "defective and fixed sorts produced different orderings";
|
||||
// First element should be 0 (lowest index in sortedProjects)
|
||||
assert defResult.get(0).equals(0)
|
||||
: "first sorted element should be 0, got " + defResult.get(0);
|
||||
assert defResult.get(n - 1).equals(n - 1)
|
||||
: "last sorted element should be " + (n-1) + ", got " + defResult.get(n-1);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Test 4 — maven-0005: BuildPlanLogger defect vs fixed operation count
|
||||
// =========================================================================
|
||||
|
||||
static void test4_buildPlanLoggerDefectVsFixed() {
|
||||
int nodeCount = 200;
|
||||
int stepCount = 100;
|
||||
|
||||
long defectOps = defectiveBuildPlanLoggerCost(nodeCount, stepCount);
|
||||
long fixedOps = fixedBuildPlanLoggerCost(nodeCount, stepCount);
|
||||
|
||||
System.out.printf(
|
||||
"test4: nodeCount=%d stepCount=%d defect_comparisons=%d fixed_lookups=%d%n",
|
||||
nodeCount, stepCount, defectOps, fixedOps);
|
||||
|
||||
// Defect: O(M log M) comparator calls × O(N) indexOf each
|
||||
// Fixed: O(M log M) × O(1) map lookup
|
||||
assert defectOps > fixedOps
|
||||
: "defect must do more work than fix";
|
||||
// Lower bound: at least M comparator invocations × 2 indexOf scans of avg length N/2
|
||||
long lowerBound = (long) stepCount * nodeCount / 2;
|
||||
assert defectOps >= lowerBound
|
||||
: "defect=" + defectOps + " expected >= " + lowerBound;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Test 5 — maven-0005: scaling node count grows defect super-linearly,
|
||||
// fixed grows at most linearly
|
||||
// =========================================================================
|
||||
|
||||
static void test5_buildPlanLoggerScaling() {
|
||||
int stepCount = 80;
|
||||
int nodes1 = 100;
|
||||
int nodes2 = 200; // 2x nodes
|
||||
|
||||
long d1 = defectiveBuildPlanLoggerCost(nodes1, stepCount);
|
||||
long d2 = defectiveBuildPlanLoggerCost(nodes2, stepCount);
|
||||
long f1 = fixedBuildPlanLoggerCost(nodes1, stepCount);
|
||||
long f2 = fixedBuildPlanLoggerCost(nodes2, stepCount);
|
||||
|
||||
double defectGrowth = (double) d2 / Math.max(1, d1);
|
||||
double fixedGrowth = (double) f2 / Math.max(1, f1);
|
||||
|
||||
System.out.printf(
|
||||
"test5: stepCount=%d defect_growth=%.2fx (nodes 2x) fixed_growth=%.2fx%n",
|
||||
stepCount, defectGrowth, fixedGrowth);
|
||||
|
||||
// Defect: O(N) per indexOf → doubling N doubles cost per comparator call → 2x overall
|
||||
assert defectGrowth > 1.5
|
||||
: "defect should grow with node count (O(N) indexOf), got " + defectGrowth;
|
||||
// Fixed: O(1) per lookup → doubling N has no effect on sort cost (only on build-map cost)
|
||||
// With N doubling, build-map is O(N) but sort is O(M log M) × O(1) — fixed lookups unchanged
|
||||
assert fixedGrowth <= 2.0
|
||||
: "fixed lookups should not grow with node count, got " + fixedGrowth;
|
||||
assert defectGrowth > fixedGrowth
|
||||
: "defect growth must exceed fixed growth when N doubles";
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Main
|
||||
// =========================================================================
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== MavenGraphBuilderTest ===");
|
||||
System.out.println("Modelling CWE-407 defects:");
|
||||
System.out.println(" maven-0004: DefaultGraphBuilder sortedProjects.indexOf() → O(N² log N)");
|
||||
System.out.println(" maven-0005: BuildPlanLogger sortedNodes().indexOf() → O(N²) per project");
|
||||
System.out.println();
|
||||
|
||||
test1_sortIndexOfVsIndexMap();
|
||||
System.out.println(" PASS test1_sortIndexOfVsIndexMap");
|
||||
|
||||
test2_sortScalingDefectVsFixed();
|
||||
System.out.println(" PASS test2_sortScalingDefectVsFixed");
|
||||
|
||||
test3_sortCorrectnessCheck();
|
||||
System.out.println(" PASS test3_sortCorrectnessCheck");
|
||||
|
||||
test4_buildPlanLoggerDefectVsFixed();
|
||||
System.out.println(" PASS test4_buildPlanLoggerDefectVsFixed");
|
||||
|
||||
test5_buildPlanLoggerScaling();
|
||||
System.out.println(" PASS test5_buildPlanLoggerScaling");
|
||||
|
||||
System.out.println();
|
||||
System.out.println("All 5 tests PASSED.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue