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

Add a missing safety invariant to System::run_unsafe #7778

Merged
merged 11 commits into from
Apr 17, 2023

Conversation

JoJoJet
Copy link
Member

@JoJoJet JoJoJet commented Feb 21, 2023

Objective

The implementation of System::run_unsafe for FunctionSystem requires that the world is the same one used to initialize the system. However, the System trait has no requirements that the world actually matches, which makes this implementation unsound.

This was previously mentioned in #7605 (comment)

Fixes part of #7833.

Solution

Add the safety invariant that System::update_archetype_component_access must be called prior to System::run_unsafe. Since FunctionSystem::update_archetype_component_access properly validates the world, this ensures that run_unsafe is not called with a mismatched world.

Most exclusive systems are not required to be run on the same world that they are initialized with, so this is not a concern for them. Systems formed by combining an exclusive system with a regular system do require the world to match, however the validation is done inside of System::run when needed.

@JoJoJet JoJoJet added A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change P-Unsound A bug that results in undefined compiler behavior labels Feb 21, 2023
@james7132 james7132 self-requested a review February 26, 2023 07:02
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.

Great catch! Though does changing the safety invariants of an unsafe function count as a breaking change? This should probably be documented in the migration guide if possible.

crates/bevy_ecs/src/schedule/executor/multi_threaded.rs Outdated Show resolved Hide resolved
@JoJoJet
Copy link
Member Author

JoJoJet commented Feb 27, 2023

While I wouldn't consider this a breaking change (the safety invariant already existed, it was just undocumented), it probably would be good to put this in the migration guide.

@JoJoJet
Copy link
Member Author

JoJoJet commented Feb 27, 2023

There is an alternative solution to this issue that I think is cleaner. We could just add the requirement that System::run_unsafe must be called with the same world that the system was initialized with, which more effectively captures the invariant at play. It would make the current default impl of System::run more complicated though, and it would probably be best to just remove that default implementation entirely. That would of course be a truly breaking change though.

Copy link
Contributor

@Carter0 Carter0 left a comment

Choose a reason for hiding this comment

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

I suppose this is better than nothing. But why not write another function that validates the world is the correct one and give it its own name rather than using update_archetype_component?

The name update_archetype_component doesn't imply that it checks the world at all. And without the comment explaining why you are calling it, this would be a confusing line of code.

@JoJoJet
Copy link
Member Author

JoJoJet commented Mar 7, 2023

I did it in this way because exclusive systems don't actually need to validate the world, and update_archetype_component_access is currently a no-op for them, so calling it is free. I didn't want to introduce extra costs for exclusive systems by requiring them to validate the world.

@JoJoJet
Copy link
Member Author

JoJoJet commented Mar 7, 2023

Actually, that part isn't as much of an issue since exclusive systems don't even use run_unsafe.

However, adding a separate method to validate the world is problematic in that it introduces a new dynamic dispatch call every time a system gets run. Performing the world validation in update_archetype_component_access is free, because that method is getting called anyway.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2023

Example alien_cake_addict failed to run, please try running it locally and check the result.

@JoJoJet
Copy link
Member Author

JoJoJet commented Mar 9, 2023

That's been fixed, the bot is just slow.

@JoJoJet
Copy link
Member Author

JoJoJet commented Mar 29, 2023

here is an alternative solution to this issue that I think is cleaner. We could just add the requirement that System::run_unsafe must be called with the same world that the system was initialized with, which more effectively captures the invariant at play. It would make the current default impl of System::run more complicated though, and it would probably be best to just remove that default implementation entirely

I've tried that approach a couple of times since leaving that comment, and I find that it ends up being messier than the approach used in this PR.

Copy link
Contributor

@hymm hymm left a comment

Choose a reason for hiding this comment

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

New safety invariants make sense to me. Unfortunate that we need to make some more of the functions unsafe, but this pr is just exposing an existing problem.

Just had the one question about a TODO and then this looks good to me.

@@ -444,7 +456,9 @@ impl MultiThreadedExecutor {
#[cfg(feature = "trace")]
let system_guard = system_span.enter();
let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
// SAFETY: access is compatible
// SAFETY:
// - Access: TODO.
Copy link
Contributor

Choose a reason for hiding this comment

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

is this meant to stay as a TODO?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. The previous "access is compatible" comment is incorrect, since it's not supported by any invariants in this function. I'd rather be explicit that these invariants aren't fully fleshed out yet than pretend that they are. I have a real fix for this in #8292.

@hymm hymm 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 Apr 15, 2023
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Apr 17, 2023
Merged via the queue into bevyengine:main with commit 328347f Apr 17, 2023
@JoJoJet JoJoJet deleted the system-world-unsoundness branch April 17, 2023 15:51
@Selene-Amanita Selene-Amanita added the M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide label Jul 10, 2023
@github-actions
Copy link
Contributor

It looks like your PR is a breaking change, but you didn't provide a migration guide.

Could you add some context on what users should update when this change get released in a new version of Bevy?
It will be used to help writing the migration guide for the version. Putting it after a ## Migration Guide will help it get automatically picked up by our tooling.

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-Code-Quality A section of code that is hard to understand or change M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide 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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants