package unit; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * CWE-407 unit test — vertx-0001 * * HAManager.nodeLeft() scans clusterMap (C entries) and calls nodes.contains() on a * List for each entry → O(C × N) = O(N²) in cluster size. * * Fix: convert nodes List to HashSet before the loop → O(N) total. */ public class HAManagerNodeLeftAlgorithm { // Simulated clusterMap: nodeId -> haInfo (one entry per cluster node) static Map buildClusterMap(int clusterSize) { Map map = new HashMap<>(); for (int i = 0; i < clusterSize; i++) { map.put("node-" + i, "{\"group\":\"default\",\"id\":\"node-" + i + "\"}"); } return map; } // Simulated getNodes() — returns a List (as per ClusteredNode SPI contract) static List buildNodesList(int clusterSize) { List list = new ArrayList<>(); for (int i = 0; i < clusterSize; i++) { list.add("node-" + i); } return list; } // SLOW: original pattern — List.contains inside loop static long slowNodeLeft(Map clusterMap, List nodes, String leftNodeID) { long slowOps = 0; for (Map.Entry entry : clusterMap.entrySet()) { if (!leftNodeID.equals(entry.getKey())) { // O(N) scan per iteration — the defect for (String n : nodes) { slowOps++; if (n.equals(entry.getKey())) { break; } } } } return slowOps; } // FAST: fixed pattern — HashSet.contains inside loop static long fastNodeLeft(Map clusterMap, List nodes, String leftNodeID) { Set nodesSet = new HashSet<>(nodes); // O(N) once long fastOps = 0; for (Map.Entry entry : clusterMap.entrySet()) { if (!leftNodeID.equals(entry.getKey())) { // O(1) lookup fastOps++; nodesSet.contains(entry.getKey()); } } return fastOps; } public static void main(String[] args) { int[] sizes = {100, 250, 500}; int pass = 0; int fail = 0; double minRatio = Double.MAX_VALUE; for (int N : sizes) { Map clusterMap = buildClusterMap(N); List nodes = buildNodesList(N); String leftNodeID = "node-" + (N - 1); long slowOps = slowNodeLeft(clusterMap, nodes, leftNodeID); long fastOps = fastNodeLeft(clusterMap, nodes, leftNodeID); // Expected: slow ≈ triangular number sum of scan depths ≈ N*(N-1)/2 // fast = O(N-1) operations (one per remaining entry, O(1) each) long expectedSlow = (long) N * (N - 1) / 2; // approximate long expectedFast = N - 1; double ratio = (double) slowOps / fastOps; // Verify ordering boolean ok = slowOps > fastOps && ratio >= 5.0; if (ok) { pass++; } else { fail++; System.out.printf("FAIL N=%d: slowOps=%d fastOps=%d ratio=%.1f%n", N, slowOps, fastOps, ratio); } System.out.printf("N=%d: slowOps=%d fastOps=%d ratio=%.1fx [%s]%n", N, slowOps, fastOps, ratio, ok ? "PASS" : "FAIL"); minRatio = Math.min(minRatio, ratio); } System.out.printf("%d/%d PASS (min ratio=%.1fx)%n", pass, pass + fail, minRatio); if (fail > 0) { System.exit(1); } } }