-
Notifications
You must be signed in to change notification settings - Fork 220
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
Conversation
src/storage/check.rs
Outdated
D: 'a, | ||
{ | ||
#[inline] | ||
fn assert_same_storage(&self, storage: &Storage<'a, T, D>) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
0a998cc
to
77ee78c
Compare
7ff5e07
to
ba4fef2
Compare
/// component. | ||
pub struct CheckStorage<'a, T, D> { | ||
/// Allows iterating over a storage without borrowing the storage itself. | ||
pub struct CheckStorage { |
There was a problem hiding this comment.
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?
There was a problem hiding this 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. |
There was a problem hiding this comment.
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
?
src/storage/restrict.rs
Outdated
|
||
/// Builds a parallel `RestrictedStorage`, does not allow mutably getting other components | ||
/// aside from the current iteration. | ||
pub fn par_restrict<'rf>(&'rf self) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
ee356c5
to
b5e1e1e
Compare
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>, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/storage/restrict.rs
Outdated
/// Generic flags for the restricted storage to determine | ||
/// the type of restriction. | ||
pub mod restrict_type { | ||
pub struct Normal; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs missing
407e0e2
to
16f44f2
Compare
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, |
There was a problem hiding this comment.
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.
src/storage/tests.rs
Outdated
} | ||
} | ||
} | ||
|
||
#[test] | ||
#[should_panic] | ||
//#[should_panic] |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
24e327d
to
695a71c
Compare
src/storage/restrict.rs
Outdated
{ | ||
bitset: B, | ||
data: R, | ||
entities: &'rf Fetch<'st, EntitiesRes>, |
There was a problem hiding this comment.
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.
src/storage/mod.rs
Outdated
@@ -18,6 +19,7 @@ use shred::Fetch; | |||
use {Component, EntitiesRes, Entity, Index, Join, ParJoin}; | |||
|
|||
mod check; | |||
mod restrict; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
0adda8d
to
602ab4b
Compare
I'm ready to merge this one, but I'd like to have another review. |
src/storage/restrict.rs
Outdated
/// 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
Alright, the use case for |
Alright, removed the immutable restricted storage and just made the |
src/storage/restrict.rs
Outdated
/// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
documentation is outdated
src/storage/restrict.rs
Outdated
/// | ||
/// Functions similar to the normal `Storage::get_mut` implementation. | ||
/// | ||
/// Note: This only works if this is a parallel and mutable `RestrictedStorage`. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
src/storage/restrict.rs
Outdated
/// the type of restriction. | ||
pub mod restrict_type { | ||
/// Specifies that the `RestrictedStorage` cannot run in parallel. | ||
pub struct Normal; |
There was a problem hiding this comment.
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 {}
There was a problem hiding this comment.
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.
src/storage/restrict.rs
Outdated
/// Specifies that the `RestrictedStorage` cannot run in parallel. | ||
pub struct Normal; | ||
/// Specifies that the `RestrictedStorage` can run in parallel. | ||
pub struct Parallel; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
});
src/storage/restrict.rs
Outdated
} | ||
|
||
|
||
unsafe impl<'rf, 'st: 'rf, B, T, R, ParallelRestriction> ParJoin for &'rf RestrictedStorage<'rf, 'st, B, T, R, ParallelRestriction> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/storage/restrict.rs
Outdated
B: Borrow<BitSet> + 'rf, | ||
{ } | ||
|
||
unsafe impl<'rf, 'st: 'rf, B, T, R, ParallelRestriction> ParJoin for &'rf mut RestrictedStorage<'rf, 'st, B, T, R, ParallelRestriction> |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
.
src/storage/restrict.rs
Outdated
/// 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> |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
src/storage/restrict.rs
Outdated
/// 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> |
There was a problem hiding this comment.
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?
@Aceeri Please rebase so we can merge this PR. |
Remove immutable parallel restrict Impl Send/Sync for Entry
@torkleyy Rebased so it should be fine now. |
Nice, thank you! bors r+ |
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.
Build succeeded |
204: Update dependency versions. r=azriel91 a=azriel91 Closes amethyst#203. Co-authored-by: Azriel Hoh <[email protected]>
Splits
CheckStorage
intoRestrictedStorage
andCheckStorage
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.