Skip to content

Commit 85e1c3b

Browse files
authored
Rollup merge of #71568 - hbina:document_unsafety_slice_sort, r=joshtriplett
Document unsafety in slice/sort.rs Let me know if these documentations are accurate c: I don't think I am capable enough to document the safety of `partition_blocks`, however. Related issue #66219
2 parents 72417d8 + 5a9df84 commit 85e1c3b

File tree

1 file changed

+70
-3
lines changed

1 file changed

+70
-3
lines changed

src/libcore/slice/sort.rs

+70-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Slice sorting
22
//!
3-
//! This module contains an sort algorithm based on Orson Peters' pattern-defeating quicksort,
3+
//! This module contains a sorting algorithm based on Orson Peters' pattern-defeating quicksort,
44
//! published at: https://github.com/orlp/pdqsort
55
//!
66
//! Unstable sorting is compatible with libcore because it doesn't allocate memory, unlike our
@@ -20,6 +20,9 @@ struct CopyOnDrop<T> {
2020

2121
impl<T> Drop for CopyOnDrop<T> {
2222
fn drop(&mut self) {
23+
// SAFETY: This is a helper class.
24+
// Please refer to its usage for correctness.
25+
// Namely, one must be sure that `src` and `dst` does not overlap as required by `ptr::copy_nonoverlapping`.
2326
unsafe {
2427
ptr::copy_nonoverlapping(self.src, self.dest, 1);
2528
}
@@ -32,6 +35,21 @@ where
3235
F: FnMut(&T, &T) -> bool,
3336
{
3437
let len = v.len();
38+
// SAFETY: The unsafe operations below involves indexing without a bound check (`get_unchecked` and `get_unchecked_mut`)
39+
// and copying memory (`ptr::copy_nonoverlapping`).
40+
//
41+
// a. Indexing:
42+
// 1. We checked the size of the array to >=2.
43+
// 2. All the indexing that we will do is always between {0 <= index < len} at most.
44+
//
45+
// b. Memory copying
46+
// 1. We are obtaining pointers to references which are guaranteed to be valid.
47+
// 2. They cannot overlap because we obtain pointers to difference indices of the slice.
48+
// Namely, `i` and `i-1`.
49+
// 3. If the slice is properly aligned, the elements are properly aligned.
50+
// It is the caller's responsibility to make sure the slice is properly aligned.
51+
//
52+
// See comments below for further detail.
3553
unsafe {
3654
// If the first two elements are out-of-order...
3755
if len >= 2 && is_less(v.get_unchecked(1), v.get_unchecked(0)) {
@@ -62,6 +80,21 @@ where
6280
F: FnMut(&T, &T) -> bool,
6381
{
6482
let len = v.len();
83+
// SAFETY: The unsafe operations below involves indexing without a bound check (`get_unchecked` and `get_unchecked_mut`)
84+
// and copying memory (`ptr::copy_nonoverlapping`).
85+
//
86+
// a. Indexing:
87+
// 1. We checked the size of the array to >= 2.
88+
// 2. All the indexing that we will do is always between `0 <= index < len-1` at most.
89+
//
90+
// b. Memory copying
91+
// 1. We are obtaining pointers to references which are guaranteed to be valid.
92+
// 2. They cannot overlap because we obtain pointers to difference indices of the slice.
93+
// Namely, `i` and `i+1`.
94+
// 3. If the slice is properly aligned, the elements are properly aligned.
95+
// It is the caller's responsibility to make sure the slice is properly aligned.
96+
//
97+
// See comments below for further detail.
6598
unsafe {
6699
// If the last two elements are out-of-order...
67100
if len >= 2 && is_less(v.get_unchecked(len - 1), v.get_unchecked(len - 2)) {
@@ -103,6 +136,8 @@ where
103136
let mut i = 1;
104137

105138
for _ in 0..MAX_STEPS {
139+
// SAFETY: We already explicitly did the bound checking with `i < len`.
140+
// All our subsequent indexing is only in the range `0 <= index < len`
106141
unsafe {
107142
// Find the next pair of adjacent out-of-order elements.
108143
while i < len && !is_less(v.get_unchecked(i), v.get_unchecked(i - 1)) {
@@ -220,6 +255,7 @@ where
220255
let mut offsets_l = [MaybeUninit::<u8>::uninit(); BLOCK];
221256

222257
// The current block on the right side (from `r.sub(block_r)` to `r`).
258+
// SAFETY: The documentation for .add() specifically mention that `vec.as_ptr().add(vec.len())` is always safe`
223259
let mut r = unsafe { l.add(v.len()) };
224260
let mut block_r = BLOCK;
225261
let mut start_r = ptr::null_mut();
@@ -268,6 +304,16 @@ where
268304
let mut elem = l;
269305

270306
for i in 0..block_l {
307+
// SAFETY: The unsafety operations below involve the usage of the `offset`.
308+
// According to the conditions required by the function, we satisfy them because:
309+
// 1. `offsets_l` is stack-allocated, and thus considered separate allocated object.
310+
// 2. The function `is_less` returns a `bool`.
311+
// Casting a `bool` will never overflow `isize`.
312+
// 3. We have guaranteed that `block_l` will be `<= BLOCK`.
313+
// Plus, `end_l` was initially set to the begin pointer of `offsets_` which was declared on the stack.
314+
// Thus, we know that even in the worst case (all invocations of `is_less` returns false) we will only be at most 1 byte pass the end.
315+
// Another unsafety operation here is dereferencing `elem`.
316+
// However, `elem` was initially the begin pointer to the slice which is always valid.
271317
unsafe {
272318
// Branchless comparison.
273319
*end_l = i as u8;
@@ -284,6 +330,17 @@ where
284330
let mut elem = r;
285331

286332
for i in 0..block_r {
333+
// SAFETY: The unsafety operations below involve the usage of the `offset`.
334+
// According to the conditions required by the function, we satisfy them because:
335+
// 1. `offsets_r` is stack-allocated, and thus considered separate allocated object.
336+
// 2. The function `is_less` returns a `bool`.
337+
// Casting a `bool` will never overflow `isize`.
338+
// 3. We have guaranteed that `block_r` will be `<= BLOCK`.
339+
// Plus, `end_r` was initially set to the begin pointer of `offsets_` which was declared on the stack.
340+
// Thus, we know that even in the worst case (all invocations of `is_less` returns true) we will only be at most 1 byte pass the end.
341+
// Another unsafety operation here is dereferencing `elem`.
342+
// However, `elem` was initially `1 * sizeof(T)` past the end and we decrement it by `1 * sizeof(T)` before accessing it.
343+
// Plus, `block_r` was asserted to be less than `BLOCK` and `elem` will therefore at most be pointing to the beginning of the slice.
287344
unsafe {
288345
// Branchless comparison.
289346
elem = elem.offset(-1);
@@ -404,8 +461,13 @@ where
404461
// Find the first pair of out-of-order elements.
405462
let mut l = 0;
406463
let mut r = v.len();
464+
465+
// SAFETY: The unsafety below involves indexing an array.
466+
// For the first one: We already do the bounds checking here with `l < r`.
467+
// For the second one: We initially have `l == 0` and `r == v.len()` and we checked that `l < r` at every indexing operation.
468+
// From here we know that `r` must be at least `r == l` which was shown to be valid from the first one.
407469
unsafe {
408-
// Find the first element greater then or equal to the pivot.
470+
// Find the first element greater than or equal to the pivot.
409471
while l < r && is_less(v.get_unchecked(l), pivot) {
410472
l += 1;
411473
}
@@ -444,6 +506,7 @@ where
444506

445507
// Read the pivot into a stack-allocated variable for efficiency. If a following comparison
446508
// operation panics, the pivot will be automatically written back into the slice.
509+
// SAFETY: The pointer here is valid because it is obtained from a reference to a slice.
447510
let mut tmp = mem::ManuallyDrop::new(unsafe { ptr::read(pivot) });
448511
let _pivot_guard = CopyOnDrop { src: &mut *tmp, dest: pivot };
449512
let pivot = &*tmp;
@@ -452,8 +515,12 @@ where
452515
let mut l = 0;
453516
let mut r = v.len();
454517
loop {
518+
// SAFETY: The unsafety below involves indexing an array.
519+
// For the first one: We already do the bounds checking here with `l < r`.
520+
// For the second one: We initially have `l == 0` and `r == v.len()` and we checked that `l < r` at every indexing operation.
521+
// From here we know that `r` must be at least `r == l` which was shown to be valid from the first one.
455522
unsafe {
456-
// Find the first element greater that the pivot.
523+
// Find the first element greater than the pivot.
457524
while l < r && !is_less(pivot, v.get_unchecked(l)) {
458525
l += 1;
459526
}

0 commit comments

Comments
 (0)