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

CheckStorage soundness fixes #203

Merged
merged 8 commits into from
Aug 11, 2017
Merged

CheckStorage soundness fixes #203

merged 8 commits into from
Aug 11, 2017

Conversation

Aceeri
Copy link
Member

@Aceeri Aceeri commented Jun 20, 2017

Splits CheckStorage into RestrictedStorage and CheckStorage for different usecases.

CheckStorage will just not borrow the storage, however it does not protect against the original storage having its bitset modified, so no component or entry is given for unchecked access. RestrictedStorage returns an entry and a reference back to the restricted storage every iteration. This allows restricted access to the storage to get and possibly modify components without invalidating the bitset.

D: 'a,
{
#[inline]
fn assert_same_storage(&self, storage: &Storage<'a, T, D>) {
Copy link
Member

Choose a reason for hiding this comment

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

Would it be possible to use something like what this library does to get rid of these asserts? Just remembered the idea so I am not sure how feasible it would be.

Copy link
Member Author

@Aceeri Aceeri Jun 21, 2017

Choose a reason for hiding this comment

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

Not sure... I don't really understand how it works. Another way to make the storage a unique type is by inserting a closure into it, however, that makes a god awful error that would probably just confuse people who use it.

Copy link
Member

@WaDelma WaDelma Jun 21, 2017

Choose a reason for hiding this comment

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

Afaik it forces creation of new lifetime for the index and then ensures that that lifetimed index can only be used with right container. You actually need closure for it too. I didn't remember that was the case.

Maybe it isn't that great idea to use it.

@Aceeri Aceeri force-pushed the check branch 4 times, most recently from 0a998cc to 77ee78c Compare June 25, 2017 00:45
@Aceeri Aceeri changed the title [WIP] CheckStorage soundness fixes CheckStorage soundness fixes Jun 25, 2017
@Aceeri Aceeri force-pushed the check branch 2 times, most recently from 7ff5e07 to ba4fef2 Compare July 1, 2017 01:55
/// component.
pub struct CheckStorage<'a, T, D> {
/// Allows iterating over a storage without borrowing the storage itself.
pub struct CheckStorage {
Copy link
Member

Choose a reason for hiding this comment

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

Looks good overall, but as far as I see it it requires a version bump, right?

Copy link
Member

@torkleyy torkleyy left a comment

Choose a reason for hiding this comment

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

Nice, thanks! Not sure about Entry yet, see below.

{
/// Attempts to get the component related to the entity mutably.
///
/// Functions similar to the normal `Storage::get_mut` implementation.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should add a note that it only works for restrict_type::Normal?


/// Builds a parallel `RestrictedStorage`, does not allow mutably getting other components
/// aside from the current iteration.
pub fn par_restrict<'rf>(&'rf self)
Copy link
Member

Choose a reason for hiding this comment

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

Do we really need to methods for immutable restriction storages? Because they both don't allow mutable access, I don't see how their functionality is different.

Copy link
Member Author

Choose a reason for hiding this comment

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

Probably not necessary.

}

/// An entry to a storage.
pub struct Entry<'rf, T>
Copy link
Member

Choose a reason for hiding this comment

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

At this point, we should evaluate which of Send / Sync this type conforms to.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it is safe for it to both be Send/Sync? It lives as long as the reference to the storage it belongs to, and only works in a Join, however I don't see how much use it has in those scenarios.

@Aceeri Aceeri force-pushed the check branch 2 times, most recently from ee356c5 to b5e1e1e Compare July 2, 2017 13:13
@torkleyy torkleyy added ready and removed in progress labels Jul 2, 2017
examples/full.rs Outdated
@@ -50,6 +50,7 @@ struct Sum(usize);
struct IntAndBoolData<'a> {
comp_int: ReadStorage<'a, CompInt>,
comp_bool: WriteStorage<'a, CompBool>,
entities: Entities<'a>,
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we need this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, that was mainly for quick testing reasons.

/// Generic flags for the restricted storage to determine
/// the type of restriction.
pub mod restrict_type {
pub struct Normal;
Copy link
Member

Choose a reason for hiding this comment

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

docs missing

@Aceeri Aceeri force-pushed the check branch 4 times, most recently from 407e0e2 to 16f44f2 Compare July 2, 2017 15:42
src/lib.rs Outdated
HashMapStorage, InsertResult, NullStorage, ReadStorage, Storage,
UnprotectedStorage, VecStorage, WriteStorage};
pub use storage::{BTreeStorage, CheckStorage, DenseVecStorage, DistinctStorage, Entry, FlaggedStorage,
HashMapStorage, InsertResult, NullStorage, ReadStorage, restrict_type, RestrictedStorage,
Copy link
Member

Choose a reason for hiding this comment

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

Let's move restrict_type to the end of the list.

}
}
}

