Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

constrain WorldQuery::get_state to only use &Components #13343

Conversation

Victoronz
Copy link
Contributor

@Victoronz Victoronz commented May 12, 2024

Objective

Passing &World in the WorldQuery::get_state method is unnecessary, as all implementations of this method in the engine either only access Components in &World, or do nothing with it.
It can introduce UB by necessitating the creation of a &World from a UnsafeWorldCell.
This currently happens in Query::transmute_lens, which obtains a &World from the internal UnsafeWorldCell solely to pass to get_state. Query::join suffers from the same issue.
Other cases of UB come from allowing implementors of WorldQuery to freely access &World, like in the bevy-trait-query crate, where a reference to a resource is obtained inside of get_state, potentially aliasing with a ResMut parameter in the same system.

WorldQuery::init_state currently requires &mut World, which doesn't suffer from these issues.
But that too can be changed to receive a wrapper around &mut Components and &mut Storages for consistency in a follow-up PR.

Solution

Replace the &World parameter in get_state with &Components.

Changelog

WorldQuery::get_state now takes &Components instead of &World.
The transmute, transmute_filtered, join and join_filtered methods on QueryState now similarly take &Components instead of &World.

Migration Guide

Users of WorldQuery::get_state or transmute, transmute_filtered, join and join_filtered methods on QueryState now need to pass &Components instead of &World.
&Components can be trivially obtained from either components method on &World or UnsafeWorldCell.
For implementors of WorldQuery::get_state that were accessing more than the Components inside &World and its methods, this is no longer allowed.

Copy link
Contributor

Welcome, new contributor!

Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨

@james7132 james7132 added A-ECS Entities, components, systems, and events C-Breaking-Change A breaking change to Bevy's public API that needs to be noted in a migration guide D-Unsafe Touches with unsafe code in some way labels May 12, 2024
Copy link
Member

@james7132 james7132 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of trait design, I don't think this is appropriate, given that init_state, the infallible version of get_state requires a &mut World, there may be WorldQuery implementations that may read other state in the world (i.e. resources, other metadata, etc).

If we were to seal WorldQuery so that it cannot be externally implemented, this change may make more sense, and it does seem like something that @maniwani was looking into for ABI compatibility. However, without those changes, I don't think this is a limitation we should be imposing without also limiting the scope of init_state.

@Victoronz
Copy link
Contributor Author

Victoronz commented May 12, 2024

That is true, init_state should mirror get_state. Since the only use the engine ever has for the &mut World argument is to call init_component on it, the &mut World argument is replaceable by &mut Components and &mut Storages together (the arguments for the Components::init_component method that World::init_components wraps).

I added this change in the second commit.

@Victoronz Victoronz changed the title constrain WorldQuery::get_state to only use &Components constrain init_state and get_state methods on WorldQuery to only use Components and Storages May 13, 2024
@Victoronz
Copy link
Contributor Author

Victoronz commented May 13, 2024

I realized that because Storages and Components aren't public fields on World, and there are no mutable accessor functions, doing the same change likely doesn't work for init_state.
Adding mutable accessor function does not make sense, because this would allow users to readily cause UB.
Instead we could have a function on &mut World that returns an opaque struct on which init_component, ... can be called.

@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior P-Unsound A bug that results in undefined compiler behavior X-Contentious There are nontrivial implications that should be thought through D-Straightforward Simple bug fixes and API improvements, docs, test and examples labels May 13, 2024
@Victoronz
Copy link
Contributor Author

Since the get_state change is a UB fix and init_state is for consistency, I'll remove the init_state change again and leave that for a follow-up PR.

@Victoronz Victoronz force-pushed the world-query-state-initialization-ub-fix branch from f4a2b5a to 0637ee4 Compare May 13, 2024 16:39
@Victoronz Victoronz changed the title constrain init_state and get_state methods on WorldQuery to only use Components and Storages constrain WorldQuery::get_state to only use &Components May 13, 2024
@alice-i-cecile alice-i-cecile added this to the 0.14 milestone May 13, 2024
@alice-i-cecile alice-i-cecile added X-Controversial There is active debate or serious implications around merging this PR and removed X-Contentious There are nontrivial implications that should be thought through labels May 13, 2024
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this as an immediately actionable step to address UB. The restriction on get_state fundamentally makes sense to me: queries should generally not be accessing non-component data. I can see a world where additional data (like indexes) are requested, but I would rather lock this down now and then add more careful access.

init_state should be changed to match, but that doesn't have to happen right now, and frankly probably shouldn't, given that it will require more careful design thought.

Copy link
Member

@james7132 james7132 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, deferring to a later PR SGTM, so long as both make it into 0.14.

@james7132 james7132 added this pull request to the merge queue May 13, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to no response for status checks May 13, 2024
@alice-i-cecile alice-i-cecile added this pull request to the merge queue May 13, 2024
@alice-i-cecile alice-i-cecile added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label May 13, 2024
Merged via the queue into bevyengine:main with commit 0eb4bb6 May 13, 2024
60 checks passed
Themayu added a commit to Themayu/bevy that referenced this pull request May 14, 2024
github-merge-queue bot pushed a commit that referenced this pull request May 30, 2024
…3442)

# Objective

In #13343, `WorldQuery::get_state` was constrained from `&World` as the
argument to `&Components`, but `WorldQuery::init_state` hasn't yet been
changed from `&mut World` to match.

Fixes #13358

## Solution

Create a wrapper around `&mut Components` and `&mut Storages` that can
be obtained from `&mut World` with a `component_initializer` method.
This new `ComponentInitializer` re-exposes the API on `&mut Components`
minus the `&mut Storages` parameter where it was present. For the
`&Components` API, it simply derefs to its `components` field.

## Changelog

### Added
The `World::component_initializer` method.
The `ComponentInitializer` struct that re-exposes `Components` API.
### Changed
`WorldQuery::init_state` now takes `&mut ComponentInitializer` instead
of `&mut World`.

## Migration Guide
Instead of passing `&mut World` to `WorldQuery::init_state` directly,
pass in a mutable reference to the struct returned from
`World::component_initializer`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Breaking-Change A breaking change to Bevy's public API that needs to be noted in a migration guide C-Bug An unexpected or incorrect behavior D-Straightforward Simple bug fixes and API improvements, docs, test and examples D-Unsafe Touches with unsafe code in some way P-Unsound A bug that results in undefined compiler behavior S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Controversial There is active debate or serious implications around merging this PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants