Skip to content

Commit 8ffa11c

Browse files
committed
Fix off-by-one error causing driftsort to crash
Fixes #136103. Based on the analysis by @jonathan-gruber-jg and @orlp.
1 parent ebcf860 commit 8ffa11c

File tree

4 files changed

+64
-7
lines changed

4 files changed

+64
-7
lines changed

library/core/src/slice/sort/stable/drift.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use crate::{cmp, intrinsics};
1010

1111
/// Sorts `v` based on comparison function `is_less`. If `eager_sort` is true,
1212
/// it will only do small-sorts and physical merges, ensuring O(N * log(N))
13-
/// worst-case complexity. `scratch.len()` must be at least `max(v.len() / 2,
14-
/// MIN_SMALL_SORT_SCRATCH_LEN)` otherwise the implementation may abort.
13+
/// worst-case complexity. `scratch.len()` must be at least
14+
/// `max(v.len() - v.len() / 2, MIN_SMALL_SORT_SCRATCH_LEN)` otherwise the implementation may abort.
1515
/// Fully ascending and descending inputs will be sorted with exactly N - 1
1616
/// comparisons.
1717
///

library/core/src/slice/sort/stable/mod.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,18 @@ fn driftsort_main<T, F: FnMut(&T, &T) -> bool, BufT: BufGuard<T>>(v: &mut [T], i
9191
// By allocating n elements of memory we can ensure the entire input can
9292
// be sorted using stable quicksort, which allows better performance on
9393
// random and low-cardinality distributions. However, we still want to
94-
// reduce our memory usage to n / 2 for large inputs. We do this by scaling
95-
// our allocation as max(n / 2, min(n, 8MB)), ensuring we scale like n for
96-
// small inputs and n / 2 for large inputs, without a sudden drop off. We
94+
// reduce our memory usage to n - n / 2 for large inputs. We do this by scaling
95+
// our allocation as max(n - n / 2, min(n, 8MB)), ensuring we scale like n for
96+
// small inputs and n - n / 2 for large inputs, without a sudden drop off. We
9797
// also need to ensure our alloc >= MIN_SMALL_SORT_SCRATCH_LEN, as the
9898
// small-sort always needs this much memory.
9999
const MAX_FULL_ALLOC_BYTES: usize = 8_000_000; // 8MB
100100
let max_full_alloc = MAX_FULL_ALLOC_BYTES / mem::size_of::<T>();
101101
let len = v.len();
102-
let alloc_len =
103-
cmp::max(cmp::max(len / 2, cmp::min(len, max_full_alloc)), SMALL_SORT_GENERAL_SCRATCH_LEN);
102+
let alloc_len = cmp::max(
103+
cmp::max(len - len / 2, cmp::min(len, max_full_alloc)),
104+
SMALL_SORT_GENERAL_SCRATCH_LEN,
105+
);
104106

105107
// For small inputs 4KiB of stack storage suffices, which allows us to avoid
106108
// calling the (de-)allocator. Benchmarks showed this was quite beneficial.

library/core/src/slice/sort/stable/quicksort.rs

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use crate::slice::sort::shared::smallsort::StableSmallSortTypeImpl;
77
use crate::{intrinsics, ptr};
88

99
/// Sorts `v` recursively using quicksort.
10+
/// `scratch.len()` must be at least `max(v.len() - v.len() / 2, MIN_SMALL_SORT_SCRATCH_LEN)`
11+
/// otherwise the implementation may abort.
1012
///
1113
/// `limit` when initialized with `c*log(v.len())` for some c ensures we do not
1214
/// overflow the stack or go quadratic.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//@ run-pass
2+
// Ensures that driftsort doesn't crash under specific slice
3+
// length and memory size.
4+
// Based on the example given in https://github.com/rust-lang/rust/issues/136103.
5+
use std::cmp::Ordering;
6+
use std::hint::black_box;
7+
8+
#[derive(Eq)]
9+
struct S(bool, [u8; 999_999]);
10+
11+
// Don't compare the large array to make
12+
// the test run faster.
13+
14+
impl PartialOrd for S {
15+
#[inline]
16+
fn partial_cmp(&self, other: &S) -> Option<Ordering> {
17+
self.0.partial_cmp(&other.0)
18+
}
19+
}
20+
21+
impl Ord for S {
22+
#[inline]
23+
fn cmp(&self, other: &S) -> Ordering {
24+
self.0.cmp(&other.0)
25+
}
26+
}
27+
28+
impl PartialEq for S {
29+
#[inline]
30+
fn eq(&self, other: &S) -> bool {
31+
self.0 == other.0
32+
}
33+
}
34+
35+
fn main() {
36+
let n = 101;
37+
let mut objs = Vec::with_capacity(n);
38+
39+
let mut on = false;
40+
41+
for _ in 0..n {
42+
objs.push(S(on, [0; 999_999]));
43+
on = !on;
44+
}
45+
46+
objs.sort();
47+
48+
// Ensure that the large array isn't eliminated,
49+
// as that's needed to trigger the bug.
50+
black_box(&objs[0].1);
51+
52+
println!("Succeeded");
53+
}

0 commit comments

Comments
 (0)