#[test]
#[should_panic]
//#[should_panic]
Copy link
Member

Choose a reason for hiding this comment

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

err..is there anything wrong with the check or did you fix it already?

Copy link
Member Author

Choose a reason for hiding this comment

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

Forgot about that, just needed to add insertions to the s2 storage.

@Aceeri Aceeri force-pushed the check branch 3 times, most recently from 24e327d to 695a71c Compare July 6, 2017 15:16
{
bitset: B,
data: R,
entities: &'rf Fetch<'st, EntitiesRes>,
Copy link
Member

Choose a reason for hiding this comment

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

Could just use Entities here.

@@ -18,6 +19,7 @@ use shred::Fetch;
use {Component, EntitiesRes, Entity, Index, Join, ParJoin};

mod check;
mod restrict;
Copy link
Member

Choose a reason for hiding this comment

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

Could you move the module declaration down by one, please?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah sorry, forgot about the alphabetical order for modules.

@Aceeri Aceeri force-pushed the check branch 2 times, most recently from 0adda8d to 602ab4b Compare July 8, 2017 01:53
@torkleyy
Copy link
Member

I'm ready to merge this one, but I'd like to have another review.

@torkleyy torkleyy requested a review from a team July 21, 2017 11:56
/// Builds an immutable `RestrictedStorage` out of a `Storage`. Allows restricted
/// access to the inner components without allowing invalidating the
/// bitset for iteration in `Join`.
pub fn restrict<'rf>(&'rf self)
Copy link
Member

Choose a reason for hiding this comment

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

Is there a point in this read-only restricted storage? I'd assume that a regular MaskedStorage is equivalent since it doesn't allow adding/removing the components (and thus modifying the bitmask) either.

Copy link
Member Author

Choose a reason for hiding this comment

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

There isn't too much difference, this one just defers the "getting" of the component to when the user wants to get it, I'm not sure how useful that is with just being able to get an immutable reference though, so it probably doesn't have much of a point being immutable.

