import java.util.*; /** * CWE-407 simulation: ClickHouseNodes load-balancing faulty-node contains scan * * Defect: ClickHouseLoadBalancingPolicy.FirstAlivePolicy.get() iterates * manager.nodes (LinkedList) and calls manager.faultyNodes.contains(node) * for every iteration — O(N×F) where N = healthy nodes, F = faulty nodes. * * Fix: change nodes and faultyNodes to LinkedHashSet for O(1) contains. */ public class ClickHouseJavaTest { // --- Simulated defect: LinkedList for both collections --- static int simulateDefect(int healthyCount, int faultyCount) { LinkedList nodes = new LinkedList<>(); LinkedList faultyNodes = new LinkedList<>(); for (int i = 0; i < healthyCount; i++) nodes.add("node-" + i); for (int i = 0; i < faultyCount; i++) faultyNodes.add("faulty-" + i); int ops = 0; // Simulate FirstAlivePolicy.get() — finds first healthy non-faulty node for (String n : nodes) { ops++; if (!faultyNodes.contains(n)) { // O(F) per iteration break; } } return ops; } // --- Simulated fix: LinkedHashSet for O(1) contains --- static int simulateFix(int healthyCount, int faultyCount) { LinkedHashSet nodes = new LinkedHashSet<>(); LinkedHashSet faultyNodes = new LinkedHashSet<>(); for (int i = 0; i < healthyCount; i++) nodes.add("node-" + i); for (int i = 0; i < faultyCount; i++) faultyNodes.add("faulty-" + i); int ops = 0; for (String n : nodes) { ops++; if (!faultyNodes.contains(n)) { // O(1) via HashSet break; } } return ops; } // --- Measure wall-clock ops for contains cost comparison --- static long timeDefect(int healthyCount, int faultyCount, int iterations) { LinkedList nodes = new LinkedList<>(); LinkedList faultyNodes = new LinkedList<>(); // Make all healthy nodes also appear in faulty (worst case: always scan all) for (int i = 0; i < healthyCount; i++) nodes.add("node-" + i); // faultyNodes doesn't contain healthy nodes, but is large enough to cost for (int i = 0; i < faultyCount; i++) faultyNodes.add("faulty-" + i); long start = System.nanoTime(); for (int iter = 0; iter < iterations; iter++) { for (String n : nodes) { if (!faultyNodes.contains(n)) break; } } return System.nanoTime() - start; } static long timeFix(int healthyCount, int faultyCount, int iterations) { LinkedHashSet nodes = new LinkedHashSet<>(); LinkedHashSet faultyNodes = new LinkedHashSet<>(); for (int i = 0; i < healthyCount; i++) nodes.add("node-" + i); for (int i = 0; i < faultyCount; i++) faultyNodes.add("faulty-" + i); long start = System.nanoTime(); for (int iter = 0; iter < iterations; iter++) { for (String n : nodes) { if (!faultyNodes.contains(n)) break; } } return System.nanoTime() - start; } public static void main(String[] args) { System.out.println("=== CWE-407: ClickHouseNodes load-balancing faulty-node O(N×F) ==="); System.out.println(); // --- Correctness: ops to find first healthy node are the same --- int HEALTHY = 10, FAULTY = 500; int defectOps = simulateDefect(HEALTHY, FAULTY); int fixOps = simulateFix(HEALTHY, FAULTY); System.out.printf("PASS: defect ops=%d fix ops=%d (same result)%n", defectOps, fixOps); assert defectOps == fixOps : "ops should match"; // --- Performance: warm up then measure --- int ITER = 50_000; // Warm up for (int i = 0; i < 3; i++) { timeDefect(HEALTHY, FAULTY, ITER); timeFix(HEALTHY, FAULTY, ITER); } long defectNs = timeDefect(HEALTHY, FAULTY, ITER); long fixNs = timeFix(HEALTHY, FAULTY, ITER); double ratio = (double) defectNs / fixNs; System.out.printf("Defect (LinkedList.contains): %,d ns over %,d iterations%n", defectNs, ITER); System.out.printf("Fix (HashSet.contains): %,d ns over %,d iterations%n", fixNs, ITER); System.out.printf("Speedup ratio: %.1fx%n", ratio); // --- Complexity demonstration at N=10, F varies --- System.out.println(); System.out.println("Complexity demo (N=10 healthy, varies F faulty):"); System.out.printf(" %-6s %-12s %-12s %-8s%n", "F", "Defect(ns)", "Fix(ns)", "Ratio"); for (int f : new int[]{10, 50, 100, 250, 500, 1000}) { long d = timeDefect(10, f, ITER); long fx = timeFix(10, f, ITER); System.out.printf(" %-6d %-12d %-12d %-8.1f%n", f, d, fx, (double) d / fx); } // --- Assert speedup is meaningful (at least 2x at F=500) --- assert ratio >= 2.0 : String.format("Expected speedup >= 2x, got %.1fx", ratio); System.out.println(); System.out.println("PASS: all assertions satisfied"); } }