package unit; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; /** * PuppetGraphTest * * Models one CWE-407 defect in puppetlabs/puppet: * * PUP-001 (LOW, error path) — lib/puppet/graph/simple_graph.rb:214 * paths_in_cycle() BFS: frame[1].member?(frame[0]) where frame[1] is a * growing Array. Each membership test is O(path_length); paths grow as * BFS expands; in a fully connected cycle of length N this produces * O(N^3) total comparisons. * * Fix: carry a parallel Set alongside the Array in each BFS frame. * frame[2].include?(frame[0]) is O(1) average. Array is retained for * ordered path output. * * All measurements are instrumented operation counts, not wall-clock timing. */ public class PuppetGraphTest { // ----------------------------------------------------------------------- // PUP-001 modelling helpers // Models BFS over a simple directed cycle: 0 -> 1 -> 2 -> ... -> (N-1) -> 0 // BFS explores from vertex 0; paths grow until cycle is detected. // // Defective: frame path is ArrayList; membership test iterates the whole list. // Fixed: parallel HashSet; membership test is O(1). // ----------------------------------------------------------------------- /** * Simulates defective paths_in_cycle BFS on a simple cycle of length N. * Returns total membership-test comparisons performed. * * BFS frame = [vertex, path_as_ArrayList] * Cycle detection: path.contains(vertex) -- O(path.size()) per call */ static long pup001Defective(int cycleLen) { // Use pair representation: ArrayList where int[0]=vertex, index into paths list // But model directly with operation counting // BFS: frame = (vertex, path ArrayList) ArrayList stack = new ArrayList<>(); ArrayList emptyPath = new ArrayList<>(); stack.add(new Object[]{0, emptyPath}); long comparisons = 0; int steps = 0; int maxSteps = cycleLen * cycleLen * 4; // safety bound while (!stack.isEmpty() && steps < maxSteps) { steps++; Object[] frame = stack.remove(0); // shift (BFS) int vertex = (Integer) frame[0]; @SuppressWarnings("unchecked") ArrayList path = (ArrayList) frame[1]; // Defective: path.contains(vertex) -- O(path.size()) comparisons boolean inPath = false; for (Integer p : path) { comparisons++; if (p == vertex) { inPath = true; break; } } if (inPath) { // cycle found — stop this branch } else { // extend path and push next vertex ArrayList newPath = new ArrayList<>(path); newPath.add(vertex); int next = (vertex + 1) % cycleLen; stack.add(new Object[]{next, newPath}); } } return comparisons; } /** * Simulates fixed paths_in_cycle BFS on a simple cycle of length N. * Returns total membership-test operations (each O(1) hash lookup = 1 op). * * BFS frame = [vertex, path_ArrayList, path_HashSet] * Cycle detection: pathSet.contains(vertex) -- O(1) per call */ static long pup001Fixed(int cycleLen) { ArrayList stack = new ArrayList<>(); ArrayList emptyPath = new ArrayList<>(); HashSet emptySet = new HashSet<>(); stack.add(new Object[]{0, emptyPath, emptySet}); long lookups = 0; int steps = 0; int maxSteps = cycleLen * cycleLen * 4; while (!stack.isEmpty() && steps < maxSteps) { steps++; Object[] frame = stack.remove(0); int vertex = (Integer) frame[0]; @SuppressWarnings("unchecked") ArrayList path = (ArrayList) frame[1]; @SuppressWarnings("unchecked") HashSet pathSet = (HashSet) frame[2]; // Fixed: pathSet.contains(vertex) -- O(1) lookup lookups++; boolean inPath = pathSet.contains(vertex); if (inPath) { // cycle found — stop this branch } else { ArrayList newPath = new ArrayList<>(path); newPath.add(vertex); @SuppressWarnings("unchecked") HashSet newSet = (HashSet) pathSet.clone(); newSet.add(vertex); int next = (vertex + 1) % cycleLen; stack.add(new Object[]{next, newPath, newSet}); } } return lookups; } // ----------------------------------------------------------------------- // Test 1 — PUP-001: defect costs more than fix at cycle_len=20 // ----------------------------------------------------------------------- static void test1_pup001_pathMembershipSet() { int N = 20; long defectCost = pup001Defective(N); long fixedCost = pup001Fixed(N); double ratio = (double) defectCost / Math.max(1, fixedCost); System.out.printf("test1 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 + " (defect=" + defectCost + ", fixed=" + fixedCost + ")"; assert ratio >= 8.0 : "expected ratio >= 8x at cycle_len=" + N + ", got " + ratio; } // ----------------------------------------------------------------------- // Test 2 — PUP-001: scaling — doubling cycle length grows defect faster // than the fix (super-linear vs near-linear) // ----------------------------------------------------------------------- static void test2_pup001_scalingGrowth() { int N1 = 15; int N2 = 30; // doubled 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("test2 PUP-001: N=%d→%d defect=%d→%d (%.2fx) fixed=%d→%d (%.2fx)%n", N1, N2, d1, d2, defectGrowth, f1, f2, fixedGrowth); assert defectGrowth > 2.0 : "defect should grow super-linearly on 2x cycle_len, got " + defectGrowth; assert fixedGrowth <= 3.0 : "fixed should grow at most linearly (×2 ± slack) on 2x cycle_len, got " + fixedGrowth; assert defectGrowth > fixedGrowth : "defect growth " + defectGrowth + " should exceed fixed growth " + fixedGrowth; } // ----------------------------------------------------------------------- // Test 3 — PUP-001: ratio at cycle_len=25 exceeds 5x // ----------------------------------------------------------------------- static void test3_pup001_ratioAt25() { int N = 25; long defectOps = pup001Defective(N); long fixedOps = pup001Fixed(N); double ratio = (double) defectOps / Math.max(1, fixedOps); System.out.printf("test3 PUP-001: cycle_len=%d defect=%d fixed=%d ratio=%.1fx%n", N, defectOps, fixedOps, ratio); assert ratio > 5.0 : "expected ratio > 5x at cycle_len=25, got " + ratio; } // ----------------------------------------------------------------------- // Main // ----------------------------------------------------------------------- public static void main(String[] args) { System.out.println("=== PuppetGraphTest ==="); System.out.println("Modelling CWE-407: PUP-001 (paths_in_cycle BFS Array#member? O(N^3))"); System.out.println(); test1_pup001_pathMembershipSet(); System.out.println(" PASS test1_pup001_pathMembershipSet"); test2_pup001_scalingGrowth(); System.out.println(" PASS test2_pup001_scalingGrowth"); test3_pup001_ratioAt25(); System.out.println(" PASS test3_pup001_ratioAt25"); System.out.println(); System.out.println("3/3 PASS"); } }