Skip to content

Commit 83aa6d4

Browse files
committed
Carefully remove bounds checks from some chunk iterators
1 parent c7e4740 commit 83aa6d4

File tree

1 file changed

+81
-6
lines changed

1 file changed

+81
-6
lines changed

library/core/src/slice/iter.rs

+81-6
Original file line numberDiff line numberDiff line change
@@ -1474,7 +1474,24 @@ impl<'a, T> DoubleEndedIterator for Chunks<'a, T> {
14741474
} else {
14751475
let remainder = self.v.len() % self.chunk_size;
14761476
let chunksz = if remainder != 0 { remainder } else { self.chunk_size };
1477-
let (fst, snd) = self.v.split_at(self.v.len() - chunksz);
1477+
// SAFETY: split_at_unchecked requires the argument be less than or
1478+
// equal to the length. This is guaranteed, but subtle: We need the
1479+
// expression `self.v.len() - sz` not to overflow, which means we
1480+
// need `sz >= tmp_len`.
1481+
//
1482+
// `sz` will always either be `self.v.len() % self.chunk_size`,
1483+
// which will always evaluate to strictly less than `self.v.len()`
1484+
// (or panic, in the case that `self.chunk_size` is zero), or it can
1485+
// be `self.chunk_size`, in the case that the length is exactly
1486+
// divisible by the chunk size.
1487+
//
1488+
// While it seems like using `self.chunk_size` in this case could
1489+
// lead to a value greater than `self.v.len()`, it cannot: if
1490+
// `self.chunk_size` were greater than `self.v.len()`, then
1491+
// `self.v.len() % self.chunk_size` would have returned non-zero
1492+
// (note that in this branch of the `if`, we already know that
1493+
// `self.v` is non-empty).
1494+
let (fst, snd) = unsafe { self.v.split_at_unchecked(self.v.len() - chunksz) };
14781495
self.v = fst;
14791496
Some(snd)
14801497
}
@@ -1639,7 +1656,26 @@ impl<'a, T> DoubleEndedIterator for ChunksMut<'a, T> {
16391656
let sz = if remainder != 0 { remainder } else { self.chunk_size };
16401657
let tmp = mem::replace(&mut self.v, &mut []);
16411658
let tmp_len = tmp.len();
1642-
let (head, tail) = tmp.split_at_mut(tmp_len - sz);
1659+
// SAFETY: split_at_unchecked requires the argument be less than or
1660+
// equal to the length. This is guaranteed, but subtle: We need the
1661+
// expression `tmp_len - sz` not to overflow, which means we need
1662+
// `sz >= tmp_len`.
1663+
//
1664+
// `sz` will always either be `tmp_or_v.len() % self.chunk_size`
1665+
// (where `tmp_or_v` is the slice that at the time was `self.v` but
1666+
// now is `tmp`, and thus `tmp_len` and `tmp_or_v.len()` are the
1667+
// same), which will always evaluate to strictly less than
1668+
// `tmp_or_v.len()` (or panic, in the case that `self.chunk_size` is
1669+
// zero), or it can be `self.chunk_size`, in the case that the
1670+
// length is exactly divisible by the chunk size.
1671+
//
1672+
// While it seems like using `self.chunk_size` in this case could
1673+
// lead to a value greater than `tmp_len`, it cannot: if
1674+
// `self.chunk_size` were greater than `tmp_len`, then
1675+
// `tmp_or_v.len() % self.chunk_size` would have returned non-zero
1676+
// (note that in this branch of the `if`, we already know that
1677+
// `self.v` is non-empty).
1678+
let (head, tail) = unsafe { tmp.split_at_mut_unchecked(tmp_len - sz) };
16431679
self.v = head;
16441680
Some(tail)
16451681
}
@@ -2409,7 +2445,12 @@ impl<'a, T> Iterator for RChunks<'a, T> {
24092445
None
24102446
} else {
24112447
let chunksz = cmp::min(self.v.len(), self.chunk_size);
2412-
let (fst, snd) = self.v.split_at(self.v.len() - chunksz);
2448+
// SAFETY: split_at_unchecked just requires the argument be less
2449+
// than the length. This could only happen if the expression
2450+
// `self.v.len() - chunksz` overflows. This could only happen if
2451+
// `chunksz > self.v.len()`, which is impossible as we initialize it
2452+
// as the `min` of `self.v.len()` and `self.chunk_size`.
2453+
let (fst, snd) = unsafe { self.v.split_at_unchecked(self.v.len() - chunksz) };
24132454
self.v = fst;
24142455
Some(snd)
24152456
}
@@ -2483,7 +2524,21 @@ impl<'a, T> DoubleEndedIterator for RChunks<'a, T> {
24832524
} else {
24842525
let remainder = self.v.len() % self.chunk_size;
24852526
let chunksz = if remainder != 0 { remainder } else { self.chunk_size };
2486-
let (fst, snd) = self.v.split_at(chunksz);
2527+
// SAFETY: split_at_unchecked requires the argument be less than or
2528+
// equal to the length. This is guaranteed, but subtle: `chunksz`
2529+
// will always either be `self.v.len() % self.chunk_size`, which
2530+
// will always evaluate to strictly less than `self.v.len()` (or
2531+
// panic, in the case that `self.chunk_size` is zero), or it can be
2532+
// `self.chunk_size`, in the case that the length is exactly
2533+
// divisible by the chunk size.
2534+
//
2535+
// While it seems like using `self.chunk_size` in this case could
2536+
// lead to a value greater than `self.v.len()`, it cannot: if
2537+
// `self.chunk_size` were greater than `self.v.len()`, then
2538+
// `self.v.len() % self.chunk_size` would return nonzero (note that
2539+
// in this branch of the `if`, we already know that `self.v` is
2540+
// non-empty).
2541+
let (fst, snd) = unsafe { self.v.split_at_unchecked(chunksz) };
24872542
self.v = snd;
24882543
Some(fst)
24892544
}
@@ -2569,7 +2624,12 @@ impl<'a, T> Iterator for RChunksMut<'a, T> {
25692624
let sz = cmp::min(self.v.len(), self.chunk_size);
25702625
let tmp = mem::replace(&mut self.v, &mut []);
25712626
let tmp_len = tmp.len();
2572-
let (head, tail) = tmp.split_at_mut(tmp_len - sz);
2627+
// SAFETY: split_at_mut_unchecked just requires the argument be less
2628+
// than the length. This could only happen if the expression
2629+
// `tmp_len - sz` overflows. This could only happen if `sz >
2630+
// tmp_len`, which is impossible as we initialize it as the `min` of
2631+
// `self.v.len()` (e.g. `tmp_len`) and `self.chunk_size`.
2632+
let (head, tail) = unsafe { tmp.split_at_mut_unchecked(tmp_len - sz) };
25732633
self.v = head;
25742634
Some(tail)
25752635
}
@@ -2647,7 +2707,22 @@ impl<'a, T> DoubleEndedIterator for RChunksMut<'a, T> {
26472707
let remainder = self.v.len() % self.chunk_size;
26482708
let sz = if remainder != 0 { remainder } else { self.chunk_size };
26492709
let tmp = mem::replace(&mut self.v, &mut []);
2650-
let (head, tail) = tmp.split_at_mut(sz);
2710+
// SAFETY: split_at_mut_unchecked requires the argument be less than
2711+
// or equal to the length. This is guaranteed, but subtle: `chunksz`
2712+
// will always either be `tmp_or_v.len() % self.chunk_size` (where
2713+
// `tmp_or_v` is the slice that at the time was `self.v` but now is
2714+
// `tmp`), which will always evaluate to strictly less than
2715+
// `tmp_or_v.len()` (or panic, in the case that `self.chunk_size` is
2716+
// zero), or it can be `self.chunk_size`, in the case that the
2717+
// length is exactly divisible by the chunk size.
2718+
//
2719+
// While it seems like using `self.chunk_size` in this case could
2720+
// lead to a value greater than `tmp_or_v.len()`, it cannot: if
2721+
// `self.chunk_size` were greater than `tmp_or_v.len()`, then
2722+
// `tmp_or_v.len() % self.chunk_size` would return nonzero (note
2723+
// that in this branch of the `if`, we already know that `tmp_or_v`
2724+
// is non-empty).
2725+
let (head, tail) = unsafe { tmp.split_at_mut_unchecked(sz) };
26512726
self.v = tail;
26522727
Some(head)
26532728
}

0 commit comments

Comments
 (0)