Skip to content

Commit 54456b7

Browse files
authored
Make SystemParam::new_archetype and QueryState::new_archetype unsafe functions (#13044)
# Objective Fix #2128. Both `Query::new_archetype` and `SystemParam::new_archetype` do not check if the `Archetype` comes from the same World the state is initialized from. This could result in unsoundness via invalid accesses if called incorrectly. ## Solution Make them `unsafe` functions and lift the invariant to the caller. This also caught one instance of us not validating the World in `SystemState::update_archetypes_unsafe_world_cell`'s implementation. --- ## Changelog Changed: `QueryState::new_archetype` is now an unsafe function. Changed: `SystemParam::new_archetype` is now an unsafe function. ## Migration Guide `QueryState::new_archetype` and `SystemParam::new_archetype` are now an unsafe functions that must be sure that the provided `Archetype` is from the same `World` that the state was initialized from. Callers may need to add additional assertions or propagate the safety invariant upwards through the callstack to ensure safety.
1 parent 8403c41 commit 54456b7

File tree

5 files changed

+72
-26
lines changed

5 files changed

+72
-26
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,9 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream {
239239
(#(#param,)*)
240240
}
241241

242-
fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) {
243-
<(#(#param,)*) as SystemParam>::new_archetype(state, archetype, system_meta);
242+
unsafe fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) {
243+
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
244+
unsafe { <(#(#param,)*) as SystemParam>::new_archetype(state, archetype, system_meta); }
244245
}
245246

246247
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
@@ -425,8 +426,9 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
425426
}
426427
}
427428

428-
fn new_archetype(state: &mut Self::State, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) {
429-
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta)
429+
unsafe fn new_archetype(state: &mut Self::State, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) {
430+
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
431+
unsafe { <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::new_archetype(&mut state.state, archetype, system_meta) }
430432
}
431433

432434
fn apply(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) {

crates/bevy_ecs/src/query/state.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,12 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
161161
) -> Self {
162162
let mut state = Self::new_uninitialized(world);
163163
for archetype in world.archetypes.iter() {
164-
if state.new_archetype_internal(archetype) {
165-
state.update_archetype_component_access(archetype, access);
164+
// SAFETY: The state was just initialized from the `world` above, and the archetypes being added
165+
// come directly from the same world.
166+
unsafe {
167+
if state.new_archetype_internal(archetype) {
168+
state.update_archetype_component_access(archetype, access);
169+
}
166170
}
167171
}
168172
state.archetype_generation = world.archetypes.generation();
@@ -342,7 +346,11 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
342346
std::mem::replace(&mut self.archetype_generation, archetypes.generation());
343347

344348
for archetype in &archetypes[old_generation..] {
345-
self.new_archetype_internal(archetype);
349+
// SAFETY: The validate_world call ensures that the world is the same the QueryState
350+
// was initialized from.
351+
unsafe {
352+
self.new_archetype_internal(archetype);
353+
}
346354
}
347355
}
348356

@@ -371,13 +379,19 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
371379
/// (if applicable, i.e. if the archetype has any intersecting [`ComponentId`] with the current [`QueryState`]).
372380
///
373381
/// The passed in `access` will be updated with any new accesses introduced by the new archetype.
374-
pub fn new_archetype(
382+
///
383+
/// # Safety
384+
/// `archetype` must be from the `World` this state was initialized from.
385+
pub unsafe fn new_archetype(
375386
&mut self,
376387
archetype: &Archetype,
377388
access: &mut Access<ArchetypeComponentId>,
378389
) {
379-
if self.new_archetype_internal(archetype) {
380-
self.update_archetype_component_access(archetype, access);
390+
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from.
391+
let matches = unsafe { self.new_archetype_internal(archetype) };
392+
if matches {
393+
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from.
394+
unsafe { self.update_archetype_component_access(archetype, access) };
381395
}
382396
}
383397

@@ -386,7 +400,10 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
386400
///
387401
/// Returns `true` if the given `archetype` matches the query. Otherwise, returns `false`.
388402
/// If there is no match, then there is no need to update the query's [`FilteredAccess`].
389-
fn new_archetype_internal(&mut self, archetype: &Archetype) -> bool {
403+
///
404+
/// # Safety
405+
/// `archetype` must be from the `World` this state was initialized from.
406+
unsafe fn new_archetype_internal(&mut self, archetype: &Archetype) -> bool {
390407
if D::matches_component_set(&self.fetch_state, &|id| archetype.contains(id))
391408
&& F::matches_component_set(&self.filter_state, &|id| archetype.contains(id))
392409
&& self.matches_component_set(&|id| archetype.contains(id))
@@ -431,7 +448,10 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
431448
/// For the given `archetype`, adds any component accessed used by this query's underlying [`FilteredAccess`] to `access`.
432449
///
433450
/// The passed in `access` will be updated with any new accesses introduced by the new archetype.
434-
pub fn update_archetype_component_access(
451+
///
452+
/// # Safety
453+
/// `archetype` must be from the `World` this state was initialized from.
454+
pub unsafe fn update_archetype_component_access(
435455
&mut self,
436456
archetype: &Archetype,
437457
access: &mut Access<ArchetypeComponentId>,

crates/bevy_ecs/src/system/function_system.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,12 +284,15 @@ impl<Param: SystemParam> SystemState<Param> {
284284
/// This method only accesses world metadata.
285285
#[inline]
286286
pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) {
287+
assert_eq!(self.world_id, world.id(), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with.");
288+
287289
let archetypes = world.archetypes();
288290
let old_generation =
289291
std::mem::replace(&mut self.archetype_generation, archetypes.generation());
290292

291293
for archetype in &archetypes[old_generation..] {
292-
Param::new_archetype(&mut self.param_state, archetype, &mut self.meta);
294+
// SAFETY: The assertion above ensures that the param_state was initialized from `world`.
295+
unsafe { Param::new_archetype(&mut self.param_state, archetype, &mut self.meta) };
293296
}
294297
}
295298

@@ -527,7 +530,8 @@ where
527530

528531
for archetype in &archetypes[old_generation..] {
529532
let param_state = self.param_state.as_mut().unwrap();
530-
F::Param::new_archetype(param_state, archetype, &mut self.system_meta);
533+
// SAFETY: The assertion above ensures that the param_state was initialized from `world`.
534+
unsafe { F::Param::new_archetype(param_state, archetype, &mut self.system_meta) };
531535
}
532536
}
533537

crates/bevy_ecs/src/system/system_param.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,16 @@ pub unsafe trait SystemParam: Sized {
137137
/// and creates a new instance of this param's [`State`](Self::State).
138138
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State;
139139

140-
/// For the specified [`Archetype`], registers the components accessed by this [`SystemParam`] (if applicable).
140+
/// For the specified [`Archetype`], registers the components accessed by this [`SystemParam`] (if applicable).a
141+
///
142+
/// # Safety
143+
/// `archetype` must be from the [`World`] used to initialize `state` in `init_state`.
141144
#[inline]
142-
fn new_archetype(
143-
_state: &mut Self::State,
144-
_archetype: &Archetype,
145-
_system_meta: &mut SystemMeta,
145+
#[allow(unused_variables)]
146+
unsafe fn new_archetype(
147+
state: &mut Self::State,
148+
archetype: &Archetype,
149+
system_meta: &mut SystemMeta,
146150
) {
147151
}
148152

@@ -208,7 +212,11 @@ unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam for Qu
208212
state
209213
}
210214

211-
fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) {
215+
unsafe fn new_archetype(
216+
state: &mut Self::State,
217+
archetype: &Archetype,
218+
system_meta: &mut SystemMeta,
219+
) {
212220
state.new_archetype(archetype, &mut system_meta.archetype_component_access);
213221
}
214222

@@ -1343,8 +1351,10 @@ macro_rules! impl_system_param_tuple {
13431351
}
13441352

13451353
#[inline]
1346-
fn new_archetype(($($param,)*): &mut Self::State, _archetype: &Archetype, _system_meta: &mut SystemMeta) {
1347-
$($param::new_archetype($param, _archetype, _system_meta);)*
1354+
#[allow(unused_unsafe)]
1355+
unsafe fn new_archetype(($($param,)*): &mut Self::State, _archetype: &Archetype, _system_meta: &mut SystemMeta) {
1356+
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
1357+
unsafe { $($param::new_archetype($param, _archetype, _system_meta);)* }
13481358
}
13491359

13501360
#[inline]
@@ -1487,8 +1497,13 @@ unsafe impl<P: SystemParam + 'static> SystemParam for StaticSystemParam<'_, '_,
14871497
P::init_state(world, system_meta)
14881498
}
14891499

1490-
fn new_archetype(state: &mut Self::State, archetype: &Archetype, system_meta: &mut SystemMeta) {
1491-
P::new_archetype(state, archetype, system_meta);
1500+
unsafe fn new_archetype(
1501+
state: &mut Self::State,
1502+
archetype: &Archetype,
1503+
system_meta: &mut SystemMeta,
1504+
) {
1505+
// SAFETY: The caller guarantees that the provided `archetype` matches the World used to initialize `state`.
1506+
unsafe { P::new_archetype(state, archetype, system_meta) };
14921507
}
14931508

14941509
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {

crates/bevy_gizmos/src/gizmos.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,26 @@ pub struct GizmosFetchState<T: GizmoConfigGroup> {
5555
unsafe impl<T: GizmoConfigGroup> SystemParam for Gizmos<'_, '_, T> {
5656
type State = GizmosFetchState<T>;
5757
type Item<'w, 's> = Gizmos<'w, 's, T>;
58+
5859
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
5960
GizmosFetchState {
6061
state: GizmosState::<T>::init_state(world, system_meta),
6162
}
6263
}
63-
fn new_archetype(
64+
65+
unsafe fn new_archetype(
6466
state: &mut Self::State,
6567
archetype: &bevy_ecs::archetype::Archetype,
6668
system_meta: &mut SystemMeta,
6769
) {
68-
GizmosState::<T>::new_archetype(&mut state.state, archetype, system_meta);
70+
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
71+
unsafe { GizmosState::<T>::new_archetype(&mut state.state, archetype, system_meta) };
6972
}
73+
7074
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
7175
GizmosState::<T>::apply(&mut state.state, system_meta, world);
7276
}
77+
7378
unsafe fn get_param<'w, 's>(
7479
state: &'s mut Self::State,
7580
system_meta: &SystemMeta,

0 commit comments

Comments
 (0)