bevy
bevy copied to clipboard
fix mutable aliases for a very short time if `WorldCell` is already borrowed
Objective
Consider the test
let cell = world.cell();
let _value_a = cell.resource_mut::<A>();
let _value_b = cell.resource_mut::<A>();
Currently, this will roughly execute
// first call
let value = unsafe {
self.world
.get_non_send_unchecked_mut_with_id(component_id)?
};
return Some(WorldBorrowMut::new(value, archetype_component_id, self.access)))
// second call
let value = unsafe {
self.world
.get_non_send_unchecked_mut_with_id(component_id)?
};
return Some(WorldBorrowMut::new(value, archetype_component_id, self.access)))
where WorldBorrowMut::new will panic if the resource is already borrowed.
This means, that _value_a will be created, the access checked (OK), then value_b will be created, and the access checked (panic).
For a moment, both _value_a and _value_b existed as &mut T to the same location, which is insta-UB as far as I understand it.
Solution
Flip the order so that WorldBorrowMut::new first checks the access, then fetches creates the value. To do that, we pass a impl FnOnce() -> Mut<T> instead of the Mut<T> directly:
let get_value = || unsafe {
self.world
.get_non_send_unchecked_mut_with_id(component_id)?
};
return Some(WorldBorrowMut::new(get_value, archetype_component_id, self.access)))