wave6a/6b: elasticsearch/opensearch/solr/llvm-0004-5/linux-0005-6/gcc-0002/tokio/actix-web + love2d/raylib new defects
This commit is contained in:
parent
a4b0cf4edd
commit
3eebda37e1
38 changed files with 3651 additions and 0 deletions
|
|
@ -0,0 +1,116 @@
|
|||
# raylib-0002: LoadRandomSequence O(n²) dedup — CWE-407
|
||||
|
||||
**Severity:** MEDIUM
|
||||
**File:** `src/rcore.c`
|
||||
**Function:** `LoadRandomSequence()`
|
||||
**Lines:** ~1788–1803
|
||||
|
||||
## Description
|
||||
|
||||
`LoadRandomSequence()` generates a sequence of `count` unique random integers
|
||||
in [min, max]. The fallback path (when `SUPPORT_RPRAND_GENERATOR` is not
|
||||
defined) uses a classic rejection-sampling loop with a nested linear scan:
|
||||
|
||||
```c
|
||||
for (int i = 0; i < (int)count;)
|
||||
{
|
||||
value = GetRandomValue(min, max);
|
||||
dupValue = false;
|
||||
|
||||
for (int j = 0; j < i; j++) // O(i) per attempt — CWE-407
|
||||
{
|
||||
if (values[j] == value)
|
||||
{
|
||||
dupValue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dupValue) { values[i] = value; i++; }
|
||||
}
|
||||
```
|
||||
|
||||
Total inner iterations: O(n²/2) expected. At n=1000 this is ~500 000 comparisons.
|
||||
At n=10 000 this is ~50 000 000 comparisons.
|
||||
|
||||
## Fix
|
||||
|
||||
Replace the `values[]` linear scan with a `bool seen[]` bitmap (range is
|
||||
bounded by [min, max]) for O(1) membership:
|
||||
|
||||
```c
|
||||
// FIX raylib-0002: O(1) membership via boolean seen-array — CWE-407
|
||||
int range = abs(max - min) + 1;
|
||||
bool *seen = (bool *)RL_CALLOC(range, sizeof(bool));
|
||||
|
||||
for (int i = 0; i < (int)count;)
|
||||
{
|
||||
value = GetRandomValue(min, max);
|
||||
int idx = value - min;
|
||||
if (!seen[idx]) // O(1)
|
||||
{
|
||||
seen[idx] = true;
|
||||
values[i] = value;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
RL_FREE(seen);
|
||||
```
|
||||
|
||||
Complexity: O(n) expected (geometric distribution, not O(n²)).
|
||||
|
||||
## Speedup
|
||||
|
||||
| n | Before (inner iters) | After (O(1) checks) | Ratio |
|
||||
|-------|---------------------|---------------------|--------|
|
||||
| 100 | ~2 500 | ~100 | ~25× |
|
||||
| 1 000 | ~250 000 | ~1 000 | ~250× |
|
||||
| 10 000| ~25 000 000 | ~10 000 | ~2500× |
|
||||
|
||||
## Patch
|
||||
|
||||
```diff
|
||||
--- a/src/rcore.c
|
||||
+++ b/src/rcore.c
|
||||
@@ -1788,19 +1788,24 @@ int *LoadRandomSequence(unsigned int count, int min, int max)
|
||||
values = (int *)RL_CALLOC(count, sizeof(int));
|
||||
|
||||
- int value = 0;
|
||||
- bool dupValue = false;
|
||||
-
|
||||
- for (int i = 0; i < (int)count;)
|
||||
- {
|
||||
- value = GetRandomValue(min, max);
|
||||
- dupValue = false;
|
||||
-
|
||||
- for (int j = 0; j < i; j++)
|
||||
- {
|
||||
- if (values[j] == value)
|
||||
- {
|
||||
- dupValue = true;
|
||||
- break;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- if (!dupValue)
|
||||
- {
|
||||
- values[i] = value;
|
||||
- i++;
|
||||
- }
|
||||
- }
|
||||
+ // FIX raylib-0002: replace O(n) inner scan with O(1) boolean bitmap — CWE-407
|
||||
+ int range = abs(max - min) + 1;
|
||||
+ bool *seen = (bool *)RL_CALLOC(range, sizeof(bool));
|
||||
+ for (int i = 0; i < (int)count;)
|
||||
+ {
|
||||
+ int value = GetRandomValue(min, max);
|
||||
+ int idx = value - min;
|
||||
+ if (!seen[idx])
|
||||
+ {
|
||||
+ seen[idx] = true;
|
||||
+ values[i] = value;
|
||||
+ i++;
|
||||
+ }
|
||||
+ }
|
||||
+ RL_FREE(seen);
|
||||
```
|
||||
157
defects/raylib/unit/RaylibRandomSequenceTest.java
Normal file
157
defects/raylib/unit/RaylibRandomSequenceTest.java
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue