B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
339 lines
14 KiB
Java
339 lines
14 KiB
Java
package unit;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
|
|
/**
|
|
* AnsibleRoleTest
|
|
*
|
|
* Models three CWE-407 defects in ansible/ansible:
|
|
*
|
|
* ANS-001 (MEDIUM) — get_vars(): `seen = []` list deduplication over transitive
|
|
* dependencies. Each `dep not in seen` is O(D) → O(D^2) total.
|
|
* Fix: identity-keyed set — `seen_ids = set()` with id(dep), O(1) per check.
|
|
*
|
|
* ANS-002 (LOW) — _load_role_data(): `self.collections` list membership tests.
|
|
* `c not in self.collections` is O(C) per candidate; two further
|
|
* `not in self.collections` guards add O(C) each → O(C) total per call.
|
|
* Fix: parallel _collections_set for O(1) membership.
|
|
*
|
|
* PUP-001 (LOW, error path) — paths_in_cycle() BFS: frame[1].member?(frame[0])
|
|
* where frame[1] is a growing Array. O(path) per BFS step → O(|cycle|^3)
|
|
* worst case. Fix: Set alongside Array for O(1) include?.
|
|
*
|
|
* All measurements are instrumented operation counts, not wall-clock timing.
|
|
*/
|
|
public class AnsibleRoleTest {
|
|
|
|
// -----------------------------------------------------------------------
|
|
// ANS-001 modelling helpers
|
|
// Defective: ArrayList.contains() — O(D) linear scan per dep
|
|
// Fixed: HashMap keyed by identity integer — O(1) containsKey
|
|
// -----------------------------------------------------------------------
|
|
|
|
/** Returns total contains-calls performed (each call costs 1 unit). */
|
|
static long ans001Defective(int[] depIds) {
|
|
ArrayList<Integer> seen = new ArrayList<>();
|
|
long comparisons = 0;
|
|
for (int dep : depIds) {
|
|
// model `dep not in seen` — O(current seen size)
|
|
comparisons += seen.size(); // worst-case linear scan cost
|
|
if (!seen.contains(dep)) {
|
|
seen.add(dep);
|
|
}
|
|
}
|
|
return comparisons;
|
|
}
|
|
|
|
/** Returns total lookup-calls performed (each O(1) hash lookup costs 1 unit). */
|
|
static long ans001Fixed(int[] depIds) {
|
|
HashMap<Integer, Boolean> seenIds = new HashMap<>();
|
|
long lookups = 0;
|
|
for (int dep : depIds) {
|
|
lookups++; // one O(1) hash lookup per dep
|
|
if (!seenIds.containsKey(dep)) {
|
|
seenIds.put(dep, Boolean.TRUE);
|
|
}
|
|
}
|
|
return lookups;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// ANS-002 modelling helpers
|
|
// Defective: ArrayList membership for each candidate collection
|
|
// Fixed: HashSet membership — O(1)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static long ans002Defective(String[] candidates, String[] existing) {
|
|
ArrayList<String> collections = new ArrayList<>();
|
|
for (String e : existing) collections.add(e);
|
|
|
|
long scans = 0;
|
|
for (String c : candidates) {
|
|
// model `c not in self.collections` — O(C) per candidate
|
|
scans += collections.size();
|
|
if (!collections.contains(c)) {
|
|
collections.add(c);
|
|
}
|
|
}
|
|
// model two sentinel checks: 'ansible.builtin' not in and 'ansible.legacy' not in
|
|
scans += collections.size(); // builtin check
|
|
scans += collections.size(); // legacy check
|
|
return scans;
|
|
}
|
|
|
|
static long ans002Fixed(String[] candidates, String[] existing) {
|
|
ArrayList<String> collections = new ArrayList<>();
|
|
HashSet<String> collectionsSet = new HashSet<>();
|
|
for (String e : existing) {
|
|
collections.add(e);
|
|
collectionsSet.add(e);
|
|
}
|
|
|
|
long lookups = 0;
|
|
for (String c : candidates) {
|
|
lookups++; // O(1) set lookup per candidate
|
|
if (!collectionsSet.contains(c)) {
|
|
collections.add(c);
|
|
collectionsSet.add(c);
|
|
}
|
|
}
|
|
lookups++; // builtin check — O(1)
|
|
lookups++; // legacy check — O(1)
|
|
return lookups;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// PUP-001 modelling helpers
|
|
// Models BFS over a cycle of length N; each BFS step tests membership of
|
|
// the current vertex in the current path.
|
|
//
|
|
// Defective: path is ArrayList; path.contains() is O(path_length)
|
|
// Fixed: path membership via HashSet; O(1) contains
|
|
//
|
|
// Returns total membership-test cost across all BFS steps.
|
|
// -----------------------------------------------------------------------
|
|
|
|
static long pup001Defective(int cycleLen) {
|
|
// Each vertex has exactly one successor in a simple cycle: v -> (v+1) % N
|
|
// BFS starting from vertex 0; path grows until we revisit a vertex.
|
|
// We simulate the BFS and count the cost of each ArrayList.contains call.
|
|
long cost = 0;
|
|
|
|
// BFS frame: [vertex, path as ArrayList]
|
|
// Use a simple ArrayList-of-ArrayLists to model the stack
|
|
ArrayList<Object[]> stack = new ArrayList<>();
|
|
ArrayList<Integer> initPath = new ArrayList<>();
|
|
stack.add(new Object[]{0, initPath});
|
|
|
|
int steps = 0;
|
|
while (!stack.isEmpty() && steps < cycleLen * cycleLen * 4) {
|
|
Object[] frame = stack.remove(0);
|
|
int vertex = (Integer) frame[0];
|
|
@SuppressWarnings("unchecked")
|
|
ArrayList<Integer> path = (ArrayList<Integer>) frame[1];
|
|
|
|
// model frame[1].member?(frame[0]) — O(path.size())
|
|
cost += path.size(); // cost of linear scan
|
|
|
|
if (path.contains(vertex)) {
|
|
// cycle found — stop this branch
|
|
} else {
|
|
ArrayList<Integer> newPath = new ArrayList<>(path);
|
|
newPath.add(vertex);
|
|
int next = (vertex + 1) % cycleLen;
|
|
stack.add(new Object[]{next, newPath});
|
|
}
|
|
steps++;
|
|
}
|
|
return cost;
|
|
}
|
|
|
|
static long pup001Fixed(int cycleLen) {
|
|
long cost = 0;
|
|
|
|
// BFS frame: [vertex, path ArrayList, path HashSet]
|
|
ArrayList<Object[]> stack = new ArrayList<>();
|
|
ArrayList<Integer> initPath = new ArrayList<>();
|
|
HashSet<Integer> initSet = new HashSet<>();
|
|
stack.add(new Object[]{0, initPath, initSet});
|
|
|
|
int steps = 0;
|
|
while (!stack.isEmpty() && steps < cycleLen * cycleLen * 4) {
|
|
Object[] frame = stack.remove(0);
|
|
int vertex = (Integer) frame[0];
|
|
@SuppressWarnings("unchecked")
|
|
ArrayList<Integer> path = (ArrayList<Integer>) frame[1];
|
|
@SuppressWarnings("unchecked")
|
|
HashSet<Integer> pathSet = (HashSet<Integer>) frame[2];
|
|
|
|
// model path_set.include?(vertex) — O(1)
|
|
cost += 1; // one hash lookup
|
|
|
|
if (pathSet.contains(vertex)) {
|
|
// cycle found — stop this branch
|
|
} else {
|
|
ArrayList<Integer> newPath = new ArrayList<>(path);
|
|
newPath.add(vertex);
|
|
HashSet<Integer> newSet = new HashSet<>(pathSet);
|
|
newSet.add(vertex);
|
|
int next = (vertex + 1) % cycleLen;
|
|
stack.add(new Object[]{next, newPath, newSet});
|
|
}
|
|
steps++;
|
|
}
|
|
return cost;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 1 — ANS-001: defective O(D^2) vs fixed O(D) at D=60 unique deps
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test1_ans001_quadraticVsLinear() {
|
|
int D = 60;
|
|
int[] depIds = new int[D];
|
|
for (int i = 0; i < D; i++) depIds[i] = i; // all unique
|
|
|
|
long defectCost = ans001Defective(depIds);
|
|
long fixedCost = ans001Fixed(depIds);
|
|
|
|
System.out.printf("test1 ANS-001: D=%d unique defect=%d fixed=%d%n",
|
|
D, defectCost, fixedCost);
|
|
|
|
assert defectCost > fixedCost
|
|
: "defect must be more expensive than fix at D=" + D;
|
|
// defective scans: 0+1+2+...+(D-1) = D*(D-1)/2
|
|
long expectedDefect = (long) D * (D - 1) / 2;
|
|
assert defectCost == expectedDefect
|
|
: "expected defect cost=" + expectedDefect + " got=" + defectCost;
|
|
double ratio = (double) defectCost / Math.max(1, fixedCost);
|
|
assert ratio > 10.0
|
|
: "expected ratio>10x for D=" + D + ", got " + ratio;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 2 — ANS-001: duplicate deps case — defect still O(D^2), fix O(D)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test2_ans001_duplicateDeps() {
|
|
int D = 80;
|
|
// half-unique: dep IDs repeat every D/2 values → many duplicates
|
|
int[] depIds = new int[D];
|
|
int half = D / 2;
|
|
for (int i = 0; i < D; i++) depIds[i] = i % half;
|
|
|
|
long defectCost = ans001Defective(depIds);
|
|
long fixedCost = ans001Fixed(depIds);
|
|
|
|
double ratio = (double) defectCost / Math.max(1, fixedCost);
|
|
System.out.printf("test2 ANS-001: D=%d half-unique defect=%d fixed=%d ratio=%.1fx%n",
|
|
D, defectCost, fixedCost, ratio);
|
|
|
|
assert defectCost > fixedCost
|
|
: "defect must be more expensive than fix at D=" + D + " with duplicates";
|
|
assert ratio > 5.0
|
|
: "expected ratio>5x for half-unique D=" + D + ", got " + ratio;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 3 — ANS-002: collections list membership — defect O(C^2), fix O(C)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test3_ans002_collectionsSet() {
|
|
int C = 50;
|
|
String[] existing = new String[5];
|
|
for (int i = 0; i < 5; i++) existing[i] = "existing.collection." + i;
|
|
|
|
String[] candidates = new String[C];
|
|
for (int i = 0; i < C; i++) candidates[i] = "meta.collection." + i;
|
|
|
|
long defectCost = ans002Defective(candidates, existing);
|
|
long fixedCost = ans002Fixed(candidates, existing);
|
|
|
|
double ratio = (double) defectCost / Math.max(1, fixedCost);
|
|
System.out.printf("test3 ANS-002: C=%d candidates defect=%d fixed=%d ratio=%.1fx%n",
|
|
C, defectCost, fixedCost, ratio);
|
|
|
|
assert defectCost > fixedCost
|
|
: "defect must be more expensive than fix for C=" + C + " collections";
|
|
assert ratio > 5.0
|
|
: "expected ratio>5x, got " + ratio;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 4 — PUP-001: BFS path membership — defect O(N^3), fix O(N)
|
|
// Cycle length N=20 — defect accumulates scan cost, fix stays O(N)
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test4_pup001_pathMembershipSet() {
|
|
int N = 20;
|
|
long defectCost = pup001Defective(N);
|
|
long fixedCost = pup001Fixed(N);
|
|
|
|
double ratio = (double) defectCost / Math.max(1, fixedCost);
|
|
System.out.printf("test4 PUP-001: cycle_len=%d defect=%d fixed=%d ratio=%.1fx%n",
|
|
N, defectCost, fixedCost, ratio);
|
|
|
|
assert defectCost > fixedCost
|
|
: "defect must be more expensive than fix at cycle_len=" + N;
|
|
assert ratio > 3.0
|
|
: "expected ratio>3x, got " + ratio;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test 5 — PUP-001: scaling — doubling cycle length grows defect faster
|
|
// than fixed, demonstrating super-linear vs linear growth
|
|
// -----------------------------------------------------------------------
|
|
|
|
static void test5_pup001_scalingGrowth() {
|
|
int N1 = 15;
|
|
int N2 = 30; // double cycle length
|
|
|
|
long d1 = pup001Defective(N1);
|
|
long d2 = pup001Defective(N2);
|
|
long f1 = pup001Fixed(N1);
|
|
long f2 = pup001Fixed(N2);
|
|
|
|
double defectGrowth = (double) d2 / Math.max(1, d1);
|
|
double fixedGrowth = (double) f2 / Math.max(1, f1);
|
|
|
|
System.out.printf("test5 PUP-001: N1=%d N2=%d defect_growth=%.2fx fixed_growth=%.2fx%n",
|
|
N1, N2, defectGrowth, fixedGrowth);
|
|
|
|
assert defectGrowth > fixedGrowth
|
|
: "defect should grow faster than fix when cycle doubles; defect=" + defectGrowth + " fixed=" + fixedGrowth;
|
|
assert defectGrowth > 2.0
|
|
: "defect should grow super-linearly (>2x) when N doubles, got " + defectGrowth;
|
|
assert fixedGrowth <= 3.0
|
|
: "fixed should grow at most linearly (~2x) when N doubles, got " + fixedGrowth;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Main
|
|
// -----------------------------------------------------------------------
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== AnsibleRoleTest ===");
|
|
System.out.println("Modelling CWE-407: ANS-001 get_vars seen-list, ANS-002 collections-list, PUP-001 BFS path Array");
|
|
System.out.println();
|
|
|
|
test1_ans001_quadraticVsLinear();
|
|
System.out.println(" PASS test1_ans001_quadraticVsLinear");
|
|
|
|
test2_ans001_duplicateDeps();
|
|
System.out.println(" PASS test2_ans001_duplicateDeps");
|
|
|
|
test3_ans002_collectionsSet();
|
|
System.out.println(" PASS test3_ans002_collectionsSet");
|
|
|
|
test4_pup001_pathMembershipSet();
|
|
System.out.println(" PASS test4_pup001_pathMembershipSet");
|
|
|
|
test5_pup001_scalingGrowth();
|
|
System.out.println(" PASS test5_pup001_scalingGrowth");
|
|
|
|
System.out.println();
|
|
System.out.println("All 5 tests PASSED.");
|
|
}
|
|
}
|