/// component.
pub struct CheckStorage<'a, T, D> {
/// Allows iterating over a storage without borrowing the storage itself.
pub struct CheckStorage {
Copy link
Member

Choose a reason for hiding this comment

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

What's a good use case for this new CheckStorage?

Copy link
Member Author

Choose a reason for hiding this comment

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

If you need to insert/remove components from the storage, this allows you to do that with a single loop rather than loop over all of them, find the ones you need to remove, store them in a Vec, then loop over those and remove them.

Choose a reason for hiding this comment

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

If you need to insert/remove components from the storage, this allows you to do that with a single loop rather than loop over all of them, find the ones you need to remove, store them in a Vec, then loop over those and remove them.

What is the performance difference with this single loop over the loop/deleteloop style?

Copy link
Member Author

@Aceeri Aceeri Jul 25, 2017

Choose a reason for hiding this comment

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

@OvermindDL1 I'm not entirely sure? It would probably be faster if the compiler didn't optimize anything out, but in this case it probably is, so this is more for just ease of use along with making it easier to slide around the borrow checker.

@kvark
Copy link
Member

kvark commented Jul 24, 2017

Alright, the use case for CheckStorage is a bit weak, but it's there.
Read-only RestrictedStorage doesn't seem to have one, so I'd prefer this concept limited to mutability only.

@Aceeri
Copy link
Member Author

Aceeri commented Jul 25, 2017

Alright, removed the immutable restricted storage and just made the restrict_mut into restrict instead.

/// Functions similar to the normal `Storage::get_mut` implementation.
///
/// Note: This only works if this is a parallel and mutable `RestrictedStorage`.
/// You can get one by using the `par_restrict_mut` method instead of the normal
Copy link
Member

Choose a reason for hiding this comment

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

documentation is outdated

///
/// Functions similar to the normal `Storage::get_mut` implementation.
///
/// Note: This only works if this is a parallel and mutable `RestrictedStorage`.
Copy link
Member

Choose a reason for hiding this comment

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

Note says this is for parallel, but the actual implementation is for Normal. Where is the truth?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, I probably meant to put "non-parallel" there.

/// the type of restriction.
pub mod restrict_type {
/// Specifies that the `RestrictedStorage` cannot run in parallel.
pub struct Normal;
Copy link
Member

Choose a reason for hiding this comment

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

enums are better phantom types, and no need for a separate module, really.

enum NormalRestriction {}
enum ParallelRestriction {}

Copy link
Member Author

Choose a reason for hiding this comment

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

True, didn't know about never types before the asset management stuff.

/// Specifies that the `RestrictedStorage` cannot run in parallel.
pub struct Normal;
/// Specifies that the `RestrictedStorage` can run in parallel.
pub struct Parallel;
Copy link
Member

Choose a reason for hiding this comment

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

could you explain a bit on how these types help you exactly?

Copy link
Member Author

Choose a reason for hiding this comment

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

I have to prevent get_mut on it when you use rayon, since that would mean you could get 2 mutable references to the same entity.

Copy link
Member Author

Choose a reason for hiding this comment

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

Basically, I'm enforcing that you can do this:

(&comp_int, &mut comp_float.par_restrict())
    .par_join()
    .for_each(|(i, (entry, restricted))| {
        let f = restricted.get_mut_unchecked(&entry);
    });

But you can't do this:

(&comp_int, &mut comp_float.par_restrict())
    .par_join()
    .for_each(|(i, (entry, restricted))| {
        let f = restricted.get_mut(&entity); // Some entity stored somewhere.
    });

}


unsafe impl<'rf, 'st: 'rf, B, T, R, ParallelRestriction> ParJoin for &'rf RestrictedStorage<'rf, 'st, B, T, R, ParallelRestriction>
Copy link
Member

Choose a reason for hiding this comment

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

I don't think you want ParallelRestriction in the impl block here

Copy link
Member Author

Choose a reason for hiding this comment

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

ParallelRestriction was the correct one, since it removes the get_mut method for the RestrictedStorage.

Copy link
Member

Choose a reason for hiding this comment

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

I read it as: you put ParallelRestriction into the impl<...> section, which defines a new local generic type with the same name as your phantom type. This is not what you want here.

Copy link
Member Author

@Aceeri Aceeri Jul 26, 2017

Choose a reason for hiding this comment

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

Oh misread what you put, I thought you meant it should be a NormalRestriction. I must've done a s/RT/ParallelRestriction instead of just replacing the RestrictedStorage one.

B: Borrow<BitSet> + 'rf,
{ }

unsafe impl<'rf, 'st: 'rf, B, T, R, ParallelRestriction> ParJoin for &'rf mut RestrictedStorage<'rf, 'st, B, T, R, ParallelRestriction>
Copy link
Member

Choose a reason for hiding this comment

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

same here... what's the difference between these blocks anyway?

Copy link
Member Author

Choose a reason for hiding this comment

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

It does the same thing as the ParJoin implementation for Storage.

/// access to the inner components without allowing invalidating the
/// bitset for iteration in `Join`.
pub fn restrict<'rf>(&'rf mut self)
-> RestrictedStorage<'rf, 'st, &'rf BitSet, T, &'rf mut T::Storage, NormalRestriction>
Copy link
Member

Choose a reason for hiding this comment

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

I think lifetime elision could be used here to omit rf entirely?

{
/// Gets the component related to the current entry without checking whether
/// the storage has it or not.
pub fn get_mut_unchecked(&mut self, entry: &Entry<'rf, T>) -> &mut T {
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't this be unsafe?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is actually safe since it requires an Entry, which can't live longer than the iteration.

/// to only getting and modifying the components. That means nothing that would
/// modify the inner bitset so the iteration cannot be invalidated. For example,
/// no insertion or removal is allowed.
pub struct RestrictedStorage<'rf, 'st: 'rf, B, T, R, RT>
Copy link
Member

Choose a reason for hiding this comment

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

Could we have an example usage?

@torkleyy
Copy link
Member

torkleyy commented Aug 9, 2017

@Aceeri Please rebase so we can merge this PR.

@torkleyy torkleyy self-assigned this Aug 9, 2017
@Aceeri
Copy link
Member Author

Aceeri commented Aug 11, 2017

@torkleyy Rebased so it should be fine now.

@torkleyy
Copy link
Member

Nice, thank you!

bors r+

bors bot added a commit that referenced this pull request Aug 11, 2017
203: CheckStorage soundness fixes r=torkleyy

Splits `CheckStorage` into `RestrictedStorage` and `CheckStorage` for different usecases.

`CheckStorage` will just not borrow the storage, however it does not protect against the original storage having its bitset modified, so no component or entry is given for unchecked access. `RestrictedStorage` returns an entry and a reference back to the restricted storage every iteration. This allows restricted access to the storage to get and possibly modify components without invalidating the bitset.
@bors
Copy link
Contributor

bors bot commented Aug 11, 2017

Build succeeded

@bors bors bot merged commit a7bae7c into amethyst:master Aug 11, 2017
xMAC94x pushed a commit to xMAC94x/specs that referenced this pull request Mar 10, 2021
204: Update dependency versions. r=azriel91 a=azriel91

Closes amethyst#203.

Co-authored-by: Azriel Hoh <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants