-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Fix vec_deque::Drain
FIXME
#106276
Fix vec_deque::Drain
FIXME
#106276
Conversation
(rustbot has picked a reviewer for you, use r? to override) |
Hey! It looks like you've submitted a new PR for the library teams! If this PR contains changes to any Examples of
|
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.
One comment inside the method still talks about self.len
/// the given range. | ||
fn slice_ranges<R>(&self, range: R) -> (Range<usize>, Range<usize>) | ||
/// the given range. The `len` parameter should usually just be `self.len`; | ||
/// the reason it's passed explicitly is that if the deque is wrapped in |
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's an extra whitespace here
let start = self.idx; | ||
// We know that `self.idx + self.remaining <= deque.len <= usize::MAX`, so this won't overflow. | ||
let end = start + self.remaining; |
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.
The variable names here could be clearer.
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 do you propose? Maybe something like let remaining_range = self.idx..self.idx+self.remaining;
?
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. and also that it's a logical index, not offsets into the allocation.
// SAFETY: the range `start..end` lies strictly inside | ||
// the range `0..deque.original_len`. Because of this, and because |
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.
Hrm, this means that end may be smaller than original_len. That happens because one is draining from the middle of the deque, right? But then end isn't deque.len anymore which one is supposed to pass to slice_ranges
(according to the new documentation).
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.
In this case it doesn't really matter which length you pass, as long as it's not smaller than end
. Since the range is trivially in bounds, and we don't pass a RangeFrom
to slice_ranges
(which would then use the len
parameter as the end index), the bounds checking in slice_ranges
is technically redundant anyways. Explicitly calculating original_len
here to pass as a dead bounds check would just result in slightly more unnecessary computations with no difference in behavior.
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.
Well, the documentation should say so? Also, could passing in the wrong length cause any unsafety?
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.
The function itself can't cause any unsafety, it doesn't access any memory and only computes index ranges into the physical buffer. Using it with the wrong inputs and then using the return values to index into the buffer could obviously lead to buffer overruns, for example:
let (a, b) = self.slice_ranges(.., usize::MAX);
// unsafety is introduced here since `b` doesn't satisfy the `buffer_range` preconditions.
let b = unsafe { &*self.buffer_range(b) };
I'm not 100% sure how to word the safety docs for this, as you can't just require the first len
items to be initialized (because Drain
may have already destroyed elements at the beginning of the deque). Would something like
"The caller has to ensure that for all possible inputs, the result of calling slice::range(range, ..len)
is a valid range into the logical array, and all of its elements must be initialized"
be okay? It's kind of a mouthful, but I'm not sure how else to phrase it.
a62348c
to
1e114a8
Compare
/// values of `range` and `len`, the result of calling `slice::range(range, ..len)` | ||
/// represents a valid range into the logical buffer, and that all elements |
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.
That is indeed a bit of a mouthful and requires one to look up with slice::range
does (I wasn't familiar with it). Maybe it's better to say "up to range's end or len, whichever is lower"?
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.
Something like "all elements up to ..." wouldn't cover all our use cases, as Drain::as_slices
calls this function on a deque whose elements in the logical range drain.start..drain.idx
aren't initialized anymore.
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 suppose we could say "all elements in the logical range range.start..cmp::min(range.end, len)
must be initialized", but I'm not sure if that makes it more readable than it currently is.
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.
Also, since there's quite a few types that implement RangeBounds
, I opted for a more precise and perhaps more convoluted comment over one that's simpler but might not cover all use cases or might introduce some ambiguities.
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.
Ok, but even if we keep the slice::range
phrasing I'm still having trouble with the whole paragraph.
the caller must ensure that for all possible values of
range
andlen
, the result of callingslice::range(range, ..len)
represents a valid range into the logical buffer
What does the "for all possible values" add? Does that refer to the covered elements? Or does it just say that every time you pass in garbage you get garbage out?
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.
Eh, the "for all possible values" can just be left out. I'm not sure why I put it there in the first place so I guess I'll just remove it.
/// values of `range` and `len`, the result of calling `slice::range(range, ..len)` | ||
/// represents a valid range into the logical buffer, and that all elements | ||
/// in that range are initialized. | ||
fn slice_ranges<R>(&self, range: R, len: usize) -> (Range<usize>, Range<usize>) |
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 it would be a bit clearer if this were a free function that takes range, capacity, len
as arguments instead of the awkward mix where it takes the capacity from self
but the len
is fed in externally.
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'm not sure that'd actually simplify things. It would always be incorrect to call slice_ranges
with a capacity that's not just self.capacity()
, whereas len
doesn't have the same restriction. Making it a free function would also make both the implementation and the call sites more lengthy and probably less readable (e.g. it couldn't use self.to_physical_idx
) and might introduce more chances to accidentally mix up two arguments of the same type.
@bors r+ rollup |
Fix `vec_deque::Drain` FIXME In my original `VecDeque` rewrite, I didn't use `VecDeque::slice_ranges` in `Drain::as_slices`, even though that's basically the exact use case for `slice_ranges`. The reason for this was that a `VecDeque` wrapped in a `Drain` actually has its length set to `drain_start`, so that there's no potential use after free if you `mem::forget` the `Drain`. I modified `slice_ranges` to accept an explicit `len` parameter instead, which it now uses to bounds check the given range. This way, `Drain::as_slices` can use `slice_ranges` internally instead of having to basically just copy paste the `slice_ranges` code. Since `slice_ranges` is just an internal helper function, this shouldn't change the user facing behavior in any way.
…iaskrgr Rollup of 9 pull requests Successful merges: - rust-lang#106276 (Fix `vec_deque::Drain` FIXME) - rust-lang#107629 (rustdoc: sort deprecated items lower in search) - rust-lang#108711 (Add note when matching token with nonterminal) - rust-lang#108757 (rustdoc: Migrate `document_item_info` to Askama) - rust-lang#108784 (rustdoc: Migrate sidebar rendering to Askama) - rust-lang#108927 (Move __thread_local_inner to sys) - rust-lang#108949 (Honor current target when checking conditional compilation values) - rust-lang#108950 (Directly construct Inherited in typeck.) - rust-lang#108988 (rustdoc: Don't crash on `crate` references in blocks) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
In my original
VecDeque
rewrite, I didn't useVecDeque::slice_ranges
inDrain::as_slices
, even though that's basically the exact use case forslice_ranges
. The reason for this was that aVecDeque
wrapped in aDrain
actually has its length set todrain_start
, so that there's no potential use after free if youmem::forget
theDrain
. I modifiedslice_ranges
to accept an explicitlen
parameter instead, which it now uses to bounds check the given range. This way,Drain::as_slices
can useslice_ranges
internally instead of having to basically just copy paste theslice_ranges
code. Sinceslice_ranges
is just an internal helper function, this shouldn't change the user facing behavior in any way.