Skip to content

Commit 39e14a4

Browse files
Make EntityRef::new unsafe (#7222)
# Objective - We rely on the construction of `EntityRef` to be valid elsewhere in unsafe code. This construction is not checked (for performance reasons), and thus this private method must be unsafe. - Fixes #7218. ## Solution - Make the method unsafe. - Add safety docs. - Improve safety docs slightly for the sibling `EntityMut::new`. - Add debug asserts to start to verify these assumptions in debug mode. ## Context for reviewers I attempted to verify the `EntityLocation` more thoroughly, but this turned out to be more work than expected. I've spun that off into #7221 as a result.
1 parent e44990a commit 39e14a4

File tree

3 files changed

+30
-6
lines changed

3 files changed

+30
-6
lines changed

crates/bevy_ecs/src/entity/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@ impl EntityMeta {
747747
// SAFETY:
748748
// This type must not contain any pointers at any level, and be safe to fully fill with u8::MAX.
749749
/// A location of an entity in an archetype.
750-
#[derive(Copy, Clone, Debug)]
750+
#[derive(Copy, Clone, Debug, PartialEq)]
751751
#[repr(C)]
752752
pub struct EntityLocation {
753753
/// The ID of the [`Archetype`] the [`Entity`] belongs to.

crates/bevy_ecs/src/world/entity_ref.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,17 @@ pub struct EntityRef<'w> {
2222
}
2323

2424
impl<'w> EntityRef<'w> {
25+
/// # Safety
26+
///
27+
/// - `entity` must be valid for `world`: the generation should match that of the entity at the same index.
28+
/// - `location` must be sourced from `world`'s `Entities` and must exactly match the location for `entity`
29+
///
30+
/// The above is trivially satisfied if `location` was sourced from `world.entities().get(entity)`.
2531
#[inline]
26-
pub(crate) fn new(world: &'w World, entity: Entity, location: EntityLocation) -> Self {
32+
pub(crate) unsafe fn new(world: &'w World, entity: Entity, location: EntityLocation) -> Self {
33+
debug_assert!(world.entities().contains(entity));
34+
debug_assert_eq!(world.entities().get(entity), Some(location));
35+
2736
Self {
2837
world,
2938
entity,
@@ -193,7 +202,9 @@ impl<'w> EntityRef<'w> {
193202

194203
impl<'w> From<EntityMut<'w>> for EntityRef<'w> {
195204
fn from(entity_mut: EntityMut<'w>) -> EntityRef<'w> {
196-
EntityRef::new(entity_mut.world, entity_mut.entity, entity_mut.location)
205+
// SAFETY: the safety invariants on EntityMut and EntityRef are identical
206+
// and EntityMut is promised to be valid by construction.
207+
unsafe { EntityRef::new(entity_mut.world, entity_mut.entity, entity_mut.location) }
197208
}
198209
}
199210

@@ -206,13 +217,20 @@ pub struct EntityMut<'w> {
206217

207218
impl<'w> EntityMut<'w> {
208219
/// # Safety
209-
/// entity and location _must_ be valid
220+
///
221+
/// - `entity` must be valid for `world`: the generation should match that of the entity at the same index.
222+
/// - `location` must be sourced from `world`'s `Entities` and must exactly match the location for `entity`
223+
///
224+
/// The above is trivially satisfied if `location` was sourced from `world.entities().get(entity)`.
210225
#[inline]
211226
pub(crate) unsafe fn new(
212227
world: &'w mut World,
213228
entity: Entity,
214229
location: EntityLocation,
215230
) -> Self {
231+
debug_assert!(world.entities().contains(entity));
232+
debug_assert_eq!(world.entities().get(entity), Some(location));
233+
216234
EntityMut {
217235
world,
218236
entity,

crates/bevy_ecs/src/world/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,10 @@ impl World {
317317
#[inline]
318318
pub fn get_entity(&self, entity: Entity) -> Option<EntityRef> {
319319
let location = self.entities.get(entity)?;
320-
Some(EntityRef::new(self, entity, location))
320+
// SAFETY: if the Entity is invalid, the function returns early.
321+
// Additionally, Entities::get(entity) returns the correct EntityLocation if the entity exists.
322+
let entity_ref = unsafe { EntityRef::new(self, entity, location) };
323+
Some(entity_ref)
321324
}
322325

323326
/// Returns an [`Entity`] iterator of current entities.
@@ -331,13 +334,16 @@ impl World {
331334
.iter()
332335
.enumerate()
333336
.map(|(archetype_row, archetype_entity)| {
337+
let entity = archetype_entity.entity();
334338
let location = EntityLocation {
335339
archetype_id: archetype.id(),
336340
archetype_row: ArchetypeRow::new(archetype_row),
337341
table_id: archetype.table_id(),
338342
table_row: archetype_entity.table_row(),
339343
};
340-
EntityRef::new(self, archetype_entity.entity(), location)
344+
345+
// SAFETY: entity exists and location accurately specifies the archetype where the entity is stored
346+
unsafe { EntityRef::new(self, entity, location) }
341347
})
342348
})
343349
}

0 commit comments

Comments
 (0)