Skip to content

Commit d98d6d8

Browse files
authored
Fix unsoundness in FilteredEntity{Ref,Mut} various get methods (#13554)
# Objective - `FilteredEntity{Ref,Mut}` various `get` methods never checked that the given component was present on the entity, only the access allowed reading/writing them, which is always the case when it is constructed from a `EntityRef`/`EntityMut`/`EntityWorldMut` (and I guess can also happen with queries containing `Option<T>` that get transmuted). - In those cases the various `get` methods were calling `debug_checked_unwrap` on `None`s, which is UB when debug assertions are not enabled; - The goal is thus to fix this soundness issue. ## Solution - Don't call `debug_checked_unwrap` on those `None` and instead `flatten` them. ## Testing - This PR includes regression tests for each combination of `FilteredEntityRef`/`FilteredEntityMut` and component present/not-present. The two tests for the not-present cases fail on `main` but success with this PR changes.
1 parent bc102d4 commit d98d6d8

File tree

1 file changed

+87
-23
lines changed

1 file changed

+87
-23
lines changed

crates/bevy_ecs/src/world/entity_ref.rs

Lines changed: 87 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
change_detection::MutUntyped,
55
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
66
entity::{Entities, Entity, EntityLocation},
7-
query::{Access, DebugCheckedUnwrap},
7+
query::Access,
88
removal_detection::RemovedComponentEvents,
99
storage::Storages,
1010
world::{Mut, World},
@@ -1824,8 +1824,9 @@ impl<'w> FilteredEntityRef<'w> {
18241824
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
18251825
self.access
18261826
.has_read(id)
1827-
// SAFETY: We have read access so we must have the component
1828-
.then(|| unsafe { self.entity.get().debug_checked_unwrap() })
1827+
// SAFETY: We have read access
1828+
.then(|| unsafe { self.entity.get() })
1829+
.flatten()
18291830
}
18301831

18311832
/// Gets access to the component of type `T` for the current entity,
@@ -1837,8 +1838,9 @@ impl<'w> FilteredEntityRef<'w> {
18371838
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
18381839
self.access
18391840
.has_read(id)
1840-
// SAFETY: We have read access so we must have the component
1841-
.then(|| unsafe { self.entity.get_ref().debug_checked_unwrap() })
1841+
// SAFETY: We have read access
1842+
.then(|| unsafe { self.entity.get_ref() })
1843+
.flatten()
18421844
}
18431845

18441846
/// Retrieves the change ticks for the given component. This can be useful for implementing change
@@ -1848,8 +1850,9 @@ impl<'w> FilteredEntityRef<'w> {
18481850
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
18491851
self.access
18501852
.has_read(id)
1851-
// SAFETY: We have read access so we must have the component
1852-
.then(|| unsafe { self.entity.get_change_ticks::<T>().debug_checked_unwrap() })
1853+
// SAFETY: We have read access
1854+
.then(|| unsafe { self.entity.get_change_ticks::<T>() })
1855+
.flatten()
18531856
}
18541857

18551858
/// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change
@@ -1860,12 +1863,11 @@ impl<'w> FilteredEntityRef<'w> {
18601863
/// compile time.**
18611864
#[inline]
18621865
pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option<ComponentTicks> {
1863-
// SAFETY: We have read access so we must have the component
1864-
self.access.has_read(component_id).then(|| unsafe {
1865-
self.entity
1866-
.get_change_ticks_by_id(component_id)
1867-
.debug_checked_unwrap()
1868-
})
1866+
self.access
1867+
.has_read(component_id)
1868+
// SAFETY: We have read access
1869+
.then(|| unsafe { self.entity.get_change_ticks_by_id(component_id) })
1870+
.flatten()
18691871
}
18701872

18711873
/// Gets the component of the given [`ComponentId`] from the entity.
@@ -1880,8 +1882,9 @@ impl<'w> FilteredEntityRef<'w> {
18801882
pub fn get_by_id(&self, component_id: ComponentId) -> Option<Ptr<'w>> {
18811883
self.access
18821884
.has_read(component_id)
1883-
// SAFETY: We have read access so we must have the component
1884-
.then(|| unsafe { self.entity.get_by_id(component_id).debug_checked_unwrap() })
1885+
// SAFETY: We have read access
1886+
.then(|| unsafe { self.entity.get_by_id(component_id) })
1887+
.flatten()
18851888
}
18861889
}
18871890

@@ -2094,8 +2097,9 @@ impl<'w> FilteredEntityMut<'w> {
20942097
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
20952098
self.access
20962099
.has_write(id)
2097-
// SAFETY: We have write access so we must have the component
2098-
.then(|| unsafe { self.entity.get_mut().debug_checked_unwrap() })
2100+
// SAFETY: We have write access
2101+
.then(|| unsafe { self.entity.get_mut() })
2102+
.flatten()
20992103
}
21002104

21012105
/// Retrieves the change ticks for the given component. This can be useful for implementing change
@@ -2139,12 +2143,11 @@ impl<'w> FilteredEntityMut<'w> {
21392143
/// which is only valid while the [`FilteredEntityMut`] is alive.
21402144
#[inline]
21412145
pub fn get_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> {
2142-
// SAFETY: We have write access so we must have the component
2143-
self.access.has_write(component_id).then(|| unsafe {
2144-
self.entity
2145-
.get_mut_by_id(component_id)
2146-
.debug_checked_unwrap()
2147-
})
2146+
self.access
2147+
.has_write(component_id)
2148+
// SAFETY: We have write access
2149+
.then(|| unsafe { self.entity.get_mut_by_id(component_id) })
2150+
.flatten()
21482151
}
21492152
}
21502153

@@ -2416,6 +2419,7 @@ mod tests {
24162419
use bevy_ptr::OwningPtr;
24172420
use std::panic::AssertUnwindSafe;
24182421

2422+
use crate::world::{FilteredEntityMut, FilteredEntityRef};
24192423
use crate::{self as bevy_ecs, component::ComponentId, prelude::*, system::assert_is_system};
24202424

24212425
#[test]
@@ -2918,4 +2922,64 @@ mod tests {
29182922

29192923
assert_is_system(incompatible_system);
29202924
}
2925+
2926+
#[test]
2927+
fn filtered_entity_ref_normal() {
2928+
let mut world = World::new();
2929+
let a_id = world.init_component::<A>();
2930+
2931+
let e: FilteredEntityRef = world.spawn(A).into();
2932+
2933+
assert!(e.get::<A>().is_some());
2934+
assert!(e.get_ref::<A>().is_some());
2935+
assert!(e.get_change_ticks::<A>().is_some());
2936+
assert!(e.get_by_id(a_id).is_some());
2937+
assert!(e.get_change_ticks_by_id(a_id).is_some());
2938+
}
2939+
2940+
#[test]
2941+
fn filtered_entity_ref_missing() {
2942+
let mut world = World::new();
2943+
let a_id = world.init_component::<A>();
2944+
2945+
let e: FilteredEntityRef = world.spawn(()).into();
2946+
2947+
assert!(e.get::<A>().is_none());
2948+
assert!(e.get_ref::<A>().is_none());
2949+
assert!(e.get_change_ticks::<A>().is_none());
2950+
assert!(e.get_by_id(a_id).is_none());
2951+
assert!(e.get_change_ticks_by_id(a_id).is_none());
2952+
}
2953+
2954+
#[test]
2955+
fn filtered_entity_mut_normal() {
2956+
let mut world = World::new();
2957+
let a_id = world.init_component::<A>();
2958+
2959+
let mut e: FilteredEntityMut = world.spawn(A).into();
2960+
2961+
assert!(e.get::<A>().is_some());
2962+
assert!(e.get_ref::<A>().is_some());
2963+
assert!(e.get_mut::<A>().is_some());
2964+
assert!(e.get_change_ticks::<A>().is_some());
2965+
assert!(e.get_by_id(a_id).is_some());
2966+
assert!(e.get_mut_by_id(a_id).is_some());
2967+
assert!(e.get_change_ticks_by_id(a_id).is_some());
2968+
}
2969+
2970+
#[test]
2971+
fn filtered_entity_mut_missing() {
2972+
let mut world = World::new();
2973+
let a_id = world.init_component::<A>();
2974+
2975+
let mut e: FilteredEntityMut = world.spawn(()).into();
2976+
2977+
assert!(e.get::<A>().is_none());
2978+
assert!(e.get_ref::<A>().is_none());
2979+
assert!(e.get_mut::<A>().is_none());
2980+
assert!(e.get_change_ticks::<A>().is_none());
2981+
assert!(e.get_by_id(a_id).is_none());
2982+
assert!(e.get_mut_by_id(a_id).is_none());
2983+
assert!(e.get_change_ticks_by_id(a_id).is_none());
2984+
}
29212985
}

0 commit comments

Comments
 (0)