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

Fix vec_deque::Drain FIXME #106276

Merged
merged 3 commits into from
Mar 11, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 10 additions & 25 deletions library/alloc/src/collections/vec_deque/drain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,31 +57,16 @@ impl<'a, T, A: Allocator> Drain<'a, T, A> {
unsafe fn as_slices(&self) -> (*mut [T], *mut [T]) {
unsafe {
let deque = self.deque.as_ref();
// FIXME: This is doing almost exactly the same thing as the else branch in `VecDeque::slice_ranges`.
// Unfortunately, we can't just call `slice_ranges` here, as the deque's `len` is currently
// just `drain_start`, so the range check would (almost) always panic. Between temporarily
// adjusting the deques `len` to call `slice_ranges`, and just copy pasting the `slice_ranges`
// implementation, this seemed like the less hacky solution, though it might be good to
// find a better one in the future.

// because `self.remaining != 0`, we know that `self.idx < deque.original_len`, so it's a valid
// logical index.
let wrapped_start = deque.to_physical_idx(self.idx);

let head_len = deque.capacity() - wrapped_start;

let (a_range, b_range) = if head_len >= self.remaining {
(wrapped_start..wrapped_start + self.remaining, 0..0)
} else {
let tail_len = self.remaining - head_len;
(wrapped_start..deque.capacity(), 0..tail_len)
};

// SAFETY: the range `self.idx..self.idx+self.remaining` lies strictly inside
// the range `0..deque.original_len`. because of this, and because of the fact
// that we acquire `a_range` and `b_range` exactly like `slice_ranges` would,
// it's guaranteed that `a_range` and `b_range` represent valid ranges into
// the deques buffer.

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;
Copy link
Member

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.

Copy link
Member Author

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;?

Copy link
Member

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: `start..end` represents the range of elements that
// haven't been drained yet, so they're all initialized,
// and `slice::range(start..end, end) == start..end`,
// so the preconditions for `slice_ranges` are met.
let (a_range, b_range) = deque.slice_ranges(start..end, end);
(deque.buffer_range(a_range), deque.buffer_range(b_range))
}
}
Expand Down
26 changes: 18 additions & 8 deletions library/alloc/src/collections/vec_deque/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,7 @@ impl<T, A: Allocator> VecDeque<T, A> {
#[inline]
#[stable(feature = "deque_extras_15", since = "1.5.0")]
pub fn as_slices(&self) -> (&[T], &[T]) {
let (a_range, b_range) = self.slice_ranges(..);
let (a_range, b_range) = self.slice_ranges(.., self.len);
// SAFETY: `slice_ranges` always returns valid ranges into
// the physical buffer.
unsafe { (&*self.buffer_range(a_range), &*self.buffer_range(b_range)) }
Expand Down Expand Up @@ -1181,7 +1181,7 @@ impl<T, A: Allocator> VecDeque<T, A> {
#[inline]
#[stable(feature = "deque_extras_15", since = "1.5.0")]
pub fn as_mut_slices(&mut self) -> (&mut [T], &mut [T]) {
let (a_range, b_range) = self.slice_ranges(..);
let (a_range, b_range) = self.slice_ranges(.., self.len);
// SAFETY: `slice_ranges` always returns valid ranges into
// the physical buffer.
unsafe { (&mut *self.buffer_range(a_range), &mut *self.buffer_range(b_range)) }
Expand Down Expand Up @@ -1223,19 +1223,29 @@ impl<T, A: Allocator> VecDeque<T, A> {

/// Given a range into the logical buffer of the deque, this function
/// return two ranges into the physical buffer that correspond to
/// 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
/// a `Drain`, then `self.len` is not actually the length of the deque.
///
/// # Safety
///
/// This function is always safe to call. For the resulting ranges to be valid
/// ranges into the physical buffer, the caller must ensure that for all possible
/// 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
Copy link
Member

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"?

Copy link
Member Author

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.

Copy link
Member Author

@Sp00ph Sp00ph Feb 20, 2023

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.

Copy link
Member Author

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.

Copy link
Member

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 and len, the result of calling slice::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?

Copy link
Member Author

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.

/// in that range are initialized.
fn slice_ranges<R>(&self, range: R, len: usize) -> (Range<usize>, Range<usize>)
Copy link
Member

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.

Copy link
Member Author

@Sp00ph Sp00ph Feb 20, 2023

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.

where
R: RangeBounds<usize>,
{
let Range { start, end } = slice::range(range, ..self.len);
let Range { start, end } = slice::range(range, ..len);
let len = end - start;

if len == 0 {
(0..0, 0..0)
} else {
// `slice::range` guarantees that `start <= end <= self.len`.
// because `len != 0`, we know that `start < end`, so `start < self.len`
// because `len != 0`, we know that `start < end`, so `start < len`
// and the indexing is valid.
let wrapped_start = self.to_physical_idx(start);

Expand Down Expand Up @@ -1281,7 +1291,7 @@ impl<T, A: Allocator> VecDeque<T, A> {
where
R: RangeBounds<usize>,
{
let (a_range, b_range) = self.slice_ranges(range);
let (a_range, b_range) = self.slice_ranges(range, self.len);
// SAFETY: The ranges returned by `slice_ranges`
// are valid ranges into the physical buffer, so
// it's ok to pass them to `buffer_range` and
Expand Down Expand Up @@ -1321,7 +1331,7 @@ impl<T, A: Allocator> VecDeque<T, A> {
where
R: RangeBounds<usize>,
{
let (a_range, b_range) = self.slice_ranges(range);
let (a_range, b_range) = self.slice_ranges(range, self.len);
// SAFETY: The ranges returned by `slice_ranges`
// are valid ranges into the physical buffer, so
// it's ok to pass them to `buffer_range` and
Expand Down