Make QueryFilter an unsafe trait (#14790)
# Objective
It's possible to create UB using an implementation of `QueryFilter` that
performs mutable access, but that does not violate any documented safety
invariants.
This code:
```rust
#[derive(Component)]
struct Foo(usize);
// This derive is a simple way to get a valid WorldQuery impl. The QueryData impl isn't used.
#[derive(QueryData)]
#[query_data(mutable)]
struct BadFilter<'w> {
foo: &'w mut Foo,
}
impl QueryFilter for BadFilter<'_> {
const IS_ARCHETYPAL: bool = false;
unsafe fn filter_fetch(
fetch: &mut Self::Fetch<'_>,
entity: Entity,
table_row: TableRow,
) -> bool {
// SAFETY: fetch and filter_fetch have the same safety requirements
let f: &mut usize = &mut unsafe { Self::fetch(fetch, entity, table_row) }.foo.0;
println!("Got &mut at {f:p}");
true
}
}
let mut world = World::new();
world.spawn(Foo(0));
world.run_system_once(|query: Query<&Foo, BadFilter>| {
let f: &usize = &query.iter().next().unwrap().0;
println!("Got & at {f:p}");
query.iter().next().unwrap();
println!("Still have & at {f:p}");
});
```
prints:
```
Got &mut at 0x1924b92dfb0
Got & at 0x1924b92dfb0
Got &mut at 0x1924b92dfb0
Still have & at 0x1924b92dfb0
```
Which means it had an `&` and `&mut` alive at the same time.
The only `unsafe` there is around `Self::fetch`, but I believe that call
correctly upholds the safety invariant, and matches what `Added` and
`Changed` do.
## Solution
Make `QueryFilter` an unsafe trait and document the requirement that the
`WorldQuery` implementation be read-only.
## Migration Guide
`QueryFilter` is now an `unsafe trait`. If you were manually
implementing it, you will need to verify that the `WorldQuery`
implementation is read-only and then add the `unsafe` keyword to the
`impl`.
This commit is contained in:
parent
4b78ba0162
commit
a9d2a9ea37
@ -120,7 +120,8 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream {
|
||||
);
|
||||
|
||||
let filter_impl = quote! {
|
||||
impl #user_impl_generics #path::query::QueryFilter
|
||||
// SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access.
|
||||
unsafe impl #user_impl_generics #path::query::QueryFilter
|
||||
for #struct_name #user_ty_generics #user_where_clauses {
|
||||
const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*;
|
||||
|
||||
|
||||
@ -70,12 +70,17 @@ use std::{cell::UnsafeCell, marker::PhantomData};
|
||||
/// [`matches_component_set`]: Self::matches_component_set
|
||||
/// [`Query`]: crate::system::Query
|
||||
/// [`State`]: Self::State
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The [`WorldQuery`] implementation must not take any mutable access.
|
||||
/// This is the same safety requirement as [`ReadOnlyQueryData`](crate::query::ReadOnlyQueryData).
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "`{Self}` is not a valid `Query` filter",
|
||||
label = "invalid `Query` filter",
|
||||
note = "a `QueryFilter` typically uses a combination of `With<T>` and `Without<T>` statements"
|
||||
)]
|
||||
pub trait QueryFilter: WorldQuery {
|
||||
pub unsafe trait QueryFilter: WorldQuery {
|
||||
/// Returns true if (and only if) this Filter relies strictly on archetypes to limit which
|
||||
/// components are accessed by the Query.
|
||||
///
|
||||
@ -201,7 +206,8 @@ unsafe impl<T: Component> WorldQuery for With<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> QueryFilter for With<T> {
|
||||
// SAFETY: WorldQuery impl performs no access at all
|
||||
unsafe impl<T: Component> QueryFilter for With<T> {
|
||||
const IS_ARCHETYPAL: bool = true;
|
||||
|
||||
#[inline(always)]
|
||||
@ -311,7 +317,8 @@ unsafe impl<T: Component> WorldQuery for Without<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> QueryFilter for Without<T> {
|
||||
// SAFETY: WorldQuery impl performs no access at all
|
||||
unsafe impl<T: Component> QueryFilter for Without<T> {
|
||||
const IS_ARCHETYPAL: bool = true;
|
||||
|
||||
#[inline(always)]
|
||||
@ -490,7 +497,8 @@ macro_rules! impl_or_query_filter {
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> {
|
||||
// SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access.
|
||||
unsafe impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> {
|
||||
const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*;
|
||||
|
||||
#[inline(always)]
|
||||
@ -512,7 +520,8 @@ macro_rules! impl_tuple_query_filter {
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::unused_unit)]
|
||||
$(#[$meta])*
|
||||
impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) {
|
||||
// SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access.
|
||||
unsafe impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) {
|
||||
const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*;
|
||||
|
||||
#[inline(always)]
|
||||
@ -734,7 +743,8 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> QueryFilter for Added<T> {
|
||||
// SAFETY: WorldQuery impl performs only read access on ticks
|
||||
unsafe impl<T: Component> QueryFilter for Added<T> {
|
||||
const IS_ARCHETYPAL: bool = false;
|
||||
#[inline(always)]
|
||||
unsafe fn filter_fetch(
|
||||
@ -949,7 +959,8 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> QueryFilter for Changed<T> {
|
||||
// SAFETY: WorldQuery impl performs only read access on ticks
|
||||
unsafe impl<T: Component> QueryFilter for Changed<T> {
|
||||
const IS_ARCHETYPAL: bool = false;
|
||||
|
||||
#[inline(always)]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user