java-topology/defects/redis/unit/RedisTest.java
russell@unturf.com 9934133dcf whitepaper: 312 sites / 151 ecosystems — wave2+3 defect tables and PDF rebuild
Add 88 new defect entries to HIGH and MEDIUM tables:
  HIGH: mysql-0001/0002, mariadb-0001, redis-0001/0002, valkey-0001/0002, openvpn-0001,
        vlc-0001, prometheus-0001, otel-collector-0001, cockroachdb-0001..0004,
        tidb-0001..0008, kubernetes-0001/0002, go-0001, kotlin-0002, scala-0001,
        allegro5-0001, sdl2-0001, grafana-0001, clickhouse-0001, duckdb-0001,
        mongodb-0001, envoy-0001, istio-0001, cilium-0001, linkerd2-0001,
        linux-0001/0002/0003, tor-0002/0003, curl-0001, julia-0001, lua-0001,
        perl5-0001, nats-0001, spring-0003/0004, tomcat-0001, onos-0002, odl-0002

  MEDIUM: helm-0001, mariadb-0002, openssl-0001/0002, memcached-0001,
          cassandra-0001..0004, flink-0001, storm-0001/0002, zookeeper-0001..0003,
          pip-0001, gradle-0001, nginx-0001, haproxy-0001, caddy-0001, varnish-0001,
          ffmpeg-0001, gstreamer-0001, raylib-0001, love2d-0001, php-0001/0002,
          r-source-0001, cpython-0002, ruby-0001, rabbitmq-0003/0004, activemq-0001,
          ovs-0001, onos-0003, odl-0002, jetty-0001

PDF: 976K
2026-03-27 15:23:43 -04:00

255 lines
10 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.*;
/**
* RedisTest — CWE-407 benchmarks for redis-0001 and redis-0002
*
* redis-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)
*
* redis-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
*/
public class RedisTest {
// -------------------------------------------------------------------------
// 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);
}
// =========================================================================
// redis-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<String> to iterate
* set1AsArray: String[] simulating a packed listpack (linear scan required)
*/
static long sinter_slow(List<String> 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<String> set0, Set<String> set1) {
long ops = 0;
for (String elem : set0) {
ops++; // O(1) hash lookup
set1.contains(elem);
}
return ops;
}
// =========================================================================
// redis-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<String> newChannels, List<String> originalChannels) {
// Build upcoming as a List (mirrors listAddNodeTail + listSearchKey)
List<String> 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<String> newChannels, List<String> originalChannels) {
Set<String> 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;
}
// =========================================================================
// Main
// =========================================================================
public static void main(String[] args) {
System.out.println("RedisTest — CWE-407");
System.out.println();
int passed = 0, total = 0;
// --- redis-0001 Scenario 1: N=128, M=128 (default listpack threshold) ---
{
int N = 128, M = 128;
List<String> set0 = new ArrayList<>(N);
String[] set1Array = new String[M];
Set<String> 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("redis-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++;
}
// --- redis-0001 Scenario 2: N=128, M=128, no overlap (worst case: full scan) ---
{
int N = 128, M = 128;
List<String> set0 = new ArrayList<>(N);
String[] set1Array = new String[M];
Set<String> 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("redis-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++;
}
// --- redis-0002 Scenario 1: S=4 selectors, C=50 channels each ---
{
int S = 4, C = 50;
List<String> newChannels = new ArrayList<>();
List<String> 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("redis-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++;
}
// --- redis-0002 Scenario 2: S=10, C=100 — heavy compartmentalization ---
{
int S = 10, C = 100;
List<String> newChannels = new ArrayList<>();
List<String> 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("redis-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++;
}
System.out.println();
System.out.printf("%d/%d PASS%n", passed, total);
if (passed < total) System.exit(1);
}
}