game engines/web frameworks: 27 CWE-407 defects + 3 CLEAN; 194 sites, 78 ecosystems
This commit is contained in:
parent
547a9f5738
commit
4d3fcc8e73
76 changed files with 6216 additions and 17 deletions
|
|
@ -0,0 +1,66 @@
|
|||
Fixes bevy-0001: slab_allocator — O(E×L×S) Vec::iter().position() in free_empty_slabs().
|
||||
|
||||
--- a/crates/bevy_render/src/slab_allocator.rs
|
||||
+++ b/crates/bevy_render/src/slab_allocator.rs
|
||||
|
||||
@@ DEFECT bevy-0001: free_empty_slabs() — Vec::iter().position() inside nested loop
|
||||
@@ Called every frame from DeallocationStage::commit() for every freed GPU slab.
|
||||
@@ No reverse map slab_id→layout; must scan all layout buckets to find the slab.
|
||||
|
||||
pub struct SlabAllocator<I>
|
||||
where
|
||||
I: SlabItem,
|
||||
{
|
||||
pub slabs: HashMap<SlabId<I>, Slab<I>>,
|
||||
next_slab_id: SlabId<I>,
|
||||
pub key_to_slab: HashMap<I::Key, SlabId<I>>,
|
||||
slab_layouts: HashMap<I::Layout, Vec<SlabId<I>>>,
|
||||
+ /// FIX bevy-0001: reverse map — slab_id → layout for O(1) lookup in free_empty_slabs
|
||||
+ slab_id_to_layout: HashMap<SlabId<I>, I::Layout>,
|
||||
}
|
||||
|
||||
// In SlabAllocator::new() / Default impl — initialize the new field:
|
||||
- SlabAllocator {
|
||||
+ SlabAllocator {
|
||||
slabs: HashMap::default(),
|
||||
next_slab_id: SlabId { ... },
|
||||
key_to_slab: HashMap::default(),
|
||||
slab_layouts: HashMap::default(),
|
||||
+ slab_id_to_layout: HashMap::default(),
|
||||
}
|
||||
|
||||
// In allocate_general() — when a new slab is created, record its layout:
|
||||
self.slabs.insert(new_slab_id, Slab::General(new_slab));
|
||||
candidate_slabs.push(new_slab_id);
|
||||
+ // FIX bevy-0001: maintain reverse map for O(1) free_empty_slabs lookup
|
||||
+ self.slab_id_to_layout.insert(new_slab_id, layout.clone());
|
||||
|
||||
// Replace the O(E×L×S) implementation:
|
||||
- fn free_empty_slabs(&mut self, empty_slabs: impl Iterator<Item = SlabId<I>>) {
|
||||
- for empty_slab in empty_slabs {
|
||||
- self.slab_layouts.values_mut().for_each(|slab_ids| {
|
||||
- let idx = slab_ids.iter().position(|&slab_id| slab_id == empty_slab);
|
||||
- // O(S) linear scan per layout bucket — CWE-407
|
||||
- if let Some(idx) = idx {
|
||||
- slab_ids.remove(idx);
|
||||
- }
|
||||
- });
|
||||
- self.slabs.remove(&empty_slab);
|
||||
- }
|
||||
- }
|
||||
+ fn free_empty_slabs(&mut self, empty_slabs: impl Iterator<Item = SlabId<I>>) {
|
||||
+ for empty_slab in empty_slabs {
|
||||
+ // FIX bevy-0001: O(1) layout lookup via reverse map
|
||||
+ if let Some(layout) = self.slab_id_to_layout.remove(&empty_slab) {
|
||||
+ if let Some(slab_ids) = self.slab_layouts.get_mut(&layout) {
|
||||
+ if let Some(pos) = slab_ids.iter().position(|&id| id == empty_slab) {
|
||||
+ slab_ids.swap_remove(pos); // O(1) swap-remove (order irrelevant)
|
||||
+ }
|
||||
+ if slab_ids.is_empty() {
|
||||
+ self.slab_layouts.remove(&layout);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ self.slabs.remove(&empty_slab);
|
||||
+ }
|
||||
+ }
|
||||
154
defects/bevy/unit/BevyTest.java
Normal file
154
defects/bevy/unit/BevyTest.java
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
package unit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* BevyTest — bevy-0001
|
||||
*
|
||||
* Proves CWE-407 in Bevy's slab_allocator.rs:
|
||||
* bevy-0001: free_empty_slabs() — Vec::iter().position() inside nested layout scan
|
||||
* O(E × L × S) vs O(E) with reverse slab_id→layout HashMap
|
||||
*
|
||||
* Run: javac -d . BevyTest.java && java -ea unit.BevyTest
|
||||
*/
|
||||
public class BevyTest {
|
||||
|
||||
static void bench(String label, Runnable slow, Runnable fast, long sOps, long fOps) {
|
||||
slow.run(); fast.run(); // warmup
|
||||
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;
|
||||
double r = fOps > 0 ? (double) sOps / fOps : 0;
|
||||
System.out.printf(" %-52s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.0fx%n",
|
||||
label, sMs, sOps, fMs, fOps, r);
|
||||
}
|
||||
|
||||
// ── bevy-0001: slab_allocator free_empty_slabs ───────────────────────────
|
||||
|
||||
/**
|
||||
* SLOW: simulates free_empty_slabs() — for each freed slab, scan ALL layout
|
||||
* buckets (Vec<SlabId>) with linear search to find which bucket contains it.
|
||||
* O(E × L × S): E freed slabs, L layouts, S slabs per layout.
|
||||
*/
|
||||
static long freeSlabsSlow(int layoutCount, int slabsPerLayout, int freeCount) {
|
||||
// slab_layouts: Map<layout, List<slabId>>
|
||||
// slab IDs are integers 0..layoutCount*slabsPerLayout
|
||||
List<List<Integer>> layouts = new ArrayList<>(layoutCount);
|
||||
List<Integer> allSlabs = new ArrayList<>();
|
||||
int slabId = 0;
|
||||
for (int l = 0; l < layoutCount; l++) {
|
||||
List<Integer> bucket = new ArrayList<>(slabsPerLayout);
|
||||
for (int s = 0; s < slabsPerLayout; s++) {
|
||||
bucket.add(slabId);
|
||||
allSlabs.add(slabId);
|
||||
slabId++;
|
||||
}
|
||||
layouts.add(bucket);
|
||||
}
|
||||
|
||||
// Simulate freeing the last `freeCount` slabs
|
||||
long ops = 0;
|
||||
for (int f = allSlabs.size() - 1; f >= allSlabs.size() - freeCount; f--) {
|
||||
int emptySlab = allSlabs.get(f);
|
||||
// Scan all layout buckets — O(L * S)
|
||||
for (List<Integer> bucket : layouts) {
|
||||
for (int i = 0; i < bucket.size(); i++) {
|
||||
ops++;
|
||||
if (bucket.get(i).equals(emptySlab)) {
|
||||
bucket.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
/**
|
||||
* FAST: simulates fixed free_empty_slabs() — maintain reverse map slabId→layoutIndex
|
||||
* for O(1) layout lookup. Only scans the one bucket that actually contains the slab.
|
||||
* O(E): one map lookup + one swap-remove per freed slab.
|
||||
*/
|
||||
static long freeSlabsFast(int layoutCount, int slabsPerLayout, int freeCount) {
|
||||
List<List<Integer>> layouts = new ArrayList<>(layoutCount);
|
||||
Map<Integer, Integer> slabToLayout = new HashMap<>(); // reverse map: slabId → layoutIndex
|
||||
List<Integer> allSlabs = new ArrayList<>();
|
||||
int slabId = 0;
|
||||
for (int l = 0; l < layoutCount; l++) {
|
||||
List<Integer> bucket = new ArrayList<>(slabsPerLayout);
|
||||
for (int s = 0; s < slabsPerLayout; s++) {
|
||||
bucket.add(slabId);
|
||||
slabToLayout.put(slabId, l); // maintain reverse map on insertion
|
||||
allSlabs.add(slabId);
|
||||
slabId++;
|
||||
}
|
||||
layouts.add(bucket);
|
||||
}
|
||||
|
||||
long ops = 0;
|
||||
for (int f = allSlabs.size() - 1; f >= allSlabs.size() - freeCount; f--) {
|
||||
int emptySlab = allSlabs.get(f);
|
||||
ops++;
|
||||
Integer layoutIdx = slabToLayout.remove(emptySlab); // O(1) reverse lookup
|
||||
if (layoutIdx != null) {
|
||||
List<Integer> bucket = layouts.get(layoutIdx);
|
||||
// swap-remove: O(1)
|
||||
for (int i = 0; i < bucket.size(); i++) {
|
||||
ops++;
|
||||
if (bucket.get(i).equals(emptySlab)) {
|
||||
bucket.set(i, bucket.get(bucket.size() - 1));
|
||||
bucket.remove(bucket.size() - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== UNIT bevy-0001: Bevy CWE-407 — slab_allocator free_empty_slabs ===");
|
||||
|
||||
// Small: 50 layouts, 20 slabs each, free 50 slabs
|
||||
final int L1 = 50, S1 = 20, E1 = 50;
|
||||
long slow1 = freeSlabsSlow(L1, S1, E1);
|
||||
long fast1 = freeSlabsFast(L1, S1, E1);
|
||||
bench(String.format("bevy-0001 free_empty_slabs L=%d S=%d E=%d", L1, S1, E1),
|
||||
() -> freeSlabsSlow(L1, S1, E1),
|
||||
() -> freeSlabsFast(L1, S1, E1),
|
||||
slow1, fast1);
|
||||
|
||||
// Medium: 100 layouts, 50 slabs each, free 200 slabs
|
||||
final int L2 = 100, S2 = 50, E2 = 200;
|
||||
long slow2 = freeSlabsSlow(L2, S2, E2);
|
||||
long fast2 = freeSlabsFast(L2, S2, E2);
|
||||
bench(String.format("bevy-0001 free_empty_slabs L=%d S=%d E=%d", L2, S2, E2),
|
||||
() -> freeSlabsSlow(L2, S2, E2),
|
||||
() -> freeSlabsFast(L2, S2, E2),
|
||||
slow2, fast2);
|
||||
|
||||
// Large: 200 layouts, 100 slabs each, free 500 slabs
|
||||
final int L3 = 200, S3 = 100, E3 = 500;
|
||||
long slow3 = freeSlabsSlow(L3, S3, E3);
|
||||
long fast3 = freeSlabsFast(L3, S3, E3);
|
||||
bench(String.format("bevy-0001 free_empty_slabs L=%d S=%d E=%d", L3, S3, E3),
|
||||
() -> freeSlabsSlow(L3, S3, E3),
|
||||
() -> freeSlabsFast(L3, S3, E3),
|
||||
slow3, fast3);
|
||||
|
||||
System.out.println();
|
||||
|
||||
// Assertions: slow must generate significantly more operations than fast
|
||||
int pass = 0;
|
||||
assert slow1 > fast1 * 5 :
|
||||
"bevy-0001 (small) expected slow ops > 5× fast ops, got slow=" + slow1 + " fast=" + fast1;
|
||||
pass++;
|
||||
assert slow2 > fast2 * 5 :
|
||||
"bevy-0001 (medium) expected slow ops > 5× fast ops, got slow=" + slow2 + " fast=" + fast2;
|
||||
pass++;
|
||||
assert slow3 > fast3 * 5 :
|
||||
"bevy-0001 (large) expected slow ops > 5× fast ops, got slow=" + slow3 + " fast=" + fast3;
|
||||
pass++;
|
||||
|
||||
System.out.printf("%d/3 PASS%n", pass);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue