bevy icon indicating copy to clipboard operation
bevy copied to clipboard

Make QueryFilter an unsafe trait

Open chescock opened this issue 1 year ago • 0 comments

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:

#[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.

chescock avatar Aug 16 '24 23:08 chescock