package unit; import java.util.*; /** * Unit test for tor-0003: kist_scheduler_run() re-add loop CWE-407. * * Defect: At the end of kist_scheduler_run() (scheduler_kist.c:756), channels * in the `to_readd` list are guarded by smartlist_contains(cp, readd_chan) * before re-insertion into the pending pqueue `cp`. * smartlist_contains() is a linear pointer scan: O(|cp|) per call. * For T channels in to_readd and C channels in cp: O(T * C) total. * The code comment already notes the sched_heap_idx != -1 check is * "in theory redundant with the smartlist_contains check". * * Fix: Remove the O(C) smartlist_contains call. Use only * sched_heap_idx == -1 as the O(1) membership test. * A channel is in cp iff its heap index is set (pqueue invariant). * * Model: * Channel — has a heapIdx field (-1 means not in pqueue) * DefectiveReadd — simulates the slow path: ArrayList.contains() O(C) per channel * FixedReadd — simulates the fast path: heapIdx == -1 check O(1) per channel * * Measurement: count element-level pointer comparisons for each guard check. */ public class TorKistSchedulerTest { // ── Channel model ───────────────────────────────────────────────────────── static class Channel { final int id; int heapIdx; // -1 = not in pqueue Channel(int id) { this.id = id; this.heapIdx = -1; } } // ── Defective: simulates smartlist_contains(cp, readd_chan) ─────────────── /** * Returns number of pointer comparisons performed (ArrayList.contains scan). */ static long defectiveGuard(List cp, Channel readd_chan) { long ops = 0; for (Channel c : cp) { ops++; if (c == readd_chan) { return ops; // found → skip re-add } } return ops; // not found → would re-add } static long runSlow(List cp, List toReadd) { long totalOps = 0; // Simulate: each channel in to_readd that's NOT in cp gets re-added. // We measure the cost of the contains check, not the add itself. Set cpSet = new HashSet<>(cp); for (Channel readd : toReadd) { totalOps += defectiveGuard(cp, readd); // If truly not in cp, it would be added (we skip actual pqueue here) } return totalOps; } // ── Fixed: simulates heapIdx == -1 check ────────────────────────────────── /** * Returns number of comparisons: always 1 (single field read). */ static long fixedGuard(Channel readd_chan) { // O(1): just check the heap index field return 1L; } static long runFast(List toReadd) { long totalOps = 0; for (Channel readd : toReadd) { totalOps += fixedGuard(readd); } return totalOps; } // ── Test data generation ────────────────────────────────────────────────── /** * Build a scenario: C channels in the pending queue, T channels to re-add. * Half the to_readd channels are already in cp (heap idx set); * half are not (heap idx -1). */ static Object[] makeScenario(int C, int T) { List cp = new ArrayList<>(); for (int i = 0; i < C; i++) { Channel c = new Channel(i); c.heapIdx = i; // already in pqueue cp.add(c); } List toReadd = new ArrayList<>(); // Half from cp (already present) for (int i = 0; i < T / 2; i++) { toReadd.add(cp.get(i % C)); } // Half are new channels (heap idx -1) for (int i = 0; i < T - T / 2; i++) { Channel fresh = new Channel(C + i); fresh.heapIdx = -1; toReadd.add(fresh); } return new Object[]{cp, toReadd}; } // ── Main ───────────────────────────────────────────────────────────────── public static void main(String[] args) { int passed = 0; int total = 0; int[][] configs = { {50, 20, 5}, // C=50, T=20, minFactor=5 {200, 50, 5}, // C=200, T=50, minFactor=5 {500, 100, 8}, // C=500, T=100, minFactor=8 {1000,200, 10}, // C=1000,T=200, minFactor=10 }; for (int[] cfg : configs) { int C = cfg[0], T = cfg[1], minFactor = cfg[2]; total++; @SuppressWarnings("unchecked") Object[] scenario = makeScenario(C, T); List cp = (List) scenario[0]; List toReadd = (List) scenario[1]; long slowOps = runSlow(cp, toReadd); long fastOps = runFast(toReadd); boolean ok = slowOps > fastOps * minFactor; System.out.printf("tor-0003 C=%4d T=%3d: slow=%6d ops fast=%4d ops ratio=%.1fx %s%n", C, T, slowOps, fastOps, (double) slowOps / fastOps, ok ? "PASS" : "FAIL"); if (ok) passed++; } System.out.printf("%d/%d PASS%n", passed, total); if (passed != total) System.exit(1); } }