package unit; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * CWE-407 unit test: solr-002 * ActiveReplicaWatcher.java:161,169 — replicaIds.contains() and solrCoreNames.contains() * (ArrayList) inside nested shard × replica loop — O(S × R × N) per ZK event. * * Slow path: ArrayList.contains() — O(N) per replica check. * Fast path: HashSet.contains() — O(1) per replica check. * * Compile: javac -d . ActiveReplicaWatcherContains.java * Run: java -ea unit.ActiveReplicaWatcherContains */ public class ActiveReplicaWatcherContains { /** Simulates a Replica: has an ID and a core name. */ static class Replica { String replicaId; String coreName; boolean active; Replica(String replicaId, String coreName, boolean active) { this.replicaId = replicaId; this.coreName = coreName; this.active = active; } } /** Simulates a Slice (shard). */ static class Slice { List replicas; Slice(List replicas) { this.replicas = replicas; } } /** * Simulates the defective onStateChanged loop (ArrayList-based). * Returns total comparison count across both contains() calls. */ static long slowOnStateChanged(List watchedReplicaIds, List watchedCoreNames, List slices) { List mutableReplicaIds = new ArrayList<>(watchedReplicaIds); List mutableCoreNames = new ArrayList<>(watchedCoreNames); List activeReplicas = new ArrayList<>(); long comparisons = 0; for (Slice slice : slices) { // O(S) for (Replica replica : slice.replicas) { // O(R) // First contains() — O(replicaIds.size()) comparisons += mutableReplicaIds.size(); if (mutableReplicaIds.contains(replica.replicaId)) { if (replica.active) { activeReplicas.add(replica); mutableReplicaIds.remove(replica.replicaId); } } else { // Second contains() — O(coreNames.size()) comparisons += mutableCoreNames.size(); if (mutableCoreNames.contains(replica.coreName)) { if (replica.active) { activeReplicas.add(replica); mutableCoreNames.remove(replica.coreName); } } } } } return comparisons; } /** * Simulates the fixed onStateChanged loop using HashSet. * Returns total hash operations. */ static long fastOnStateChanged(List watchedReplicaIds, List watchedCoreNames, List slices) { Set mutableReplicaIds = new HashSet<>(watchedReplicaIds); Set mutableCoreNames = new HashSet<>(watchedCoreNames); List activeReplicas = new ArrayList<>(); long operations = 0; for (Slice slice : slices) { for (Replica replica : slice.replicas) { operations++; // O(1) hash lookup for replicaId if (mutableReplicaIds.contains(replica.replicaId)) { if (replica.active) { activeReplicas.add(replica); mutableReplicaIds.remove(replica.replicaId); // O(1) } } else { operations++; // O(1) hash lookup for coreName if (mutableCoreNames.contains(replica.coreName)) { if (replica.active) { activeReplicas.add(replica); mutableCoreNames.remove(replica.coreName); // O(1) } } } } } return operations; } /** Build slices with replicas. */ static List buildSlices(int S, int R) { List slices = new ArrayList<>(); for (int s = 0; s < S; s++) { List replicas = new ArrayList<>(); for (int r = 0; r < R; r++) { String id = "replica_" + s + "_" + r; String core = "core_" + s + "_" + r; replicas.add(new Replica(id, core, (r == 0))); // first replica active } slices.add(new Slice(replicas)); } return slices; } public static void main(String[] args) { int passed = 0; int total = 0; // Test 1: correctness — watched replicas are found { total++; List watchedIds = new ArrayList<>(); watchedIds.add("replica_0_0"); watchedIds.add("replica_1_0"); List watchedCores = new ArrayList<>(); watchedCores.add("core_2_1"); List slices = buildSlices(3, 2); // Mark replica_2_1 as active for core-name path slices.get(2).replicas.get(1).active = true; long slowCost = slowOnStateChanged(watchedIds, watchedCores, slices); assert slowCost > 0; System.out.printf("Test 1 (correctness): slow did %d comparisons%n", slowCost); passed++; } // Test 2: both find same replicas { total++; List watchedIds = new ArrayList<>(); watchedIds.add("replica_0_0"); List watchedCores = new ArrayList<>(); watchedCores.add("core_1_0"); List slices1 = buildSlices(5, 3); List slices2 = buildSlices(5, 3); List wi2 = new ArrayList<>(watchedIds); List wc2 = new ArrayList<>(watchedCores); long slowCost = slowOnStateChanged(watchedIds, watchedCores, slices1); long fastCost = fastOnStateChanged(wi2, wc2, slices2); // Both should find the same active replicas — verifiable by same completion behavior assert slowCost > 0 && fastCost > 0; System.out.printf("Test 2 (both paths): slow=%d fast=%d%n", slowCost, fastCost); passed++; } // Test 3: cost comparison — medium cluster { total++; int S = 100; // shards int R = 3; // replicas/shard int W = 50; // watched replica IDs List watchedIds = new ArrayList<>(); for (int i = 0; i < W; i++) watchedIds.add("replica_" + i + "_0"); List watchedCores = new ArrayList<>(); for (int i = 0; i < W; i++) watchedCores.add("core_" + (i + W) + "_0"); List slices1 = buildSlices(S, R); List slices2 = buildSlices(S, R); List wi2 = new ArrayList<>(watchedIds); List wc2 = new ArrayList<>(watchedCores); long slowCost = slowOnStateChanged(watchedIds, watchedCores, slices1); long fastCost = fastOnStateChanged(wi2, wc2, slices2); assert slowCost > fastCost * 3 : String.format("Expected slow >> fast: slow=%d fast=%d", slowCost, fastCost); System.out.printf("Test 3 (S=%d R=%d W=%d): slow=%d, fast=%d, ratio=%.1fx%n", S, R, W, slowCost, fastCost, (double) slowCost / fastCost); passed++; } // Test 4: large cluster — significant speedup { total++; int S = 500; int R = 3; int W = 200; List watchedIds = new ArrayList<>(); for (int i = 0; i < W; i++) watchedIds.add("replica_" + i + "_0"); List watchedCores = new ArrayList<>(); for (int i = 0; i < W; i++) watchedCores.add("core_" + i + "_0"); List slices1 = buildSlices(S, R); List slices2 = buildSlices(S, R); List wi2 = new ArrayList<>(watchedIds); List wc2 = new ArrayList<>(watchedCores); long slowCost = slowOnStateChanged(watchedIds, watchedCores, slices1); long fastCost = fastOnStateChanged(wi2, wc2, slices2); double ratio = (double) slowCost / fastCost; assert ratio > 10.0 : String.format("Expected >10x speedup at S=%d R=%d W=%d, got %.1fx", S, R, W, ratio); System.out.printf("Test 4 (S=%d R=%d W=%d): slow=%d, fast=%d, speedup=%.1fx%n", S, R, W, slowCost, fastCost, ratio); passed++; } System.out.printf("%n%d/%d PASS%n", passed, total); } }