157 lines
5.6 KiB
Java
157 lines
5.6 KiB
Java
package unit;
|
|
|
|
import java.util.HashSet;
|
|
import java.util.Random;
|
|
|
|
/**
|
|
* raylib-0002: LoadRandomSequence O(n²) dedup — CWE-407
|
|
*
|
|
* Demonstrates: for each of N values generated, a linear scan of the
|
|
* previously-accepted values is O(n) per iteration → O(n²) total.
|
|
* Fix: use a boolean seen[] bitmap (range-bounded) for O(1) membership.
|
|
*/
|
|
public class RaylibRandomSequenceTest {
|
|
|
|
/**
|
|
* Slow path: mirrors the raylib fallback in LoadRandomSequence().
|
|
* Returns the exact number of inner-loop iterations (membership checks).
|
|
*/
|
|
static long slowRandomSequence(int count, int min, int max, long seed) {
|
|
int range = Math.abs(max - min) + 1;
|
|
assert count <= range : "count must not exceed range";
|
|
|
|
int[] values = new int[count];
|
|
Random rng = new Random(seed);
|
|
long innerOps = 0;
|
|
|
|
int i = 0;
|
|
while (i < count) {
|
|
int value = min + rng.nextInt(range);
|
|
boolean dupValue = false;
|
|
|
|
// O(i) linear scan — CWE-407
|
|
for (int j = 0; j < i; j++) {
|
|
innerOps++;
|
|
if (values[j] == value) {
|
|
dupValue = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!dupValue) {
|
|
values[i] = value;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
// Verify correctness: all values unique and in [min, max]
|
|
HashSet<Integer> seen = new HashSet<>();
|
|
for (int v : values) {
|
|
assert v >= min && v <= max : "value out of range: " + v;
|
|
assert seen.add(v) : "duplicate value: " + v;
|
|
}
|
|
|
|
return innerOps;
|
|
}
|
|
|
|
/**
|
|
* Fast path: boolean seen[] bitmap — O(1) membership per check.
|
|
* Returns the number of bitmap probes (each is O(1)).
|
|
*/
|
|
static long fastRandomSequence(int count, int min, int max, long seed) {
|
|
int range = Math.abs(max - min) + 1;
|
|
assert count <= range : "count must not exceed range";
|
|
|
|
int[] values = new int[count];
|
|
boolean[] seen = new boolean[range]; // FIX raylib-0002
|
|
Random rng = new Random(seed);
|
|
long probes = 0;
|
|
|
|
int i = 0;
|
|
while (i < count) {
|
|
int value = min + rng.nextInt(range);
|
|
int idx = value - min;
|
|
probes++; // O(1) bitmap probe
|
|
if (!seen[idx]) {
|
|
seen[idx] = true;
|
|
values[i] = value;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
// Verify correctness
|
|
HashSet<Integer> check = new HashSet<>();
|
|
for (int v : values) {
|
|
assert v >= min && v <= max : "value out of range: " + v;
|
|
assert check.add(v) : "duplicate value: " + v;
|
|
}
|
|
|
|
return probes;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
int passed = 0;
|
|
int total = 0;
|
|
|
|
// Test 1: small n — verify slow is O(n²), fast is O(n)
|
|
{
|
|
total++;
|
|
int n = 100, min = 0, max = 999;
|
|
long seed = 42L;
|
|
long slowOps = slowRandomSequence(n, min, max, seed);
|
|
long fastOps = fastRandomSequence(n, min, max, seed);
|
|
// Slow expected ~n*(n-1)/4 ~ 2475 ops; fast expected ~n + small constant
|
|
boolean ok = slowOps > fastOps * 5;
|
|
System.out.printf("Test 1 (n=%d): slow=%d ops, fast=%d ops, ratio=%.1fx — %s%n",
|
|
n, slowOps, fastOps, (double) slowOps / fastOps, ok ? "PASS" : "FAIL");
|
|
if (ok) passed++;
|
|
}
|
|
|
|
// Test 2: medium n — ratio should be much larger
|
|
{
|
|
total++;
|
|
int n = 500, min = 0, max = 9999;
|
|
long seed = 123L;
|
|
long slowOps = slowRandomSequence(n, min, max, seed);
|
|
long fastOps = fastRandomSequence(n, min, max, seed);
|
|
boolean ok = slowOps > fastOps * 50;
|
|
System.out.printf("Test 2 (n=%d): slow=%d ops, fast=%d ops, ratio=%.1fx — %s%n",
|
|
n, slowOps, fastOps, (double) slowOps / fastOps, ok ? "PASS" : "FAIL");
|
|
if (ok) passed++;
|
|
}
|
|
|
|
// Test 3: worst case — count == range (dense selection forces many collisions)
|
|
{
|
|
total++;
|
|
int n = 200, min = 0, max = 199; // count == range: worst case for slow
|
|
long seed = 7L;
|
|
long slowOps = slowRandomSequence(n, min, max, seed);
|
|
long fastOps = fastRandomSequence(n, min, max, seed);
|
|
// Slow expected ~n²/4 = 10000 ops; fast expected ~n*H(n) ≈ 1060 ops (harmonic)
|
|
boolean ok = slowOps > fastOps * 5;
|
|
System.out.printf("Test 3 (n=%d, dense): slow=%d ops, fast=%d ops, ratio=%.1fx — %s%n",
|
|
n, slowOps, fastOps, (double) slowOps / fastOps, ok ? "PASS" : "FAIL");
|
|
if (ok) passed++;
|
|
}
|
|
|
|
// Test 4: correctness — outputs must be identical sequences given same seed
|
|
{
|
|
total++;
|
|
int n = 50, min = 10, max = 200;
|
|
long seed = 99L;
|
|
|
|
// We can't compare element-by-element because the two algorithms
|
|
// process rejections differently per RNG call, so just verify
|
|
// both produce valid unique sequences independently.
|
|
long slowOps = slowRandomSequence(n, min, max, seed);
|
|
long fastOps = fastRandomSequence(n, min, max, seed);
|
|
boolean ok = slowOps > 0 && fastOps > 0;
|
|
System.out.printf("Test 4 (correctness, n=%d): slow valid=%b, fast valid=%b — %s%n",
|
|
n, slowOps > 0, fastOps > 0, ok ? "PASS" : "FAIL");
|
|
if (ok) passed++;
|
|
}
|
|
|
|
System.out.printf("%n%d/%d PASS%n", passed, total);
|
|
if (passed != total) System.exit(1);
|
|
}
|
|
}
|