Repository: bevyengine/bevy
File: crates/bevy_ecs/src/query/state.rs (Rust)

Code before:
        last_change_tick: u32,
        change_tick: u32,
    ) {
        // NOTE: If you are changing query iteration code, remember to update the following places, where relevant:
        // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual
        ComputeTaskPool::get().scope(|scope| {
            if Q::IS_DENSE && F::IS_DENSE {
                let tables = &world.storages().tables;
                for table_id in &self.matched_table_ids {
                    let table = &tables[*table_id];
                    if table.is_empty() {
                        continue;
                    }

                    let mut offset = 0;
                    while offset < table.entity_count() {
                        let mut func = func.clone();
                        let len = batch_size.min(table.entity_count() - offset);
                        let task = async move {
                            let table = &world
                                .storages()
                                .tables
                                .get(*table_id)
                                .debug_checked_unwrap();
                            let batch = offset..offset + len;
                            self.iter_unchecked_manual(world, last_change_tick, change_tick)
                                .for_each_table(&mut func, table, batch);
                        };
                        #[cfg(feature = "trace")]
                        let span = bevy_utils::tracing::info_span!(
                            "par_for_each",
                            query = std::any::type_name::<Q>(),
                            filter = std::any::type_name::<F>(),
                            count = len,
                        );
                        #[cfg(feature = "trace")]
                        let task = task.instrument(span);
                        scope.spawn(task);
                        offset += batch_size;
                    }
                }
            } else {
                let archetypes = &world.archetypes;
                for archetype_id in &self.matched_archetype_ids {
                    let mut offset = 0;
                    let archetype = &archetypes[*archetype_id];
                    if archetype.is_empty() {
                        continue;
                    }

                    while offset < archetype.len() {

Review comment (suggestion):
not particularly thrilled with us creating a `QueryIter` that isnt actually allowed to access all the component data it should have access to. I've always interpreted `iter_unchecked_manual`'s safety invariant to be that it must be legal for the iterator to access all matched components which is absolutely not the case here...

(same applies to the non-dense branch below)

I am not sure if this is actually exploitable or not (regardless I do not like this). For example when this calls `QueryState::iter_unchecked_manual` which calls `QueryIter::new` which calls `QueryIterationCursor::init` which calls `Q::init_fetch` we end up in arbitrary user code which is rather poorly documented safety wise as to what it is allowed to access from `World` but I think that the intention is that it has access to anything specified in `fn update_archetype_component_access`

Code after:
        self.validate_world(world.id());
        // SAFETY: query is read only, world id is validated
        unsafe {
            self.as_readonly().iter_many_unchecked_manual(
                entities,
                world.as_unsafe_world_cell_readonly(),
                world.last_change_tick(),
                world.read_change_tick(),
            )
        }
    }

    /// Returns an iterator over the query items generated from an [`Entity`] list.
    ///
    /// Items are returned in the order of the list of entities.
    /// Entities that don't match the query are skipped.
    #[inline]
    pub fn iter_many_mut<'w, 's, EntityList: IntoIterator>(
        &'s mut self,
        world: &'w mut World,
        entities: EntityList,
    ) -> QueryManyIter<'w, 's, Q, F, EntityList::IntoIter>
    where
        EntityList::Item: Borrow<Entity>,
    {
        self.update_archetypes(world);
        let change_tick = world.change_tick();
        let last_change_tick = world.last_change_tick();
        // SAFETY: Query has unique world access.
        unsafe {
            self.iter_many_unchecked_manual(
                entities,
                world.as_unsafe_world_cell(),
                last_change_tick,
                change_tick,
            )
        }
    }

    /// Returns an [`Iterator`] over the query results for the given [`World`].
    ///
    /// # Safety
    ///
    /// This does not check for mutable query correctness. To be safe, make sure mutable queries
    /// have unique access to the components they query.
    #[inline]
    pub unsafe fn iter_unchecked<'w, 's>(
        &'s mut self,
        world: UnsafeWorldCell<'w>,
    ) -> QueryIter<'w, 's, Q, F> {
        self.update_archetypes_unsafe_world_cell(world);