fix some memory leaks detected by miri (#4959)
The first leak:
```rust
    #[test]
    fn blob_vec_drop_empty_capacity() {
        let item_layout = Layout:🆕:<Foo>();
        let drop = drop_ptr::<Foo>;
        let _ = unsafe { BlobVec::new(item_layout, Some(drop), 0) };
    }
```
this is because we allocate the swap scratch in blobvec regardless of what the capacity is, but we only deallocate if capacity is > 0
The second leak:
```rust
    #[test]
    fn panic_while_overwriting_component() {
        let helper = DropTestHelper::new();
        let res = panic::catch_unwind(|| {
            let mut world = World::new();
            world
                .spawn()
                .insert(helper.make_component(true, 0))
                .insert(helper.make_component(false, 1));
            println!("Done inserting! Dropping world...");
        });
        let drop_log = helper.finish(res);
        assert_eq!(
            &*drop_log,
            [
                DropLogItem::Create(0),
                DropLogItem::Create(1),
                DropLogItem::Drop(0),
            ]
        );
    }
```
this is caused by us not running the drop impl on the to-be-inserted component if the drop impl of the overwritten component panics
---
managed to figure out where the leaks were by using this 10/10 command
```
cargo --quiet test --lib -- --list | sed 's/: test$//' | MIRIFLAGS="-Zmiri-disable-isolation" xargs -n1 cargo miri test --lib -- --exact
```
which runs every test one by one rather than all at once which let miri actually tell me which test had the leak 🙃
			
			
This commit is contained in:
		
							parent
							
								
									cdbabb7053
								
							
						
					
					
						commit
						a1a07945d6
					
				
							
								
								
									
										5
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @ -99,11 +99,10 @@ jobs: | ||||
|           RUSTFLAGS: -Zrandomize-layout | ||||
|           # https://github.com/rust-lang/miri#miri--z-flags-and-environment-variables | ||||
|           # -Zmiri-disable-isolation is needed because our executor uses `fastrand` which accesses system time. | ||||
|           # -Zmiri-ignore-leaks is needed because running bevy_ecs tests finds a memory leak but its impossible | ||||
|           # to track down because allocids are nondeterministic. | ||||
|           # -Zmiri-permissive-provenance disables warnings against int2ptr casts (since those are used by once_cell) | ||||
|           # -Zmiri-disable-weak-memory-emulation works around https://github.com/bevyengine/bevy/issues/5164. | ||||
|           MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-ignore-leaks -Zmiri-permissive-provenance -Zmiri-disable-weak-memory-emulation | ||||
|           # -Zmiri-ignore-leaks is necessary because a bunch of tests don't join all threads before finishing. | ||||
|           MIRIFLAGS: -Zmiri-ignore-leaks -Zmiri-disable-isolation -Zmiri-permissive-provenance -Zmiri-disable-weak-memory-emulation | ||||
| 
 | ||||
|   check-compiles: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
| @ -151,8 +151,20 @@ impl BlobVec { | ||||
|         let ptr = self.get_unchecked_mut(index).promote().as_ptr(); | ||||
|         self.len = 0; | ||||
|         // Drop the old value, then write back, justifying the promotion
 | ||||
|         // If the drop impl for the old value panics then we run the drop impl for `value` too.
 | ||||
|         if let Some(drop) = self.drop { | ||||
|             struct OnDrop<F: FnMut()>(F); | ||||
|             impl<F: FnMut()> Drop for OnDrop<F> { | ||||
|                 fn drop(&mut self) { | ||||
|                     (self.0)(); | ||||
|                 } | ||||
|             } | ||||
|             let value = value.as_ptr(); | ||||
|             let on_unwind = OnDrop(|| (drop)(OwningPtr::new(NonNull::new_unchecked(value)))); | ||||
| 
 | ||||
|             (drop)(OwningPtr::new(NonNull::new_unchecked(ptr))); | ||||
| 
 | ||||
|             core::mem::forget(on_unwind); | ||||
|         } | ||||
|         std::ptr::copy_nonoverlapping::<u8>(value.as_ptr(), ptr, self.item_layout.size()); | ||||
|         self.len = old_len; | ||||
| @ -304,12 +316,16 @@ impl BlobVec { | ||||
| impl Drop for BlobVec { | ||||
|     fn drop(&mut self) { | ||||
|         self.clear(); | ||||
|         if self.item_layout.size() > 0 { | ||||
|             unsafe { | ||||
|                 std::alloc::dealloc(self.swap_scratch.as_ptr(), self.item_layout); | ||||
|             } | ||||
|         } | ||||
|         let array_layout = | ||||
|             array_layout(&self.item_layout, self.capacity).expect("array layout should be valid"); | ||||
|         if array_layout.size() > 0 { | ||||
|             unsafe { | ||||
|                 std::alloc::dealloc(self.get_ptr_mut().as_ptr(), array_layout); | ||||
|                 std::alloc::dealloc(self.swap_scratch.as_ptr(), self.item_layout); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -1693,6 +1693,7 @@ mod tests { | ||||
|                 DropLogItem::Create(0), | ||||
|                 DropLogItem::Create(1), | ||||
|                 DropLogItem::Drop(0), | ||||
|                 DropLogItem::Drop(1), | ||||
|             ] | ||||
|         ); | ||||
|     } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Boxy
						Boxy