-
Notifications
You must be signed in to change notification settings - Fork 9
Description
Neither does current Allocator
trait exclude Copy
trait nor does it require Drop
trait.
Scenario 1: No implementation of Copy
Only one allocation is allowed in this scenario. The following example won't compile.
let a=AltAlloc::default();
// Compiler will allow this allocation.
let ab1:Box<u32,AltAlloc>=Box::new_in(1234,a);
// Compiler will reject this allocation.
let ab2:Box<u32,AltAlloc>=Box::new_in(4321,a);
Scenario 2: No implementation of Drop
, but Copy
is implemented
This can cause some issues, though the following example will compile.
let a=AltAlloc::default();
let ab1:Box<u32,AltAlloc>=Box::new_in(1234,a);
let ab2:Box<u32,AltAlloc>=Box::new_in(4321,a);
Waste of Resource if AltAlloc
is implemented by slicing huge internal array
Assume AltAlloc
is an array of u8
:
#[derive(Copy,Clone)] struct AltAlloc
{
internal:[u8;0x10000], // Each allocator has 64K bytes.
// Internal members that records allocations.
/**/
}
This AltAlloc
might not need a real Drop
trait implementation. But the Copy
trait will make this allocator to be created more than once. In above example, there are two allocators actually created. In other words, 128K was allocated, rather than 64K.
// Allocated 64K.
let a=AltAlloc::default();
let ab1:Box<u32,AltAlloc>=Box::new_in(1234,a);
// Allocated another 64K.
let ab2:Box<u32,AltAlloc>=Box::new_in(4321,a);
Use After Free
if AltAlloc
is implemented by slicing referenced internal array
Assume AltAlloc
includes a pointer to u8
:
#[derive(Copy,Clone)] struct AltAlloc
{
internal:*mut u8, // Pointer received from `VirtualAlloc`, `mmap`, etc.
// Records of allocations are included inside `internal`.
}
In this case, using Copy
trait is fine because it only copies a pointer. It won't cause repetitive huge memory allocations. However, this AltAlloc
requires something like Drop
trait. Since Copy
trait is implemented, Drop
trait can't be implemented. Assume a destroy
method is implemented:
impl AltAlloc
{
pub unsafe fn destroy(self)
{
/* Internal implementation that calls munmap, VirtualFree, etc. */
}
}
Then it would cause Use-After-Free
while dropping:
let a=AltAlloc::default();
let ab1:Box<u32,AltAlloc>=Box::new_in(1234,a);
let ab2:Box<u32,AltAlloc>=Box::new_in(4321,a);
a.destroy();
// Dropping ab1 and ab2 requires using data in `a.internal`.
// This causes `Use-After-Free` fault.
The only workaround is via scoping:
let a=AltAlloc::default();
{
let ab1:Box<u32,AltAlloc>=Box::new_in(1234,a);
let ab2:Box<u32,AltAlloc>=Box::new_in(4321,a);
// ab1 and ab2 are dropped right before this scope ends.
}
a.destroy();
If Drop
trait is implemented, then destroy
method won't be required.
let a=AltAlloc::default();
let ab1:Box<u32,AltAlloc>=Box::new_in(1234,a);
let ab2:Box<u32,AltAlloc>=Box::new_in(4321,a);
// Ideally, Rust will drop things in the order of: ab2, ab1, a.
However, Copy
trait can't co-exist with Drop
trait. That's because it would cause doubled-free if AltAlloc
contains only a pointer.
Idea: Allocators should get borrowed
If allocators are borrowed, then Copy
trait won't be needed. I think Allocator
trait should exclude Copy
trait.
pub fn new_in(x: T, alloc: &mut A) -> Self
let mut a=AltAlloc::default();
let ab1:Box<u32,AltAlloc>=Box::new_in(1234,&mut a);
let ab2:Box<u32,AltAlloc>=Box::new_in(4321,&mut a);
// Rust will drop things in the order of: ab2, ab1, a.
// This works fine even if `AltAlloc` is implemented via slicing an internal huge array on stack.
Same principle can be applied on String
, Vec
, etc.