Skip to content

Commit e53abfc

Browse files
committed
Auto merge of rust-lang#121662 - saethlin:precondition-unification, r=RalfJung
Distinguish between library and lang UB in assert_unsafe_precondition As described in rust-lang#121583 (comment), `assert_unsafe_precondition` now explicitly distinguishes between language UB (conditions we explicitly optimize on) and library UB (things we document you shouldn't do, and maybe some library internals assume you don't do). `debug_assert_nounwind` was originally added to avoid the "only at runtime" aspect of `assert_unsafe_precondition`. Since then the difference between the macros has gotten muddied. This totally revamps the situation. Now _all_ preconditions shall be checked with `assert_unsafe_precondition`. If you have a precondition that's only checkable at runtime, do a `const_eval_select` hack, as done in this PR. r? RalfJung
2 parents 7e2f02f + c2819a6 commit e53abfc

15 files changed

+279
-202
lines changed

core/src/cell.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,6 @@
237237

238238
use crate::cmp::Ordering;
239239
use crate::fmt::{self, Debug, Display};
240-
use crate::intrinsics;
241240
use crate::marker::{PhantomData, Unsize};
242241
use crate::mem::{self, size_of};
243242
use crate::ops::{CoerceUnsized, Deref, DerefMut, DispatchFromDyn};
@@ -435,8 +434,13 @@ impl<T> Cell<T> {
435434
#[inline]
436435
#[stable(feature = "move_cell", since = "1.17.0")]
437436
pub fn swap(&self, other: &Self) {
437+
// This function documents that it *will* panic, and intrinsics::is_nonoverlapping doesn't
438+
// do the check in const, so trying to use it here would be inviting unnecessary fragility.
438439
fn is_nonoverlapping<T>(src: *const T, dst: *const T) -> bool {
439-
intrinsics::is_nonoverlapping(src.cast(), dst.cast(), size_of::<T>(), 1)
440+
let src_usize = src.addr();
441+
let dst_usize = dst.addr();
442+
let diff = src_usize.abs_diff(dst_usize);
443+
diff >= size_of::<T>()
440444
}
441445

442446
if ptr::eq(self, other) {

core/src/char/convert.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub(super) const unsafe fn from_u32_unchecked(i: u32) -> char {
2626
// SAFETY: the caller must guarantee that `i` is a valid char value.
2727
unsafe {
2828
assert_unsafe_precondition!(
29+
check_language_ub,
2930
"invalid value for `char`",
3031
(i: u32 = i) => char_try_from_u32(i).is_ok()
3132
);

core/src/hint.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,14 @@ use crate::intrinsics;
9898
#[rustc_const_stable(feature = "const_unreachable_unchecked", since = "1.57.0")]
9999
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
100100
pub const unsafe fn unreachable_unchecked() -> ! {
101+
intrinsics::assert_unsafe_precondition!(
102+
check_language_ub,
103+
"hint::unreachable_unchecked must never be reached",
104+
() => false
105+
);
101106
// SAFETY: the safety contract for `intrinsics::unreachable` must
102107
// be upheld by the caller.
103-
unsafe {
104-
intrinsics::assert_unsafe_precondition!("hint::unreachable_unchecked must never be reached", () => false);
105-
intrinsics::unreachable()
106-
}
108+
unsafe { intrinsics::unreachable() }
107109
}
108110

109111
/// Makes a *soundness* promise to the compiler that `cond` holds.
@@ -147,6 +149,7 @@ pub const unsafe fn assert_unchecked(cond: bool) {
147149
// SAFETY: The caller promised `cond` is true.
148150
unsafe {
149151
intrinsics::assert_unsafe_precondition!(
152+
check_language_ub,
150153
"hint::assert_unchecked must never be called when the condition is false",
151154
(cond: bool = cond) => cond,
152155
);

core/src/intrinsics.rs

+123-74
Original file line numberDiff line numberDiff line change
@@ -2628,24 +2628,38 @@ pub const fn is_val_statically_known<T: Copy>(_arg: T) -> bool {
26282628
false
26292629
}
26302630

2631-
/// Returns the value of `cfg!(debug_assertions)`, but after monomorphization instead of in
2632-
/// macro expansion.
2633-
///
2634-
/// This always returns `false` in const eval and Miri. The interpreter provides better
2635-
/// diagnostics than the checks that this is used to implement. However, this means
2636-
/// you should only be using this intrinsic to guard requirements that, if violated,
2637-
/// immediately lead to UB. Otherwise, const-eval and Miri will miss out on those
2638-
/// checks entirely.
2639-
///
2640-
/// Since this is evaluated after monomorphization, branching on this value can be used to
2641-
/// implement debug assertions that are included in the precompiled standard library, but can
2642-
/// be optimized out by builds that monomorphize the standard library code with debug
2631+
/// Returns whether we should check for library UB. This evaluate to the value of `cfg!(debug_assertions)`
2632+
/// during monomorphization.
2633+
///
2634+
/// This intrinsic is evaluated after monomorphization, and therefore branching on this value can
2635+
/// be used to implement debug assertions that are included in the precompiled standard library,
2636+
/// but can be optimized out by builds that monomorphize the standard library code with debug
26432637
/// assertions disabled. This intrinsic is primarily used by [`assert_unsafe_precondition`].
2644-
#[rustc_const_unstable(feature = "delayed_debug_assertions", issue = "none")]
2638+
///
2639+
/// We have separate intrinsics for library UB and language UB because checkers like the const-eval
2640+
/// interpreter and Miri already implement checks for language UB. Since such checkers do not know
2641+
/// about library preconditions, checks guarded by this intrinsic let them find more UB.
2642+
#[rustc_const_unstable(feature = "ub_checks", issue = "none")]
26452643
#[unstable(feature = "core_intrinsics", issue = "none")]
26462644
#[inline(always)]
26472645
#[cfg_attr(not(bootstrap), rustc_intrinsic)]
2648-
pub(crate) const fn debug_assertions() -> bool {
2646+
pub(crate) const fn check_library_ub() -> bool {
2647+
cfg!(debug_assertions)
2648+
}
2649+
2650+
/// Returns whether we should check for language UB. This evaluate to the value of `cfg!(debug_assertions)`
2651+
/// during monomorphization.
2652+
///
2653+
/// Since checks implemented at the source level must come strictly before the operation that
2654+
/// executes UB, if we enabled language UB checks in const-eval/Miri we would miss out on the
2655+
/// interpreter's improved diagnostics for the cases that our source-level checks catch.
2656+
///
2657+
/// See `check_library_ub` for more information.
2658+
#[rustc_const_unstable(feature = "ub_checks", issue = "none")]
2659+
#[unstable(feature = "core_intrinsics", issue = "none")]
2660+
#[inline(always)]
2661+
#[cfg_attr(not(bootstrap), rustc_intrinsic)]
2662+
pub(crate) const fn check_language_ub() -> bool {
26492663
cfg!(debug_assertions)
26502664
}
26512665

@@ -2713,13 +2727,24 @@ pub unsafe fn retag_box_to_raw<T: ?Sized, A>(ptr: *mut T) -> *mut T {
27132727
// (`transmute` also falls into this category, but it cannot be wrapped due to the
27142728
// check that `T` and `U` have the same size.)
27152729

2716-
/// Check that the preconditions of an unsafe function are followed, if debug_assertions are on,
2717-
/// and only at runtime.
2730+
/// Check that the preconditions of an unsafe function are followed. The check is enabled at
2731+
/// runtime if debug assertions are enabled when the caller is monomorphized. In const-eval/Miri
2732+
/// checks implemented with this macro for language UB are always ignored.
27182733
///
27192734
/// This macro should be called as
2720-
/// `assert_unsafe_precondition!((expr => name: Type, expr => name: Type) => Expression)`
2721-
/// where each `expr` will be evaluated and passed in as function argument `name: Type`. Then all
2722-
/// those arguments are passed to a function via [`const_eval_select`].
2735+
/// `assert_unsafe_precondition!(check_{library,lang}_ub, "message", (ident: type = expr, ident: type = expr) => check_expr)`
2736+
/// where each `expr` will be evaluated and passed in as function argument `ident: type`. Then all
2737+
/// those arguments are passed to a function with the body `check_expr`.
2738+
/// Pick `check_language_ub` when this is guarding a violation of language UB, i.e., immediate UB
2739+
/// according to the Rust Abstract Machine. Pick `check_library_ub` when this is guarding a violation
2740+
/// of a documented library precondition that does not *immediately* lead to language UB.
2741+
///
2742+
/// If `check_library_ub` is used but the check is actually guarding language UB, the check will
2743+
/// slow down const-eval/Miri and we'll get the panic message instead of the interpreter's nice
2744+
/// diagnostic, but our ability to detect UB is unchanged.
2745+
/// But if `check_language_ub` is used when the check is actually for library UB, the check is
2746+
/// omitted in const-eval/Miri and thus if we eventually execute language UB which relies on the
2747+
/// library UB, the backtrace Miri reports may be far removed from original cause.
27232748
///
27242749
/// These checks are behind a condition which is evaluated at codegen time, not expansion time like
27252750
/// [`debug_assert`]. This means that a standard library built with optimizations and debug
@@ -2728,31 +2753,25 @@ pub unsafe fn retag_box_to_raw<T: ?Sized, A>(ptr: *mut T) -> *mut T {
27282753
/// this macro, that monomorphization will contain the check.
27292754
///
27302755
/// Since these checks cannot be optimized out in MIR, some care must be taken in both call and
2731-
/// implementation to mitigate their compile-time overhead. The runtime function that we
2732-
/// [`const_eval_select`] to is monomorphic, `#[inline(never)]`, and `#[rustc_nounwind]`. That
2733-
/// combination of properties ensures that the code for the checks is only compiled once, and has a
2734-
/// minimal impact on the caller's code size.
2756+
/// implementation to mitigate their compile-time overhead. Calls to this macro always expand to
2757+
/// this structure:
2758+
/// ```ignore (pseudocode)
2759+
/// if ::core::intrinsics::check_language_ub() {
2760+
/// precondition_check(args)
2761+
/// }
2762+
/// ```
2763+
/// where `precondition_check` is monomorphic with the attributes `#[rustc_nounwind]`, `#[inline]` and
2764+
/// `#[rustc_no_mir_inline]`. This combination of attributes ensures that the actual check logic is
2765+
/// compiled only once and generates a minimal amount of IR because the check cannot be inlined in
2766+
/// MIR, but *can* be inlined and fully optimized by a codegen backend.
27352767
///
2736-
/// Callers should also avoid introducing any other `let` bindings or any code outside this macro in
2768+
/// Callers should avoid introducing any other `let` bindings or any code outside this macro in
27372769
/// order to call it. Since the precompiled standard library is built with full debuginfo and these
27382770
/// variables cannot be optimized out in MIR, an innocent-looking `let` can produce enough
27392771
/// debuginfo to have a measurable compile-time impact on debug builds.
2740-
///
2741-
/// # Safety
2742-
///
2743-
/// Invoking this macro is only sound if the following code is already UB when the passed
2744-
/// expression evaluates to false.
2745-
///
2746-
/// This macro expands to a check at runtime if debug_assertions is set. It has no effect at
2747-
/// compile time, but the semantics of the contained `const_eval_select` must be the same at
2748-
/// runtime and at compile time. Thus if the expression evaluates to false, this macro produces
2749-
/// different behavior at compile time and at runtime, and invoking it is incorrect.
2750-
///
2751-
/// So in a sense it is UB if this macro is useful, but we expect callers of `unsafe fn` to make
2752-
/// the occasional mistake, and this check should help them figure things out.
2753-
#[allow_internal_unstable(const_eval_select, delayed_debug_assertions)] // permit this to be called in stably-const fn
2772+
#[allow_internal_unstable(ub_checks)] // permit this to be called in stably-const fn
27542773
macro_rules! assert_unsafe_precondition {
2755-
($message:expr, ($($name:ident:$ty:ty = $arg:expr),*$(,)?) => $e:expr $(,)?) => {
2774+
($kind:ident, $message:expr, ($($name:ident:$ty:ty = $arg:expr),*$(,)?) => $e:expr $(,)?) => {
27562775
{
27572776
// #[cfg(bootstrap)] (this comment)
27582777
// When the standard library is compiled with debug assertions, we want the check to inline for better performance.
@@ -2774,17 +2793,17 @@ macro_rules! assert_unsafe_precondition {
27742793
#[cfg_attr(not(bootstrap), rustc_no_mir_inline)]
27752794
#[cfg_attr(not(bootstrap), inline)]
27762795
#[rustc_nounwind]
2777-
fn precondition_check($($name:$ty),*) {
2796+
#[rustc_const_unstable(feature = "ub_checks", issue = "none")]
2797+
const fn precondition_check($($name:$ty),*) {
27782798
if !$e {
27792799
::core::panicking::panic_nounwind(
27802800
concat!("unsafe precondition(s) violated: ", $message)
27812801
);
27822802
}
27832803
}
2784-
const fn comptime($(_:$ty),*) {}
27852804

2786-
if ::core::intrinsics::debug_assertions() {
2787-
::core::intrinsics::const_eval_select(($($arg,)*), comptime, precondition_check);
2805+
if ::core::intrinsics::$kind() {
2806+
precondition_check($($arg,)*);
27882807
}
27892808
}
27902809
};
@@ -2793,32 +2812,60 @@ pub(crate) use assert_unsafe_precondition;
27932812

27942813
/// Checks whether `ptr` is properly aligned with respect to
27952814
/// `align_of::<T>()`.
2815+
///
2816+
/// In `const` this is approximate and can fail spuriously. It is primarily intended
2817+
/// for `assert_unsafe_precondition!` with `check_language_ub`, in which case the
2818+
/// check is anyway not executed in `const`.
27962819
#[inline]
2797-
pub(crate) fn is_aligned_and_not_null(ptr: *const (), align: usize) -> bool {
2820+
pub(crate) const fn is_aligned_and_not_null(ptr: *const (), align: usize) -> bool {
27982821
!ptr.is_null() && ptr.is_aligned_to(align)
27992822
}
28002823

28012824
#[inline]
2802-
pub(crate) fn is_valid_allocation_size(size: usize, len: usize) -> bool {
2825+
pub(crate) const fn is_valid_allocation_size(size: usize, len: usize) -> bool {
28032826
let max_len = if size == 0 { usize::MAX } else { isize::MAX as usize / size };
28042827
len <= max_len
28052828
}
28062829

28072830
/// Checks whether the regions of memory starting at `src` and `dst` of size
28082831
/// `count * size` do *not* overlap.
2832+
///
2833+
/// Note that in const-eval this function just returns `true` and therefore must
2834+
/// only be used with `assert_unsafe_precondition!`, similar to `is_aligned_and_not_null`.
28092835
#[inline]
2810-
pub(crate) fn is_nonoverlapping(src: *const (), dst: *const (), size: usize, count: usize) -> bool {
2811-
let src_usize = src.addr();
2812-
let dst_usize = dst.addr();
2813-
let Some(size) = size.checked_mul(count) else {
2814-
crate::panicking::panic_nounwind(
2815-
"is_nonoverlapping: `size_of::<T>() * count` overflows a usize",
2816-
)
2817-
};
2818-
let diff = src_usize.abs_diff(dst_usize);
2819-
// If the absolute distance between the ptrs is at least as big as the size of the buffer,
2820-
// they do not overlap.
2821-
diff >= size
2836+
pub(crate) const fn is_nonoverlapping(
2837+
src: *const (),
2838+
dst: *const (),
2839+
size: usize,
2840+
count: usize,
2841+
) -> bool {
2842+
#[inline]
2843+
fn runtime(src: *const (), dst: *const (), size: usize, count: usize) -> bool {
2844+
let src_usize = src.addr();
2845+
let dst_usize = dst.addr();
2846+
let Some(size) = size.checked_mul(count) else {
2847+
crate::panicking::panic_nounwind(
2848+
"is_nonoverlapping: `size_of::<T>() * count` overflows a usize",
2849+
)
2850+
};
2851+
let diff = src_usize.abs_diff(dst_usize);
2852+
// If the absolute distance between the ptrs is at least as big as the size of the buffer,
2853+
// they do not overlap.
2854+
diff >= size
2855+
}
2856+
2857+
#[inline]
2858+
const fn comptime(_: *const (), _: *const (), _: usize, _: usize) -> bool {
2859+
true
2860+
}
2861+
2862+
#[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block
2863+
// SAFETY: This function's precondition is equivalent to that of `const_eval_select`.
2864+
// Programs which do not execute UB will only see this function return `true`, which makes the
2865+
// const and runtime implementation indistinguishable.
2866+
unsafe {
2867+
const_eval_select((src, dst, size, count), comptime, runtime)
2868+
}
28222869
}
28232870

28242871
/// Copies `count * size_of::<T>()` bytes from `src` to `dst`. The source
@@ -2919,25 +2966,25 @@ pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: us
29192966
pub fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize);
29202967
}
29212968

2969+
assert_unsafe_precondition!(
2970+
check_language_ub,
2971+
"ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \
2972+
and the specified memory ranges do not overlap",
2973+
(
2974+
src: *const () = src as *const (),
2975+
dst: *mut () = dst as *mut (),
2976+
size: usize = size_of::<T>(),
2977+
align: usize = align_of::<T>(),
2978+
count: usize = count,
2979+
) =>
2980+
is_aligned_and_not_null(src, align)
2981+
&& is_aligned_and_not_null(dst, align)
2982+
&& is_nonoverlapping(src, dst, size, count)
2983+
);
2984+
29222985
// SAFETY: the safety contract for `copy_nonoverlapping` must be
29232986
// upheld by the caller.
2924-
unsafe {
2925-
assert_unsafe_precondition!(
2926-
"ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \
2927-
and the specified memory ranges do not overlap",
2928-
(
2929-
src: *const () = src as *const (),
2930-
dst: *mut () = dst as *mut (),
2931-
size: usize = size_of::<T>(),
2932-
align: usize = align_of::<T>(),
2933-
count: usize = count,
2934-
) =>
2935-
is_aligned_and_not_null(src, align)
2936-
&& is_aligned_and_not_null(dst, align)
2937-
&& is_nonoverlapping(src, dst, size, count)
2938-
);
2939-
copy_nonoverlapping(src, dst, count)
2940-
}
2987+
unsafe { copy_nonoverlapping(src, dst, count) }
29412988
}
29422989

29432990
/// Copies `count * size_of::<T>()` bytes from `src` to `dst`. The source
@@ -3024,6 +3071,7 @@ pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
30243071
// SAFETY: the safety contract for `copy` must be upheld by the caller.
30253072
unsafe {
30263073
assert_unsafe_precondition!(
3074+
check_language_ub,
30273075
"ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \
30283076
and the specified memory ranges do not overlap",
30293077
(
@@ -3104,6 +3152,7 @@ pub const unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize) {
31043152
// SAFETY: the safety contract for `write_bytes` must be upheld by the caller.
31053153
unsafe {
31063154
assert_unsafe_precondition!(
3155+
check_language_ub,
31073156
"ptr::write_bytes requires that the destination pointer is aligned and non-null",
31083157
(
31093158
addr: *const () = dst as *const (),

core/src/num/nonzero.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,9 @@ where
328328
// SAFETY: The caller guarantees that `n` is non-zero, so this is unreachable.
329329
unsafe {
330330
intrinsics::assert_unsafe_precondition!(
331-
"NonZero::new_unchecked requires the argument to be non-zero",
332-
() => false,
331+
check_language_ub,
332+
"NonZero::new_unchecked requires the argument to be non-zero",
333+
() => false,
333334
);
334335
intrinsics::unreachable()
335336
}
@@ -367,8 +368,9 @@ where
367368
// SAFETY: The caller guarantees that `n` references a value that is non-zero, so this is unreachable.
368369
unsafe {
369370
intrinsics::assert_unsafe_precondition!(
370-
"NonZero::from_mut_unchecked requires the argument to dereference as non-zero",
371-
() => false,
371+
check_library_ub,
372+
"NonZero::from_mut_unchecked requires the argument to dereference as non-zero",
373+
() => false,
372374
);
373375
intrinsics::unreachable()
374376
}

core/src/ops/index_range.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::intrinsics::{unchecked_add, unchecked_sub};
1+
use crate::intrinsics::{assert_unsafe_precondition, unchecked_add, unchecked_sub};
22
use crate::iter::{FusedIterator, TrustedLen};
33
use crate::num::NonZero;
44

@@ -19,9 +19,10 @@ impl IndexRange {
1919
/// - `start <= end`
2020
#[inline]
2121
pub const unsafe fn new_unchecked(start: usize, end: usize) -> Self {
22-
crate::panic::debug_assert_nounwind!(
23-
start <= end,
24-
"IndexRange::new_unchecked requires `start <= end`"
22+
assert_unsafe_precondition!(
23+
check_library_ub,
24+
"IndexRange::new_unchecked requires `start <= end`",
25+
(start: usize = start, end: usize = end) => start <= end,
2526
);
2627
IndexRange { start, end }
2728
}

0 commit comments

Comments
 (0)