Skip to content

Commit 458aaa5

Browse files
saethlinRalfJung
andcommitted
Print the precondition we violated, and visible through output capture
Co-authored-by: Ralf Jung <[email protected]>
1 parent 629a414 commit 458aaa5

File tree

12 files changed

+138
-36
lines changed

12 files changed

+138
-36
lines changed

library/core/src/hint.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pub const unsafe fn unreachable_unchecked() -> ! {
101101
// SAFETY: the safety contract for `intrinsics::unreachable` must
102102
// be upheld by the caller.
103103
unsafe {
104-
intrinsics::assert_unsafe_precondition!(() => false);
104+
intrinsics::assert_unsafe_precondition!("hint::unreachable_unchecked must never be reached", () => false);
105105
intrinsics::unreachable()
106106
}
107107
}

library/core/src/intrinsics.rs

+17-6
Original file line numberDiff line numberDiff line change
@@ -2203,15 +2203,17 @@ extern "rust-intrinsic" {
22032203
/// the occasional mistake, and this check should help them figure things out.
22042204
#[allow_internal_unstable(const_eval_select)] // permit this to be called in stably-const fn
22052205
macro_rules! assert_unsafe_precondition {
2206-
($([$($tt:tt)*])?($($i:ident:$ty:ty),*$(,)?) => $e:expr) => {
2206+
($name:expr, $([$($tt:tt)*])?($($i:ident:$ty:ty),*$(,)?) => $e:expr) => {
22072207
if cfg!(debug_assertions) {
22082208
// allow non_snake_case to allow capturing const generics
22092209
#[allow(non_snake_case)]
22102210
#[inline(always)]
22112211
fn runtime$(<$($tt)*>)?($($i:$ty),*) {
22122212
if !$e {
22132213
// don't unwind to reduce impact on code size
2214-
::core::panicking::panic_str_nounwind("unsafe precondition violated");
2214+
::core::panicking::panic_str_nounwind(
2215+
concat!("unsafe precondition(s) violated: ", $name)
2216+
);
22152217
}
22162218
}
22172219
#[allow(non_snake_case)]
@@ -2350,7 +2352,10 @@ pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: us
23502352
// SAFETY: the safety contract for `copy_nonoverlapping` must be
23512353
// upheld by the caller.
23522354
unsafe {
2353-
assert_unsafe_precondition!([T](src: *const T, dst: *mut T, count: usize) =>
2355+
assert_unsafe_precondition!(
2356+
"ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \
2357+
and the specified memory ranges do not overlap",
2358+
[T](src: *const T, dst: *mut T, count: usize) =>
23542359
is_aligned_and_not_null(src)
23552360
&& is_aligned_and_not_null(dst)
23562361
&& is_nonoverlapping(src, dst, count)
@@ -2436,8 +2441,11 @@ pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
24362441

24372442
// SAFETY: the safety contract for `copy` must be upheld by the caller.
24382443
unsafe {
2439-
assert_unsafe_precondition!([T](src: *const T, dst: *mut T) =>
2440-
is_aligned_and_not_null(src) && is_aligned_and_not_null(dst));
2444+
assert_unsafe_precondition!(
2445+
"ptr::copy requires that both pointer arguments are aligned aligned and non-null",
2446+
[T](src: *const T, dst: *mut T) =>
2447+
is_aligned_and_not_null(src) && is_aligned_and_not_null(dst)
2448+
);
24412449
copy(src, dst, count)
24422450
}
24432451
}
@@ -2505,7 +2513,10 @@ pub const unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize) {
25052513

25062514
// SAFETY: the safety contract for `write_bytes` must be upheld by the caller.
25072515
unsafe {
2508-
assert_unsafe_precondition!([T](dst: *mut T) => is_aligned_and_not_null(dst));
2516+
assert_unsafe_precondition!(
2517+
"ptr::write_bytes requires that the destination pointer is aligned and non-null",
2518+
[T](dst: *mut T) => is_aligned_and_not_null(dst)
2519+
);
25092520
write_bytes(dst, val, count)
25102521
}
25112522
}

library/core/src/num/nonzero.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ macro_rules! nonzero_integers {
5656
pub const unsafe fn new_unchecked(n: $Int) -> Self {
5757
// SAFETY: this is guaranteed to be safe by the caller.
5858
unsafe {
59-
core::intrinsics::assert_unsafe_precondition!((n: $Int) => n != 0);
59+
core::intrinsics::assert_unsafe_precondition!(
60+
concat!(stringify!($Ty), "::new_unchecked requires a non-zero argument"),
61+
(n: $Int) => n != 0
62+
);
6063
Self(n)
6164
}
6265
}

library/core/src/ops/index_range.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ impl IndexRange {
1919
#[inline]
2020
pub const unsafe fn new_unchecked(start: usize, end: usize) -> Self {
2121
// SAFETY: comparisons on usize are pure
22-
unsafe { assert_unsafe_precondition!((start: usize, end: usize) => start <= end) };
22+
unsafe {
23+
assert_unsafe_precondition!(
24+
"IndexRange::new_unchecked requires `start <= end`",
25+
(start: usize, end: usize) => start <= end
26+
)
27+
};
2328
IndexRange { start, end }
2429
}
2530

library/core/src/ptr/alignment.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,12 @@ impl Alignment {
7676
#[inline]
7777
pub const unsafe fn new_unchecked(align: usize) -> Self {
7878
// SAFETY: Precondition passed to the caller.
79-
unsafe { assert_unsafe_precondition!((align: usize) => align.is_power_of_two()) };
79+
unsafe {
80+
assert_unsafe_precondition!(
81+
"Alignment::new_unchecked requires a power of two",
82+
(align: usize) => align.is_power_of_two()
83+
)
84+
};
8085

8186
// SAFETY: By precondition, this must be a power of two, and
8287
// our variants encompass all possible powers of two.

library/core/src/ptr/const_ptr.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,10 @@ impl<T: ?Sized> *const T {
761761
// SAFETY: The comparison has no side-effects, and the intrinsic
762762
// does this check internally in the CTFE implementation.
763763
unsafe {
764-
assert_unsafe_precondition!([T](this: *const T, origin: *const T) => this >= origin)
764+
assert_unsafe_precondition!(
765+
"ptr::sub_ptr requires `this >= origin`",
766+
[T](this: *const T, origin: *const T) => this >= origin
767+
)
765768
};
766769

767770
let pointee_size = mem::size_of::<T>();

library/core/src/ptr/mod.rs

+24-6
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,10 @@ pub const unsafe fn swap_nonoverlapping<T>(x: *mut T, y: *mut T, count: usize) {
889889
// SAFETY: the caller must guarantee that `x` and `y` are
890890
// valid for writes and properly aligned.
891891
unsafe {
892-
assert_unsafe_precondition!([T](x: *mut T, y: *mut T, count: usize) =>
892+
assert_unsafe_precondition!(
893+
"ptr::swap_nonoverlapping requires that both pointer arguments are aligned and non-null \
894+
and the specified memory ranges do not overlap",
895+
[T](x: *mut T, y: *mut T, count: usize) =>
893896
is_aligned_and_not_null(x)
894897
&& is_aligned_and_not_null(y)
895898
&& is_nonoverlapping(x, y, count)
@@ -986,7 +989,10 @@ pub const unsafe fn replace<T>(dst: *mut T, mut src: T) -> T {
986989
// and cannot overlap `src` since `dst` must point to a distinct
987990
// allocated object.
988991
unsafe {
989-
assert_unsafe_precondition!([T](dst: *mut T) => is_aligned_and_not_null(dst));
992+
assert_unsafe_precondition!(
993+
"ptr::replace requires that the pointer argument is aligned and non-null",
994+
[T](dst: *mut T) => is_aligned_and_not_null(dst)
995+
);
990996
mem::swap(&mut *dst, &mut src); // cannot overlap
991997
}
992998
src
@@ -1117,7 +1123,10 @@ pub const unsafe fn read<T>(src: *const T) -> T {
11171123
// Also, since we just wrote a valid value into `tmp`, it is guaranteed
11181124
// to be properly initialized.
11191125
unsafe {
1120-
assert_unsafe_precondition!([T](src: *const T) => is_aligned_and_not_null(src));
1126+
assert_unsafe_precondition!(
1127+
"ptr::read requires that the pointer argument is aligned and non-null",
1128+
[T](src: *const T) => is_aligned_and_not_null(src)
1129+
);
11211130
copy_nonoverlapping(src, tmp.as_mut_ptr(), 1);
11221131
tmp.assume_init()
11231132
}
@@ -1311,7 +1320,10 @@ pub const unsafe fn write<T>(dst: *mut T, src: T) {
13111320
// `dst` cannot overlap `src` because the caller has mutable access
13121321
// to `dst` while `src` is owned by this function.
13131322
unsafe {
1314-
assert_unsafe_precondition!([T](dst: *mut T) => is_aligned_and_not_null(dst));
1323+
assert_unsafe_precondition!(
1324+
"ptr::write requires that the pointer argument is aligned and non-null",
1325+
[T](dst: *mut T) => is_aligned_and_not_null(dst)
1326+
);
13151327
copy_nonoverlapping(&src as *const T, dst, 1);
13161328
intrinsics::forget(src);
13171329
}
@@ -1475,7 +1487,10 @@ pub const unsafe fn write_unaligned<T>(dst: *mut T, src: T) {
14751487
pub unsafe fn read_volatile<T>(src: *const T) -> T {
14761488
// SAFETY: the caller must uphold the safety contract for `volatile_load`.
14771489
unsafe {
1478-
assert_unsafe_precondition!([T](src: *const T) => is_aligned_and_not_null(src));
1490+
assert_unsafe_precondition!(
1491+
"ptr::read_volatile requires that the pointer argument is aligned and non-null",
1492+
[T](src: *const T) => is_aligned_and_not_null(src)
1493+
);
14791494
intrinsics::volatile_load(src)
14801495
}
14811496
}
@@ -1546,7 +1561,10 @@ pub unsafe fn read_volatile<T>(src: *const T) -> T {
15461561
pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
15471562
// SAFETY: the caller must uphold the safety contract for `volatile_store`.
15481563
unsafe {
1549-
assert_unsafe_precondition!([T](dst: *mut T) => is_aligned_and_not_null(dst));
1564+
assert_unsafe_precondition!(
1565+
"ptr::write_volatile requires that the pointer argument is aligned and non-null",
1566+
[T](dst: *mut T) => is_aligned_and_not_null(dst)
1567+
);
15501568
intrinsics::volatile_store(dst, src);
15511569
}
15521570
}

library/core/src/ptr/non_null.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ impl<T: ?Sized> NonNull<T> {
197197
pub const unsafe fn new_unchecked(ptr: *mut T) -> Self {
198198
// SAFETY: the caller must guarantee that `ptr` is non-null.
199199
unsafe {
200-
assert_unsafe_precondition!([T: ?Sized](ptr: *mut T) => !ptr.is_null());
200+
assert_unsafe_precondition!("NonNull::new_unchecked requires that the pointer is non-null", [T: ?Sized](ptr: *mut T) => !ptr.is_null());
201201
NonNull { pointer: ptr as _ }
202202
}
203203
}

library/core/src/slice/index.rs

+26-10
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,10 @@ unsafe impl<T> const SliceIndex<[T]> for usize {
232232
// `self` is in bounds of `slice` so `self` cannot overflow an `isize`,
233233
// so the call to `add` is safe.
234234
unsafe {
235-
assert_unsafe_precondition!([T](this: usize, slice: *const [T]) => this < slice.len());
235+
assert_unsafe_precondition!(
236+
"slice::get_unchecked requires that the index is within the slice",
237+
[T](this: usize, slice: *const [T]) => this < slice.len()
238+
);
236239
slice.as_ptr().add(self)
237240
}
238241
}
@@ -242,7 +245,10 @@ unsafe impl<T> const SliceIndex<[T]> for usize {
242245
let this = self;
243246
// SAFETY: see comments for `get_unchecked` above.
244247
unsafe {
245-
assert_unsafe_precondition!([T](this: usize, slice: *mut [T]) => this < slice.len());
248+
assert_unsafe_precondition!(
249+
"slice::get_unchecked_mut requires that the index is within the slice",
250+
[T](this: usize, slice: *mut [T]) => this < slice.len()
251+
);
246252
slice.as_mut_ptr().add(self)
247253
}
248254
}
@@ -295,8 +301,10 @@ unsafe impl<T> const SliceIndex<[T]> for ops::IndexRange {
295301
// so the call to `add` is safe.
296302

297303
unsafe {
298-
assert_unsafe_precondition!([T](end: usize, slice: *const [T]) =>
299-
end <= slice.len());
304+
assert_unsafe_precondition!(
305+
"slice::get_unchecked requires that the index is within the slice",
306+
[T](end: usize, slice: *const [T]) => end <= slice.len()
307+
);
300308
ptr::slice_from_raw_parts(slice.as_ptr().add(self.start()), self.len())
301309
}
302310
}
@@ -306,8 +314,10 @@ unsafe impl<T> const SliceIndex<[T]> for ops::IndexRange {
306314
let end = self.end();
307315
// SAFETY: see comments for `get_unchecked` above.
308316
unsafe {
309-
assert_unsafe_precondition!([T](end: usize, slice: *mut [T]) =>
310-
end <= slice.len());
317+
assert_unsafe_precondition!(
318+
"slice::get_unchecked_mut requires that the index is within the slice",
319+
[T](end: usize, slice: *mut [T]) => end <= slice.len()
320+
);
311321
ptr::slice_from_raw_parts_mut(slice.as_mut_ptr().add(self.start()), self.len())
312322
}
313323
}
@@ -367,8 +377,11 @@ unsafe impl<T> const SliceIndex<[T]> for ops::Range<usize> {
367377
// so the call to `add` is safe.
368378

369379
unsafe {
370-
assert_unsafe_precondition!([T](this: ops::Range<usize>, slice: *const [T]) =>
371-
this.end >= this.start && this.end <= slice.len());
380+
assert_unsafe_precondition!(
381+
"slice::get_unchecked requires that the range is within the slice",
382+
[T](this: ops::Range<usize>, slice: *const [T]) =>
383+
this.end >= this.start && this.end <= slice.len()
384+
);
372385
ptr::slice_from_raw_parts(slice.as_ptr().add(self.start), self.end - self.start)
373386
}
374387
}
@@ -378,8 +391,11 @@ unsafe impl<T> const SliceIndex<[T]> for ops::Range<usize> {
378391
let this = ops::Range { start: self.start, end: self.end };
379392
// SAFETY: see comments for `get_unchecked` above.
380393
unsafe {
381-
assert_unsafe_precondition!([T](this: ops::Range<usize>, slice: *mut [T]) =>
382-
this.end >= this.start && this.end <= slice.len());
394+
assert_unsafe_precondition!(
395+
"slice::get_unchecked_mut requires that the range is within the slice",
396+
[T](this: ops::Range<usize>, slice: *mut [T]) =>
397+
this.end >= this.start && this.end <= slice.len()
398+
);
383399
ptr::slice_from_raw_parts_mut(slice.as_mut_ptr().add(self.start), self.end - self.start)
384400
}
385401
}

library/core/src/slice/mod.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,10 @@ impl<T> [T] {
653653
let ptr = this.as_mut_ptr();
654654
// SAFETY: caller has to guarantee that `a < self.len()` and `b < self.len()`
655655
unsafe {
656-
assert_unsafe_precondition!([T](a: usize, b: usize, this: &mut [T]) => a < this.len() && b < this.len());
656+
assert_unsafe_precondition!(
657+
"slice::swap_unchecked requires that the indices are within the slice",
658+
[T](a: usize, b: usize, this: &mut [T]) => a < this.len() && b < this.len()
659+
);
657660
ptr::swap(ptr.add(a), ptr.add(b));
658661
}
659662
}
@@ -969,7 +972,10 @@ impl<T> [T] {
969972
let this = self;
970973
// SAFETY: Caller must guarantee that `N` is nonzero and exactly divides the slice length
971974
let new_len = unsafe {
972-
assert_unsafe_precondition!([T](this: &[T], N: usize) => N != 0 && this.len() % N == 0);
975+
assert_unsafe_precondition!(
976+
"slice::as_chunks_unchecked requires `N != 0` and the slice to split exactly into `N`-element chunks",
977+
[T](this: &[T], N: usize) => N != 0 && this.len() % N == 0
978+
);
973979
exact_div(self.len(), N)
974980
};
975981
// SAFETY: We cast a slice of `new_len * N` elements into
@@ -1109,7 +1115,10 @@ impl<T> [T] {
11091115
let this = &*self;
11101116
// SAFETY: Caller must guarantee that `N` is nonzero and exactly divides the slice length
11111117
let new_len = unsafe {
1112-
assert_unsafe_precondition!([T](this: &[T], N: usize) => N != 0 && this.len() % N == 0);
1118+
assert_unsafe_precondition!(
1119+
"slice::as_chunks_unchecked_mut requires `N != 0` and the slice to split exactly into `N`-element chunks",
1120+
[T](this: &[T], N: usize) => N != 0 && this.len() % N == 0
1121+
);
11131122
exact_div(this.len(), N)
11141123
};
11151124
// SAFETY: We cast a slice of `new_len * N` elements into
@@ -1685,7 +1694,10 @@ impl<T> [T] {
16851694
// `[ptr; mid]` and `[mid; len]` are not overlapping, so returning a mutable reference
16861695
// is fine.
16871696
unsafe {
1688-
assert_unsafe_precondition!((mid: usize, len: usize) => mid <= len);
1697+
assert_unsafe_precondition!(
1698+
"slice::split_at_mut_unchecked requires the index to be within the slice",
1699+
(mid: usize, len: usize) => mid <= len
1700+
);
16891701
(from_raw_parts_mut(ptr, mid), from_raw_parts_mut(ptr.add(mid), len - mid))
16901702
}
16911703
}

library/core/src/slice/raw.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,10 @@ use crate::ptr;
9292
pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] {
9393
// SAFETY: the caller must uphold the safety contract for `from_raw_parts`.
9494
unsafe {
95-
assert_unsafe_precondition!([T](data: *const T, len: usize) =>
96-
is_aligned_and_not_null(data) && is_valid_allocation_size::<T>(len)
95+
assert_unsafe_precondition!(
96+
"slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX`",
97+
[T](data: *const T, len: usize) => is_aligned_and_not_null(data)
98+
&& is_valid_allocation_size::<T>(len)
9799
);
98100
&*ptr::slice_from_raw_parts(data, len)
99101
}
@@ -135,8 +137,10 @@ pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T]
135137
pub const unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> &'a mut [T] {
136138
// SAFETY: the caller must uphold the safety contract for `from_raw_parts_mut`.
137139
unsafe {
138-
assert_unsafe_precondition!([T](data: *mut T, len: usize) =>
139-
is_aligned_and_not_null(data) && is_valid_allocation_size::<T>(len)
140+
assert_unsafe_precondition!(
141+
"slice::from_raw_parts_mut requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX`",
142+
[T](data: *mut T, len: usize) => is_aligned_and_not_null(data)
143+
&& is_valid_allocation_size::<T>(len)
140144
);
141145
&mut *ptr::slice_from_raw_parts_mut(data, len)
142146
}

library/test/src/lib.rs

+25
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#![feature(is_terminal)]
2121
#![feature(staged_api)]
2222
#![feature(process_exitcode_internals)]
23+
#![feature(panic_can_unwind)]
2324
#![feature(test)]
2425

2526
// Public reexports
@@ -54,6 +55,7 @@ use std::{
5455
collections::VecDeque,
5556
env, io,
5657
io::prelude::Write,
58+
mem::ManuallyDrop,
5759
panic::{self, catch_unwind, AssertUnwindSafe, PanicInfo},
5860
process::{self, Command, Termination},
5961
sync::mpsc::{channel, Sender},
@@ -112,6 +114,29 @@ pub fn test_main(args: &[String], tests: Vec<TestDescAndFn>, options: Option<Opt
112114
process::exit(ERROR_EXIT_CODE);
113115
}
114116
} else {
117+
if !opts.nocapture {
118+
// If we encounter a non-unwinding panic, flush any captured output from the current test,
119+
// and stop capturing output to ensure that the non-unwinding panic message is visible.
120+
// We also acquire the locks for both output streams to prevent output from other threads
121+
// from interleaving with the panic message or appearing after it.
122+
let builtin_panic_hook = panic::take_hook();
123+
let hook = Box::new({
124+
move |info: &'_ PanicInfo<'_>| {
125+
if !info.can_unwind() {
126+
std::mem::forget(std::io::stderr().lock());
127+
let mut stdout = ManuallyDrop::new(std::io::stdout().lock());
128+
if let Some(captured) = io::set_output_capture(None) {
129+
if let Ok(data) = captured.lock() {
130+
let _ = stdout.write_all(&data);
131+
let _ = stdout.flush();
132+
}
133+
}
134+
}
135+
builtin_panic_hook(info);
136+
}
137+
});
138+
panic::set_hook(hook);
139+
}
115140
match console::run_tests_console(&opts, tests) {
116141
Ok(true) => {}
117142
Ok(false) => process::exit(ERROR_EXIT_CODE),

0 commit comments

Comments
 (0)