package unit; import java.util.*; /** * ValkeyTest — CWE-407 benchmarks for valkey-0001, valkey-0002, valkey-0003 * * valkey-0001: SINTER on listpack-encoded sets * SLOW: O(N×M) — outer iterate N elements, inner lpFind O(M) per probe set * FAST: O(N) — outer iterate N elements, inner HashSet.contains O(1) * * valkey-0002: getUpcomingChannelList ACL channel superset check * SLOW: O((S×C)²) — build flat list, listSearchKey O(n) per pattern * FAST: O(S×C) — build HashSet, O(1) lookup per pattern * * valkey-0003: ACLSetSelector key-pattern/channel deduplication O(P²) * SLOW: O(P²) — listSearchKey(selector->patterns, newpat) called per rule * FAST: O(P) — parallel dict for O(1) dedup per rule */ public class ValkeyTest { // ------------------------------------------------------------------------- // Benchmark harness (matches project style) // ------------------------------------------------------------------------- static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) { slow.run(); fast.run(); long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000; System.out.printf(" %-56s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n", label, sMs, sOps, fMs, fOps, fOps > 0 ? (double) sOps / fOps : 0); } // ========================================================================= // valkey-0001: SINTER listpack O(N×M) // // Models sinterGenericCommand with listpack-encoded inner sets. // "listpack membership" = linear scan (lpFindCbInternal). // "htset membership" = O(1) HashSet lookup. // ========================================================================= /** * Simulate SINTER(set0, set1) where set1 uses listpack encoding. * Returns number of element comparisons performed. * * set0: List to iterate * set1AsArray: String[] simulating a packed listpack (linear scan required) */ static long sinter_slow(List set0, String[] set1) { long ops = 0; for (String elem : set0) { // listpack membership = linear scan from start for (String s : set1) { ops++; if (s.equals(elem)) break; } } return ops; } /** * Simulate SINTER(set0, set1) after promoting set1 to a hash set. * Returns number of element comparisons performed. */ static long sinter_fast(List set0, Set set1) { long ops = 0; for (String elem : set0) { ops++; // O(1) hash lookup set1.contains(elem); } return ops; } // ========================================================================= // valkey-0002: getUpcomingChannelList O((S×C)²) // // Models the upcoming-channel superset check. // SLOW: build a list, scan linearly for each original-selector channel. // FAST: build a HashSet, O(1) lookup. // ========================================================================= /** Returns number of comparisons in the listSearchKey-style scan. */ static long upcomingChannels_slow(List newChannels, List originalChannels) { // Build upcoming as a List (mirrors listAddNodeTail + listSearchKey) List upcoming = new ArrayList<>(newChannels); long ops = 0; for (String orig : originalChannels) { // listSearchKey — O(upcoming.size()) for (String up : upcoming) { ops++; if (up.equals(orig)) break; } } return ops; } /** Returns number of comparisons using a HashSet (the fix). */ static long upcomingChannels_fast(List newChannels, List originalChannels) { Set upcoming = new HashSet<>(newChannels); long ops = newChannels.size(); // one pass to build the set for (String orig : originalChannels) { ops++; // O(1) HashSet lookup upcoming.contains(orig); } return ops; } // ========================================================================= // valkey-0003: ACLSetSelector key-pattern deduplication O(P²) // // Models ACLSetSelector: each ~ rule calls listSearchKey on the // already-accumulated patterns list to avoid duplicates. // Adding P patterns costs O(1+2+...+P) = O(P²). // // SLOW: List-based dedup (mirrors listSearchKey scan) // FAST: HashSet-based dedup (mirrors dict lookup fix) // ========================================================================= /** * Simulate adding P distinct key patterns to selector->patterns using * listSearchKey-style linear scan for deduplication. * Returns total number of comparisons performed. */ static long aclPatternDedup_slow(int numPatterns) { List patterns = new ArrayList<>(); long ops = 0; for (int i = 0; i < numPatterns; i++) { String newpat = "key:" + i; // listSearchKey: O(patterns.size()) scan boolean found = false; for (String p : patterns) { ops++; if (p.equals(newpat)) { found = true; break; } } if (!found) patterns.add(newpat); } return ops; } /** * Simulate adding P distinct key patterns using a parallel dict for O(1) dedup. * Returns total number of comparisons performed. */ static long aclPatternDedup_fast(int numPatterns) { List patterns = new ArrayList<>(); Set patternsHt = new HashSet<>(); long ops = 0; for (int i = 0; i < numPatterns; i++) { String newpat = "key:" + i; ops++; // O(1) dict lookup if (patternsHt.add(newpat)) { patterns.add(newpat); } } return ops; } // ========================================================================= // Main // ========================================================================= public static void main(String[] args) { System.out.println("ValkeyTest — CWE-407"); System.out.println(); int passed = 0, total = 0; // --- valkey-0001 Scenario 1: N=128, M=128 (default listpack threshold) --- { int N = 128, M = 128; List set0 = new ArrayList<>(N); String[] set1Array = new String[M]; Set set1Set = new HashSet<>(); // set0 and set1 share half their elements (worst-case: all must be scanned) for (int i = 0; i < N; i++) set0.add("elem:" + i); for (int i = 0; i < M; i++) { set1Array[i] = "elem:" + (i + N / 2); // partial overlap set1Set.add(set1Array[i]); } final long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int r = 0; r < 1000; r++) ops += sinter_slow(set0, set1Array); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int r = 0; r < 1000; r++) ops += sinter_fast(set0, set1Set); fOps[0] = ops; }; slow.run(); fast.run(); bench("valkey-0001 SINTER N=128 M=128 listpack (1k runs)", slow, fast, sOps[0], fOps[0]); total++; // Expect at least 50x speedup (theoretical max 128x, partial overlap ~64x) boolean ok = sOps[0] >= fOps[0] * 50; System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.0fx (need >=50x)%n", ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]); if (ok) passed++; } // --- valkey-0001 Scenario 2: N=128, M=128, no overlap (worst case: full scan) --- { int N = 128, M = 128; List set0 = new ArrayList<>(N); String[] set1Array = new String[M]; Set set1Set = new HashSet<>(); for (int i = 0; i < N; i++) set0.add("a:" + i); for (int i = 0; i < M; i++) { set1Array[i] = "b:" + i; set1Set.add(set1Array[i]); } final long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int r = 0; r < 1000; r++) ops += sinter_slow(set0, set1Array); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int r = 0; r < 1000; r++) ops += sinter_fast(set0, set1Set); fOps[0] = ops; }; slow.run(); fast.run(); bench("valkey-0001 SINTER N=128 M=128 no-overlap (1k runs)", slow, fast, sOps[0], fOps[0]); total++; // No overlap: every element scans all M=128 entries → 128×128=16384 vs 128 boolean ok = sOps[0] >= fOps[0] * 100; System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.0fx (need >=100x)%n", ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]); if (ok) passed++; } // --- valkey-0002 Scenario 1: S=4 selectors, C=50 channels each --- { int S = 4, C = 50; List newChannels = new ArrayList<>(); List origChannels = new ArrayList<>(); for (int s = 0; s < S; s++) for (int c = 0; c < C; c++) newChannels.add("chan:" + s + ":" + c); // Original has same S×C channels (full overlap — must check all) origChannels.addAll(newChannels); Collections.shuffle(origChannels, new Random(42)); final long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int r = 0; r < 500; r++) ops += upcomingChannels_slow(newChannels, origChannels); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int r = 0; r < 500; r++) ops += upcomingChannels_fast(newChannels, origChannels); fOps[0] = ops; }; slow.run(); fast.run(); bench("valkey-0002 ACL channels S=4 C=50 full-overlap (500r)", slow, fast, sOps[0], fOps[0]); total++; // 200 channels: slow ~200*100 avg = 20000 per call; fast = 200+200 = 400 boolean ok = sOps[0] >= fOps[0] * 20; System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.0fx (need >=20x)%n", ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]); if (ok) passed++; } // --- valkey-0002 Scenario 2: S=10, C=100 — heavy compartmentalization --- { int S = 10, C = 100; List newChannels = new ArrayList<>(); List origChannels = new ArrayList<>(); for (int s = 0; s < S; s++) for (int c = 0; c < C; c++) newChannels.add("ch:" + s + ":" + c); origChannels.addAll(newChannels); Collections.shuffle(origChannels, new Random(99)); final long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int r = 0; r < 100; r++) ops += upcomingChannels_slow(newChannels, origChannels); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int r = 0; r < 100; r++) ops += upcomingChannels_fast(newChannels, origChannels); fOps[0] = ops; }; slow.run(); fast.run(); bench("valkey-0002 ACL channels S=10 C=100 (100 runs)", slow, fast, sOps[0], fOps[0]); total++; // 1000 channels: slow avg ~500 per lookup * 1000 checks = 500k; fast = 2000 boolean ok = sOps[0] >= fOps[0] * 100; System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.0fx (need >=100x)%n", ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]); if (ok) passed++; } // --- valkey-0003 Scenario 1: P=500 distinct key patterns --- { int P = 500; final long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int r = 0; r < 200; r++) ops += aclPatternDedup_slow(P); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int r = 0; r < 200; r++) ops += aclPatternDedup_fast(P); fOps[0] = ops; }; slow.run(); fast.run(); bench("valkey-0003 ACL key-pattern dedup P=500 (200 runs)", slow, fast, sOps[0], fOps[0]); total++; // P=500: slow = sum(0..499) * 200 = 24,950,000; fast = 500*200 = 100,000 → ~249x boolean ok = sOps[0] >= fOps[0] * 100; System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.0fx (need >=100x)%n", ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]); if (ok) passed++; } // --- valkey-0003 Scenario 2: P=1000 distinct key patterns --- { int P = 1000; final long[] sOps = {0}, fOps = {0}; Runnable slow = () -> { long ops = 0; for (int r = 0; r < 50; r++) ops += aclPatternDedup_slow(P); sOps[0] = ops; }; Runnable fast = () -> { long ops = 0; for (int r = 0; r < 50; r++) ops += aclPatternDedup_fast(P); fOps[0] = ops; }; slow.run(); fast.run(); bench("valkey-0003 ACL key-pattern dedup P=1000 (50 runs)", slow, fast, sOps[0], fOps[0]); total++; // P=1000: slow = sum(0..999) * 50 = 24,975,000; fast = 1000*50 = 50,000 → ~499x boolean ok = sOps[0] >= fOps[0] * 200; System.out.printf(" [%s] slow=%,d fast=%,d ratio=%.0fx (need >=200x)%n", ok ? "PASS" : "FAIL", sOps[0], fOps[0], (double) sOps[0] / fOps[0]); if (ok) passed++; } System.out.println(); System.out.printf("%d/%d PASS%n", passed, total); if (passed < total) System.exit(1); } }