From 45203aac55b61a7bd1b44d20e289998368fea9cd Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 17 Mar 2024 10:12:25 +0100 Subject: [PATCH 1/2] move assert_unsafe_preconditions to its own file These macros and functions are not intrinsics, after all. --- library/core/src/char/convert.rs | 2 +- library/core/src/hint.rs | 5 +- library/core/src/intrinsics.rs | 160 ++-------------------------- library/core/src/lib.rs | 1 + library/core/src/num/nonzero.rs | 5 +- library/core/src/ops/index_range.rs | 5 +- library/core/src/ptr/const_ptr.rs | 2 +- library/core/src/ptr/mod.rs | 33 +++--- library/core/src/ptr/non_null.rs | 2 +- library/core/src/slice/index.rs | 2 +- library/core/src/slice/mod.rs | 2 +- library/core/src/slice/raw.rs | 16 ++- library/core/src/str/traits.rs | 2 +- library/core/src/ub_checks.rs | 145 +++++++++++++++++++++++++ 14 files changed, 194 insertions(+), 188 deletions(-) create mode 100644 library/core/src/ub_checks.rs diff --git a/library/core/src/char/convert.rs b/library/core/src/char/convert.rs index 70b9e89f9ea93..8f61292911030 100644 --- a/library/core/src/char/convert.rs +++ b/library/core/src/char/convert.rs @@ -4,9 +4,9 @@ use crate::char::TryFromCharError; use crate::convert::TryFrom; use crate::error::Error; use crate::fmt; -use crate::intrinsics::assert_unsafe_precondition; use crate::mem::transmute; use crate::str::FromStr; +use crate::ub_checks::assert_unsafe_precondition; /// Converts a `u32` to a `char`. See [`char::from_u32`]. #[must_use] diff --git a/library/core/src/hint.rs b/library/core/src/hint.rs index ffe059bf65cad..b27d0db461917 100644 --- a/library/core/src/hint.rs +++ b/library/core/src/hint.rs @@ -4,6 +4,7 @@ //! Hints may be compile time or runtime. use crate::intrinsics; +use crate::ub_checks; /// Informs the compiler that the site which is calling this function is not /// reachable, possibly enabling further optimizations. @@ -98,7 +99,7 @@ use crate::intrinsics; #[rustc_const_stable(feature = "const_unreachable_unchecked", since = "1.57.0")] #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces pub const unsafe fn unreachable_unchecked() -> ! { - intrinsics::assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "hint::unreachable_unchecked must never be reached", () => false @@ -148,7 +149,7 @@ pub const unsafe fn unreachable_unchecked() -> ! { pub const unsafe fn assert_unchecked(cond: bool) { // SAFETY: The caller promised `cond` is true. unsafe { - intrinsics::assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "hint::assert_unchecked must never be called when the condition is false", (cond: bool = cond) => cond, diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index 86b9a39d68a67..0ed26b6ed4ee4 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -66,6 +66,7 @@ use crate::marker::DiscriminantKind; use crate::marker::Tuple; use crate::mem::align_of; +use crate::ub_checks; pub mod mir; pub mod simd; @@ -2727,147 +2728,6 @@ pub unsafe fn retag_box_to_raw(ptr: *mut T) -> *mut T { // (`transmute` also falls into this category, but it cannot be wrapped due to the // check that `T` and `U` have the same size.) -/// Check that the preconditions of an unsafe function are followed. The check is enabled at -/// runtime if debug assertions are enabled when the caller is monomorphized. In const-eval/Miri -/// checks implemented with this macro for language UB are always ignored. -/// -/// This macro should be called as -/// `assert_unsafe_precondition!(check_{library,lang}_ub, "message", (ident: type = expr, ident: type = expr) => check_expr)` -/// where each `expr` will be evaluated and passed in as function argument `ident: type`. Then all -/// those arguments are passed to a function with the body `check_expr`. -/// Pick `check_language_ub` when this is guarding a violation of language UB, i.e., immediate UB -/// according to the Rust Abstract Machine. Pick `check_library_ub` when this is guarding a violation -/// of a documented library precondition that does not *immediately* lead to language UB. -/// -/// If `check_library_ub` is used but the check is actually guarding language UB, the check will -/// slow down const-eval/Miri and we'll get the panic message instead of the interpreter's nice -/// diagnostic, but our ability to detect UB is unchanged. -/// But if `check_language_ub` is used when the check is actually for library UB, the check is -/// omitted in const-eval/Miri and thus if we eventually execute language UB which relies on the -/// library UB, the backtrace Miri reports may be far removed from original cause. -/// -/// These checks are behind a condition which is evaluated at codegen time, not expansion time like -/// [`debug_assert`]. This means that a standard library built with optimizations and debug -/// assertions disabled will have these checks optimized out of its monomorphizations, but if a -/// caller of the standard library has debug assertions enabled and monomorphizes an expansion of -/// this macro, that monomorphization will contain the check. -/// -/// Since these checks cannot be optimized out in MIR, some care must be taken in both call and -/// implementation to mitigate their compile-time overhead. Calls to this macro always expand to -/// this structure: -/// ```ignore (pseudocode) -/// if ::core::intrinsics::check_language_ub() { -/// precondition_check(args) -/// } -/// ``` -/// where `precondition_check` is monomorphic with the attributes `#[rustc_nounwind]`, `#[inline]` and -/// `#[rustc_no_mir_inline]`. This combination of attributes ensures that the actual check logic is -/// compiled only once and generates a minimal amount of IR because the check cannot be inlined in -/// MIR, but *can* be inlined and fully optimized by a codegen backend. -/// -/// Callers should avoid introducing any other `let` bindings or any code outside this macro in -/// order to call it. Since the precompiled standard library is built with full debuginfo and these -/// variables cannot be optimized out in MIR, an innocent-looking `let` can produce enough -/// debuginfo to have a measurable compile-time impact on debug builds. -#[allow_internal_unstable(ub_checks)] // permit this to be called in stably-const fn -macro_rules! assert_unsafe_precondition { - ($kind:ident, $message:expr, ($($name:ident:$ty:ty = $arg:expr),*$(,)?) => $e:expr $(,)?) => { - { - // #[cfg(bootstrap)] (this comment) - // When the standard library is compiled with debug assertions, we want the check to inline for better performance. - // This is important when working on the compiler, which is compiled with debug assertions locally. - // When not compiled with debug assertions (so the precompiled std) we outline the check to minimize the compile - // time impact when debug assertions are disabled. - // The proper solution to this is the `#[rustc_no_mir_inline]` below, but we still want decent performance for cfg(bootstrap). - #[cfg_attr(all(debug_assertions, bootstrap), inline(always))] - #[cfg_attr(all(not(debug_assertions), bootstrap), inline(never))] - - // This check is inlineable, but not by the MIR inliner. - // The reason for this is that the MIR inliner is in an exceptionally bad position - // to think about whether or not to inline this. In MIR, this call is gated behind `debug_assertions`, - // which will codegen to `false` in release builds. Inlining the check would be wasted work in that case and - // would be bad for compile times. - // - // LLVM on the other hand sees the constant branch, so if it's `false`, it can immediately delete it without - // inlining the check. If it's `true`, it can inline it and get significantly better performance. - #[cfg_attr(not(bootstrap), rustc_no_mir_inline)] - #[cfg_attr(not(bootstrap), inline)] - #[rustc_nounwind] - #[rustc_const_unstable(feature = "ub_checks", issue = "none")] - const fn precondition_check($($name:$ty),*) { - if !$e { - ::core::panicking::panic_nounwind( - concat!("unsafe precondition(s) violated: ", $message) - ); - } - } - - if ::core::intrinsics::$kind() { - precondition_check($($arg,)*); - } - } - }; -} -pub(crate) use assert_unsafe_precondition; - -/// Checks whether `ptr` is properly aligned with respect to -/// `align_of::()`. -/// -/// In `const` this is approximate and can fail spuriously. It is primarily intended -/// for `assert_unsafe_precondition!` with `check_language_ub`, in which case the -/// check is anyway not executed in `const`. -#[inline] -pub(crate) const fn is_aligned_and_not_null(ptr: *const (), align: usize) -> bool { - !ptr.is_null() && ptr.is_aligned_to(align) -} - -#[inline] -pub(crate) const fn is_valid_allocation_size(size: usize, len: usize) -> bool { - let max_len = if size == 0 { usize::MAX } else { isize::MAX as usize / size }; - len <= max_len -} - -/// Checks whether the regions of memory starting at `src` and `dst` of size -/// `count * size` do *not* overlap. -/// -/// Note that in const-eval this function just returns `true` and therefore must -/// only be used with `assert_unsafe_precondition!`, similar to `is_aligned_and_not_null`. -#[inline] -pub(crate) const fn is_nonoverlapping( - src: *const (), - dst: *const (), - size: usize, - count: usize, -) -> bool { - #[inline] - fn runtime(src: *const (), dst: *const (), size: usize, count: usize) -> bool { - let src_usize = src.addr(); - let dst_usize = dst.addr(); - let Some(size) = size.checked_mul(count) else { - crate::panicking::panic_nounwind( - "is_nonoverlapping: `size_of::() * count` overflows a usize", - ) - }; - let diff = src_usize.abs_diff(dst_usize); - // If the absolute distance between the ptrs is at least as big as the size of the buffer, - // they do not overlap. - diff >= size - } - - #[inline] - const fn comptime(_: *const (), _: *const (), _: usize, _: usize) -> bool { - true - } - - #[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block - // SAFETY: This function's precondition is equivalent to that of `const_eval_select`. - // Programs which do not execute UB will only see this function return `true`, which makes the - // const and runtime implementation indistinguishable. - unsafe { - const_eval_select((src, dst, size, count), comptime, runtime) - } -} - /// Copies `count * size_of::()` bytes from `src` to `dst`. The source /// and destination must *not* overlap. /// @@ -2966,7 +2826,7 @@ pub const unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: us pub fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize); } - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \ and the specified memory ranges do not overlap", @@ -2977,9 +2837,9 @@ pub const unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: us align: usize = align_of::(), count: usize = count, ) => - is_aligned_and_not_null(src, align) - && is_aligned_and_not_null(dst, align) - && is_nonoverlapping(src, dst, size, count) + ub_checks::is_aligned_and_not_null(src, align) + && ub_checks::is_aligned_and_not_null(dst, align) + && ub_checks::is_nonoverlapping(src, dst, size, count) ); // SAFETY: the safety contract for `copy_nonoverlapping` must be @@ -3070,7 +2930,7 @@ pub const unsafe fn copy(src: *const T, dst: *mut T, count: usize) { // SAFETY: the safety contract for `copy` must be upheld by the caller. unsafe { - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \ and the specified memory ranges do not overlap", @@ -3079,8 +2939,8 @@ pub const unsafe fn copy(src: *const T, dst: *mut T, count: usize) { dst: *mut () = dst as *mut (), align: usize = align_of::(), ) => - is_aligned_and_not_null(src, align) - && is_aligned_and_not_null(dst, align) + ub_checks::is_aligned_and_not_null(src, align) + && ub_checks::is_aligned_and_not_null(dst, align) ); copy(src, dst, count) } @@ -3151,13 +3011,13 @@ pub const unsafe fn write_bytes(dst: *mut T, val: u8, count: usize) { // SAFETY: the safety contract for `write_bytes` must be upheld by the caller. unsafe { - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "ptr::write_bytes requires that the destination pointer is aligned and non-null", ( addr: *const () = dst as *const (), align: usize = align_of::(), - ) => is_aligned_and_not_null(addr, align) + ) => ub_checks::is_aligned_and_not_null(addr, align) ); write_bytes(dst, val, count) } diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 9b786feba8988..626e85fc0484b 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -368,6 +368,7 @@ pub mod hint; pub mod intrinsics; pub mod mem; pub mod ptr; +mod ub_checks; /* Core language traits */ diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index 1f7a4e276f553..12e22ad14a361 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -9,6 +9,7 @@ use crate::ops::{BitOr, BitOrAssign, Div, Neg, Rem}; use crate::panic::{RefUnwindSafe, UnwindSafe}; use crate::ptr; use crate::str::FromStr; +use crate::ub_checks; use super::from_str_radix; use super::{IntErrorKind, ParseIntError}; @@ -369,7 +370,7 @@ where None => { // SAFETY: The caller guarantees that `n` is non-zero, so this is unreachable. unsafe { - intrinsics::assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "NonZero::new_unchecked requires the argument to be non-zero", () => false, @@ -409,7 +410,7 @@ where None => { // SAFETY: The caller guarantees that `n` references a value that is non-zero, so this is unreachable. unsafe { - intrinsics::assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_library_ub, "NonZero::from_mut_unchecked requires the argument to dereference as non-zero", () => false, diff --git a/library/core/src/ops/index_range.rs b/library/core/src/ops/index_range.rs index b28d88fa5bfc2..65bda9177c7be 100644 --- a/library/core/src/ops/index_range.rs +++ b/library/core/src/ops/index_range.rs @@ -1,6 +1,7 @@ -use crate::intrinsics::{assert_unsafe_precondition, unchecked_add, unchecked_sub}; +use crate::intrinsics::{unchecked_add, unchecked_sub}; use crate::iter::{FusedIterator, TrustedLen}; use crate::num::NonZero; +use crate::ub_checks; /// Like a `Range`, but with a safety invariant that `start <= end`. /// @@ -19,7 +20,7 @@ impl IndexRange { /// - `start <= end` #[inline] pub const unsafe fn new_unchecked(start: usize, end: usize) -> Self { - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_library_ub, "IndexRange::new_unchecked requires `start <= end`", (start: usize = start, end: usize = end) => start <= end, diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index a0c04d3f65dfd..02fc920a0fac1 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -827,7 +827,7 @@ impl *const T { } } - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "ptr::sub_ptr requires `self >= origin`", ( diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 1f0204daf7238..55f0c7db82db4 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -388,10 +388,9 @@ use crate::cmp::Ordering; use crate::fmt; use crate::hash; -use crate::intrinsics::{ - self, assert_unsafe_precondition, is_aligned_and_not_null, is_nonoverlapping, -}; +use crate::intrinsics; use crate::marker::FnPtr; +use crate::ub_checks; use crate::mem::{self, align_of, size_of, MaybeUninit}; @@ -1019,7 +1018,7 @@ pub const unsafe fn swap_nonoverlapping(x: *mut T, y: *mut T, count: usize) { }; } - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "ptr::swap_nonoverlapping requires that both pointer arguments are aligned and non-null \ and the specified memory ranges do not overlap", @@ -1030,9 +1029,9 @@ pub const unsafe fn swap_nonoverlapping(x: *mut T, y: *mut T, count: usize) { align: usize = align_of::(), count: usize = count, ) => - is_aligned_and_not_null(x, align) - && is_aligned_and_not_null(y, align) - && is_nonoverlapping(x, y, size, count) + ub_checks::is_aligned_and_not_null(x, align) + && ub_checks::is_aligned_and_not_null(y, align) + && ub_checks::is_nonoverlapping(x, y, size, count) ); // Split up the slice into small power-of-two-sized chunks that LLVM is able @@ -1120,13 +1119,13 @@ pub const unsafe fn replace(dst: *mut T, src: T) -> T { // and cannot overlap `src` since `dst` must point to a distinct // allocated object. unsafe { - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "ptr::replace requires that the pointer argument is aligned and non-null", ( addr: *const () = dst as *const (), align: usize = align_of::(), - ) => is_aligned_and_not_null(addr, align) + ) => ub_checks::is_aligned_and_not_null(addr, align) ); mem::replace(&mut *dst, src) } @@ -1272,13 +1271,13 @@ pub const unsafe fn read(src: *const T) -> T { // SAFETY: the caller must guarantee that `src` is valid for reads. unsafe { #[cfg(debug_assertions)] // Too expensive to always enable (for now?) - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "ptr::read requires that the pointer argument is aligned and non-null", ( addr: *const () = src as *const (), align: usize = align_of::(), - ) => is_aligned_and_not_null(addr, align) + ) => ub_checks::is_aligned_and_not_null(addr, align) ); crate::intrinsics::read_via_copy(src) } @@ -1481,13 +1480,13 @@ pub const unsafe fn write(dst: *mut T, src: T) { // to `dst` while `src` is owned by this function. unsafe { #[cfg(debug_assertions)] // Too expensive to always enable (for now?) - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "ptr::write requires that the pointer argument is aligned and non-null", ( addr: *mut () = dst as *mut (), align: usize = align_of::(), - ) => is_aligned_and_not_null(addr, align) + ) => ub_checks::is_aligned_and_not_null(addr, align) ); intrinsics::write_via_move(dst, src) } @@ -1653,13 +1652,13 @@ pub const unsafe fn write_unaligned(dst: *mut T, src: T) { pub unsafe fn read_volatile(src: *const T) -> T { // SAFETY: the caller must uphold the safety contract for `volatile_load`. unsafe { - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "ptr::read_volatile requires that the pointer argument is aligned and non-null", ( addr: *const () = src as *const (), align: usize = align_of::(), - ) => is_aligned_and_not_null(addr, align) + ) => ub_checks::is_aligned_and_not_null(addr, align) ); intrinsics::volatile_load(src) } @@ -1732,13 +1731,13 @@ pub unsafe fn read_volatile(src: *const T) -> T { pub unsafe fn write_volatile(dst: *mut T, src: T) { // SAFETY: the caller must uphold the safety contract for `volatile_store`. unsafe { - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "ptr::write_volatile requires that the pointer argument is aligned and non-null", ( addr: *mut () = dst as *mut (), align: usize = align_of::(), - ) => is_aligned_and_not_null(addr, align) + ) => ub_checks::is_aligned_and_not_null(addr, align) ); intrinsics::volatile_store(dst, src); } diff --git a/library/core/src/ptr/non_null.rs b/library/core/src/ptr/non_null.rs index 9c0236c172a93..7097b6fbf2f85 100644 --- a/library/core/src/ptr/non_null.rs +++ b/library/core/src/ptr/non_null.rs @@ -2,7 +2,6 @@ use crate::cmp::Ordering; use crate::fmt; use crate::hash; use crate::intrinsics; -use crate::intrinsics::assert_unsafe_precondition; use crate::marker::Unsize; use crate::mem::{MaybeUninit, SizedTypeProperties}; use crate::num::NonZero; @@ -10,6 +9,7 @@ use crate::ops::{CoerceUnsized, DispatchFromDyn}; use crate::ptr; use crate::ptr::Unique; use crate::slice::{self, SliceIndex}; +use crate::ub_checks::assert_unsafe_precondition; /// `*mut T` but non-zero and [covariant]. /// diff --git a/library/core/src/slice/index.rs b/library/core/src/slice/index.rs index 64f1f360821da..18f63e4a461eb 100644 --- a/library/core/src/slice/index.rs +++ b/library/core/src/slice/index.rs @@ -1,10 +1,10 @@ //! Indexing implementations for `[T]`. -use crate::intrinsics::assert_unsafe_precondition; use crate::intrinsics::const_eval_select; use crate::intrinsics::unchecked_sub; use crate::ops; use crate::ptr; +use crate::ub_checks::assert_unsafe_precondition; #[stable(feature = "rust1", since = "1.0.0")] impl ops::Index for [T] diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 4a574bf034745..92080c5020ad8 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -9,7 +9,6 @@ use crate::cmp::Ordering::{self, Equal, Greater, Less}; use crate::fmt; use crate::hint; -use crate::intrinsics::assert_unsafe_precondition; use crate::intrinsics::exact_div; use crate::mem::{self, SizedTypeProperties}; use crate::num::NonZero; @@ -17,6 +16,7 @@ use crate::ops::{Bound, OneSidedRange, Range, RangeBounds}; use crate::ptr; use crate::simd::{self, Simd}; use crate::slice; +use crate::ub_checks::assert_unsafe_precondition; #[unstable( feature = "slice_internals", diff --git a/library/core/src/slice/raw.rs b/library/core/src/slice/raw.rs index 2199614ce27e4..29a12f106c5ed 100644 --- a/library/core/src/slice/raw.rs +++ b/library/core/src/slice/raw.rs @@ -1,12 +1,10 @@ //! Free functions to create `&[T]` and `&mut [T]`. use crate::array; -use crate::intrinsics::{ - assert_unsafe_precondition, is_aligned_and_not_null, is_valid_allocation_size, -}; use crate::mem::{align_of, size_of}; use crate::ops::Range; use crate::ptr; +use crate::ub_checks; /// Forms a slice from a pointer and a length. /// @@ -95,7 +93,7 @@ use crate::ptr; pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] { // SAFETY: the caller must uphold the safety contract for `from_raw_parts`. unsafe { - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "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`", ( @@ -104,8 +102,8 @@ pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] align: usize = align_of::(), len: usize = len, ) => - is_aligned_and_not_null(data, align) - && is_valid_allocation_size(size, len) + ub_checks::is_aligned_and_not_null(data, align) + && ub_checks::is_valid_allocation_size(size, len) ); &*ptr::slice_from_raw_parts(data, len) } @@ -149,7 +147,7 @@ pub const unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] pub const unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> &'a mut [T] { // SAFETY: the caller must uphold the safety contract for `from_raw_parts_mut`. unsafe { - assert_unsafe_precondition!( + ub_checks::assert_unsafe_precondition!( check_language_ub, "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`", ( @@ -158,8 +156,8 @@ pub const unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> &'a m align: usize = align_of::(), len: usize = len, ) => - is_aligned_and_not_null(data, align) - && is_valid_allocation_size(size, len) + ub_checks::is_aligned_and_not_null(data, align) + && ub_checks::is_valid_allocation_size(size, len) ); &mut *ptr::slice_from_raw_parts_mut(data, len) } diff --git a/library/core/src/str/traits.rs b/library/core/src/str/traits.rs index ec81fd095d530..672af75214936 100644 --- a/library/core/src/str/traits.rs +++ b/library/core/src/str/traits.rs @@ -1,10 +1,10 @@ //! Trait implementations for `str`. use crate::cmp::Ordering; -use crate::intrinsics::assert_unsafe_precondition; use crate::ops; use crate::ptr; use crate::slice::SliceIndex; +use crate::ub_checks::assert_unsafe_precondition; use super::ParseBoolError; diff --git a/library/core/src/ub_checks.rs b/library/core/src/ub_checks.rs new file mode 100644 index 0000000000000..4ef56392fad33 --- /dev/null +++ b/library/core/src/ub_checks.rs @@ -0,0 +1,145 @@ +//! Provides the [`assert_unsafe_precondition`] macro as well as some utility functions that cover +//! common preconditions. + +use crate::intrinsics::const_eval_select; + +/// Check that the preconditions of an unsafe function are followed. The check is enabled at +/// runtime if debug assertions are enabled when the caller is monomorphized. In const-eval/Miri +/// checks implemented with this macro for language UB are always ignored. +/// +/// This macro should be called as +/// `assert_unsafe_precondition!(check_{library,lang}_ub, "message", (ident: type = expr, ident: type = expr) => check_expr)` +/// where each `expr` will be evaluated and passed in as function argument `ident: type`. Then all +/// those arguments are passed to a function with the body `check_expr`. +/// Pick `check_language_ub` when this is guarding a violation of language UB, i.e., immediate UB +/// according to the Rust Abstract Machine. Pick `check_library_ub` when this is guarding a violation +/// of a documented library precondition that does not *immediately* lead to language UB. +/// +/// If `check_library_ub` is used but the check is actually guarding language UB, the check will +/// slow down const-eval/Miri and we'll get the panic message instead of the interpreter's nice +/// diagnostic, but our ability to detect UB is unchanged. +/// But if `check_language_ub` is used when the check is actually for library UB, the check is +/// omitted in const-eval/Miri and thus if we eventually execute language UB which relies on the +/// library UB, the backtrace Miri reports may be far removed from original cause. +/// +/// These checks are behind a condition which is evaluated at codegen time, not expansion time like +/// [`debug_assert`]. This means that a standard library built with optimizations and debug +/// assertions disabled will have these checks optimized out of its monomorphizations, but if a +/// caller of the standard library has debug assertions enabled and monomorphizes an expansion of +/// this macro, that monomorphization will contain the check. +/// +/// Since these checks cannot be optimized out in MIR, some care must be taken in both call and +/// implementation to mitigate their compile-time overhead. Calls to this macro always expand to +/// this structure: +/// ```ignore (pseudocode) +/// if ::core::intrinsics::check_language_ub() { +/// precondition_check(args) +/// } +/// ``` +/// where `precondition_check` is monomorphic with the attributes `#[rustc_nounwind]`, `#[inline]` and +/// `#[rustc_no_mir_inline]`. This combination of attributes ensures that the actual check logic is +/// compiled only once and generates a minimal amount of IR because the check cannot be inlined in +/// MIR, but *can* be inlined and fully optimized by a codegen backend. +/// +/// Callers should avoid introducing any other `let` bindings or any code outside this macro in +/// order to call it. Since the precompiled standard library is built with full debuginfo and these +/// variables cannot be optimized out in MIR, an innocent-looking `let` can produce enough +/// debuginfo to have a measurable compile-time impact on debug builds. +#[allow_internal_unstable(ub_checks)] // permit this to be called in stably-const fn +macro_rules! assert_unsafe_precondition { + ($kind:ident, $message:expr, ($($name:ident:$ty:ty = $arg:expr),*$(,)?) => $e:expr $(,)?) => { + { + // #[cfg(bootstrap)] (this comment) + // When the standard library is compiled with debug assertions, we want the check to inline for better performance. + // This is important when working on the compiler, which is compiled with debug assertions locally. + // When not compiled with debug assertions (so the precompiled std) we outline the check to minimize the compile + // time impact when debug assertions are disabled. + // The proper solution to this is the `#[rustc_no_mir_inline]` below, but we still want decent performance for cfg(bootstrap). + #[cfg_attr(all(debug_assertions, bootstrap), inline(always))] + #[cfg_attr(all(not(debug_assertions), bootstrap), inline(never))] + + // This check is inlineable, but not by the MIR inliner. + // The reason for this is that the MIR inliner is in an exceptionally bad position + // to think about whether or not to inline this. In MIR, this call is gated behind `debug_assertions`, + // which will codegen to `false` in release builds. Inlining the check would be wasted work in that case and + // would be bad for compile times. + // + // LLVM on the other hand sees the constant branch, so if it's `false`, it can immediately delete it without + // inlining the check. If it's `true`, it can inline it and get significantly better performance. + #[cfg_attr(not(bootstrap), rustc_no_mir_inline)] + #[cfg_attr(not(bootstrap), inline)] + #[rustc_nounwind] + #[rustc_const_unstable(feature = "ub_checks", issue = "none")] + const fn precondition_check($($name:$ty),*) { + if !$e { + ::core::panicking::panic_nounwind( + concat!("unsafe precondition(s) violated: ", $message) + ); + } + } + + if ::core::intrinsics::$kind() { + precondition_check($($arg,)*); + } + } + }; +} +pub(crate) use assert_unsafe_precondition; + +/// Checks whether `ptr` is properly aligned with respect to +/// `align_of::()`. +/// +/// In `const` this is approximate and can fail spuriously. It is primarily intended +/// for `assert_unsafe_precondition!` with `check_language_ub`, in which case the +/// check is anyway not executed in `const`. +#[inline] +pub(crate) const fn is_aligned_and_not_null(ptr: *const (), align: usize) -> bool { + !ptr.is_null() && ptr.is_aligned_to(align) +} + +#[inline] +pub(crate) const fn is_valid_allocation_size(size: usize, len: usize) -> bool { + let max_len = if size == 0 { usize::MAX } else { isize::MAX as usize / size }; + len <= max_len +} + +/// Checks whether the regions of memory starting at `src` and `dst` of size +/// `count * size` do *not* overlap. +/// +/// Note that in const-eval this function just returns `true` and therefore must +/// only be used with `assert_unsafe_precondition!`, similar to `is_aligned_and_not_null`. +#[inline] +pub(crate) const fn is_nonoverlapping( + src: *const (), + dst: *const (), + size: usize, + count: usize, +) -> bool { + #[inline] + fn runtime(src: *const (), dst: *const (), size: usize, count: usize) -> bool { + let src_usize = src.addr(); + let dst_usize = dst.addr(); + let Some(size) = size.checked_mul(count) else { + crate::panicking::panic_nounwind( + "is_nonoverlapping: `size_of::() * count` overflows a usize", + ) + }; + let diff = src_usize.abs_diff(dst_usize); + // If the absolute distance between the ptrs is at least as big as the size of the buffer, + // they do not overlap. + diff >= size + } + + #[inline] + const fn comptime(_: *const (), _: *const (), _: usize, _: usize) -> bool { + true + } + + #[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block + // SAFETY: This function's precondition is equivalent to that of `const_eval_select`. + // Programs which do not execute UB will only see this function return `true`, which makes the + // const and runtime implementation indistinguishable. + unsafe { + const_eval_select((src, dst, size, count), comptime, runtime) + } +} From b602b20b392ed3b5b93059bf8abd2893325da5c1 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 17 Mar 2024 10:29:02 +0100 Subject: [PATCH 2/2] refactor check_{lang,library}_ub: use a single intrinsics, put policy into library --- compiler/rustc_borrowck/src/type_check/mod.rs | 2 +- compiler/rustc_codegen_ssa/src/mir/rvalue.rs | 2 +- .../rustc_const_eval/src/interpret/step.rs | 12 +----- .../src/transform/check_consts/check.rs | 2 +- .../src/transform/validate.rs | 2 +- .../rustc_hir_analysis/src/check/intrinsic.rs | 5 +-- compiler/rustc_middle/src/mir/mod.rs | 2 +- compiler/rustc_middle/src/mir/pretty.rs | 2 +- compiler/rustc_middle/src/mir/syntax.rs | 13 ++----- compiler/rustc_middle/src/mir/tcx.rs | 2 +- .../src/move_paths/builder.rs | 2 +- compiler/rustc_mir_transform/src/gvn.rs | 2 +- .../src/known_panics_lint.rs | 2 +- .../src/lower_intrinsics.rs | 21 +--------- .../rustc_mir_transform/src/promote_consts.rs | 2 +- .../rustc_smir/src/rustc_smir/convert/mir.rs | 8 +--- compiler/rustc_span/src/symbol.rs | 3 +- compiler/stable_mir/src/mir/body.rs | 10 +---- library/core/src/intrinsics.rs | 28 ++------------ library/core/src/lib.rs | 1 + library/core/src/ub_checks.rs | 38 +++++++++++++++++-- 21 files changed, 62 insertions(+), 99 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 412d50f493eee..7832ce08baf60 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -2000,7 +2000,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ConstraintCategory::SizedBound, ); } - &Rvalue::NullaryOp(NullOp::UbCheck(_), _) => {} + &Rvalue::NullaryOp(NullOp::UbChecks, _) => {} Rvalue::ShallowInitBox(operand, ty) => { self.check_operand(operand, location); diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 8159f76b421b3..14caa59a85399 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -685,7 +685,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let val = layout.offset_of_subfield(bx.cx(), fields.iter()).bytes(); bx.cx().const_usize(val) } - mir::NullOp::UbCheck(_) => { + mir::NullOp::UbChecks => { // In codegen, we want to check for language UB and library UB let val = bx.tcx().sess.opts.debug_assertions; bx.cx().const_bool(val) diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 54bac70da3881..9114ffff6fde9 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -258,17 +258,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let val = layout.offset_of_subfield(self, fields.iter()).bytes(); Scalar::from_target_usize(val, self) } - mir::NullOp::UbCheck(kind) => { - // We want to enable checks for library UB, because the interpreter doesn't - // know about those on its own. - // But we want to disable checks for language UB, because the interpreter - // has its own better checks for that. - let should_check = match kind { - mir::UbKind::LibraryUb => self.tcx.sess.opts.debug_assertions, - mir::UbKind::LanguageUb => false, - }; - Scalar::from_bool(should_check) - } + mir::NullOp::UbChecks => Scalar::from_bool(self.tcx.sess.opts.debug_assertions), }; self.write_scalar(val, &dest)?; } diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs index a93e8138aa412..da8e28d02982e 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs @@ -558,7 +558,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { Rvalue::Cast(_, _, _) => {} Rvalue::NullaryOp( - NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbCheck(_), + NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks, _, ) => {} Rvalue::ShallowInitBox(_, _) => {} diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs index f26c8f8592dea..7d7197e3ca4e5 100644 --- a/compiler/rustc_const_eval/src/transform/validate.rs +++ b/compiler/rustc_const_eval/src/transform/validate.rs @@ -1157,7 +1157,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { Rvalue::Repeat(_, _) | Rvalue::ThreadLocalRef(_) | Rvalue::AddressOf(_, _) - | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::UbCheck(_), _) + | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::UbChecks, _) | Rvalue::Discriminant(_) => {} } self.super_rvalue(rvalue, location); diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index bf5838143d945..b37e72944ae96 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -127,8 +127,7 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) - | sym::variant_count | sym::is_val_statically_known | sym::ptr_mask - | sym::check_language_ub - | sym::check_library_ub + | sym::ub_checks | sym::fadd_algebraic | sym::fsub_algebraic | sym::fmul_algebraic @@ -585,7 +584,7 @@ pub fn check_intrinsic_type( (0, 0, vec![Ty::new_imm_ptr(tcx, Ty::new_unit(tcx))], tcx.types.usize) } - sym::check_language_ub | sym::check_library_ub => (0, 1, Vec::new(), tcx.types.bool), + sym::ub_checks => (0, 1, Vec::new(), tcx.types.bool), sym::simd_eq | sym::simd_ne diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index d57ffc0f8b5c5..ea8a3412f16cb 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -762,7 +762,7 @@ impl<'tcx> Body<'tcx> { } match rvalue { - Rvalue::NullaryOp(NullOp::UbCheck(_), _) => { + Rvalue::NullaryOp(NullOp::UbChecks, _) => { Some((tcx.sess.opts.debug_assertions as u128, targets)) } Rvalue::Use(Operand::Constant(constant)) => { diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 94751c4476157..fbee4a9366fb3 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -944,7 +944,7 @@ impl<'tcx> Debug for Rvalue<'tcx> { NullOp::SizeOf => write!(fmt, "SizeOf({t})"), NullOp::AlignOf => write!(fmt, "AlignOf({t})"), NullOp::OffsetOf(fields) => write!(fmt, "OffsetOf({t}, {fields:?})"), - NullOp::UbCheck(kind) => write!(fmt, "UbCheck({kind:?})"), + NullOp::UbChecks => write!(fmt, "UbChecks()"), } } ThreadLocalRef(did) => ty::tls::with(|tcx| { diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 0ab797134c0af..3341b64eee094 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -1366,16 +1366,9 @@ pub enum NullOp<'tcx> { AlignOf, /// Returns the offset of a field OffsetOf(&'tcx List<(VariantIdx, FieldIdx)>), - /// Returns whether we want to check for library UB or language UB at monomorphization time. - /// Both kinds of UB evaluate to `true` in codegen, and only library UB evalutes to `true` in - /// const-eval/Miri, because the interpreter has its own better checks for language UB. - UbCheck(UbKind), -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)] -pub enum UbKind { - LanguageUb, - LibraryUb, + /// Returns whether we want to check for UB at monomorphization time. + /// This returns the value of `cfg!(debug_assertions)`. + UbChecks, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/compiler/rustc_middle/src/mir/tcx.rs b/compiler/rustc_middle/src/mir/tcx.rs index 0c29fe57d4fc9..f14715304c223 100644 --- a/compiler/rustc_middle/src/mir/tcx.rs +++ b/compiler/rustc_middle/src/mir/tcx.rs @@ -194,7 +194,7 @@ impl<'tcx> Rvalue<'tcx> { Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => { tcx.types.usize } - Rvalue::NullaryOp(NullOp::UbCheck(_), _) => tcx.types.bool, + Rvalue::NullaryOp(NullOp::UbChecks, _) => tcx.types.bool, Rvalue::Aggregate(ref ak, ref ops) => match **ak { AggregateKind::Array(ty) => Ty::new_array(tcx, ty, ops.len() as u64), AggregateKind::Tuple => { diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index 0f900e6a5573a..bf21e37bdd119 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -433,7 +433,7 @@ impl<'b, 'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> Gatherer<'b, 'a, 'tcx, F> { | Rvalue::Discriminant(..) | Rvalue::Len(..) | Rvalue::NullaryOp( - NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::UbCheck(_), + NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..) | NullOp::UbChecks, _, ) => {} } diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index a3a2108787a73..c1bb7ae8d537f 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -488,7 +488,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { NullOp::OffsetOf(fields) => { layout.offset_of_subfield(&self.ecx, fields.iter()).bytes() } - NullOp::UbCheck(_) => return None, + NullOp::UbChecks => return None, }; let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap(); let imm = ImmTy::try_from_uint(val, usize_layout)?; diff --git a/compiler/rustc_mir_transform/src/known_panics_lint.rs b/compiler/rustc_mir_transform/src/known_panics_lint.rs index 4bca437ea6f27..3e68e3cc47a9f 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -639,7 +639,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { NullOp::OffsetOf(fields) => { op_layout.offset_of_subfield(self, fields.iter()).bytes() } - NullOp::UbCheck(_) => return None, + NullOp::UbChecks => return None, }; ImmTy::from_scalar(Scalar::from_target_usize(val, self), layout).into() } diff --git a/compiler/rustc_mir_transform/src/lower_intrinsics.rs b/compiler/rustc_mir_transform/src/lower_intrinsics.rs index 1bab240ef50f2..7d4c1b9c21a62 100644 --- a/compiler/rustc_mir_transform/src/lower_intrinsics.rs +++ b/compiler/rustc_mir_transform/src/lower_intrinsics.rs @@ -20,30 +20,13 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics { sym::unreachable => { terminator.kind = TerminatorKind::Unreachable; } - sym::check_language_ub => { + sym::ub_checks => { let target = target.unwrap(); block.statements.push(Statement { source_info: terminator.source_info, kind: StatementKind::Assign(Box::new(( *destination, - Rvalue::NullaryOp( - NullOp::UbCheck(UbKind::LanguageUb), - tcx.types.bool, - ), - ))), - }); - terminator.kind = TerminatorKind::Goto { target }; - } - sym::check_library_ub => { - let target = target.unwrap(); - block.statements.push(Statement { - source_info: terminator.source_info, - kind: StatementKind::Assign(Box::new(( - *destination, - Rvalue::NullaryOp( - NullOp::UbCheck(UbKind::LibraryUb), - tcx.types.bool, - ), + Rvalue::NullaryOp(NullOp::UbChecks, tcx.types.bool), ))), }); terminator.kind = TerminatorKind::Goto { target }; diff --git a/compiler/rustc_mir_transform/src/promote_consts.rs b/compiler/rustc_mir_transform/src/promote_consts.rs index 9fe8c34a8bf5d..bb4e38b148a2c 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -446,7 +446,7 @@ impl<'tcx> Validator<'_, 'tcx> { NullOp::SizeOf => {} NullOp::AlignOf => {} NullOp::OffsetOf(_) => {} - NullOp::UbCheck(_) => {} + NullOp::UbChecks => {} }, Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable), diff --git a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs index c0876adf90509..b6a722da602e0 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs @@ -251,19 +251,13 @@ impl<'tcx> Stable<'tcx> for mir::NullOp<'tcx> { type T = stable_mir::mir::NullOp; fn stable(&self, tables: &mut Tables<'_>) -> Self::T { use rustc_middle::mir::NullOp::*; - use rustc_middle::mir::UbKind; match self { SizeOf => stable_mir::mir::NullOp::SizeOf, AlignOf => stable_mir::mir::NullOp::AlignOf, OffsetOf(indices) => stable_mir::mir::NullOp::OffsetOf( indices.iter().map(|idx| idx.stable(tables)).collect(), ), - UbCheck(UbKind::LanguageUb) => { - stable_mir::mir::NullOp::UbCheck(stable_mir::mir::UbKind::LanguageUb) - } - UbCheck(UbKind::LibraryUb) => { - stable_mir::mir::NullOp::UbCheck(stable_mir::mir::UbKind::LibraryUb) - } + UbChecks => stable_mir::mir::NullOp::UbChecks, } } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index e43c9533382e1..fddeb3e8d6cfa 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -518,8 +518,6 @@ symbols! { cfi, cfi_encoding, char, - check_language_ub, - check_library_ub, client, clippy, clobber_abi, @@ -1865,6 +1863,7 @@ symbols! { u8_legacy_fn_max_value, u8_legacy_fn_min_value, u8_legacy_mod, + ub_checks, unaligned_volatile_load, unaligned_volatile_store, unboxed_closures, diff --git a/compiler/stable_mir/src/mir/body.rs b/compiler/stable_mir/src/mir/body.rs index ae8e71bb950a1..c99ad22fc29f3 100644 --- a/compiler/stable_mir/src/mir/body.rs +++ b/compiler/stable_mir/src/mir/body.rs @@ -639,7 +639,7 @@ impl Rvalue { Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => { Ok(Ty::usize_ty()) } - Rvalue::NullaryOp(NullOp::UbCheck(_), _) => Ok(Ty::bool_ty()), + Rvalue::NullaryOp(NullOp::UbChecks, _) => Ok(Ty::bool_ty()), Rvalue::Aggregate(ak, ops) => match *ak { AggregateKind::Array(ty) => Ty::try_new_array(ty, ops.len() as u64), AggregateKind::Tuple => Ok(Ty::new_tuple( @@ -1007,13 +1007,7 @@ pub enum NullOp { /// Returns the offset of a field. OffsetOf(Vec<(VariantIdx, FieldIdx)>), /// cfg!(debug_assertions), but at codegen time - UbCheck(UbKind), -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum UbKind { - LanguageUb, - LibraryUb, + UbChecks, } impl Operand { diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index 0ed26b6ed4ee4..c2001e9ecb23d 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -2629,38 +2629,18 @@ pub const fn is_val_statically_known(_arg: T) -> bool { false } -/// Returns whether we should check for library UB. This evaluate to the value of `cfg!(debug_assertions)` -/// during monomorphization. +/// Returns whether we should perform some UB-checking at runtime. This evaluate to the value of +/// `cfg!(debug_assertions)` during monomorphization. /// /// This intrinsic is evaluated after monomorphization, and therefore branching on this value can /// be used to implement debug assertions that are included in the precompiled standard library, /// but can be optimized out by builds that monomorphize the standard library code with debug /// assertions disabled. This intrinsic is primarily used by [`assert_unsafe_precondition`]. -/// -/// We have separate intrinsics for library UB and language UB because checkers like the const-eval -/// interpreter and Miri already implement checks for language UB. Since such checkers do not know -/// about library preconditions, checks guarded by this intrinsic let them find more UB. -#[rustc_const_unstable(feature = "ub_checks", issue = "none")] -#[unstable(feature = "core_intrinsics", issue = "none")] -#[inline(always)] -#[cfg_attr(not(bootstrap), rustc_intrinsic)] -pub(crate) const fn check_library_ub() -> bool { - cfg!(debug_assertions) -} - -/// Returns whether we should check for language UB. This evaluate to the value of `cfg!(debug_assertions)` -/// during monomorphization. -/// -/// Since checks implemented at the source level must come strictly before the operation that -/// executes UB, if we enabled language UB checks in const-eval/Miri we would miss out on the -/// interpreter's improved diagnostics for the cases that our source-level checks catch. -/// -/// See `check_library_ub` for more information. -#[rustc_const_unstable(feature = "ub_checks", issue = "none")] +#[rustc_const_unstable(feature = "const_ub_checks", issue = "none")] #[unstable(feature = "core_intrinsics", issue = "none")] #[inline(always)] #[cfg_attr(not(bootstrap), rustc_intrinsic)] -pub(crate) const fn check_language_ub() -> bool { +pub(crate) const fn ub_checks() -> bool { cfg!(debug_assertions) } diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 626e85fc0484b..3f722d4ad9e55 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -168,6 +168,7 @@ #![feature(const_try)] #![feature(const_type_id)] #![feature(const_type_name)] +#![feature(const_ub_checks)] #![feature(const_unicode_case_lookup)] #![feature(const_unsafecell_get_mut)] #![feature(const_waker)] diff --git a/library/core/src/ub_checks.rs b/library/core/src/ub_checks.rs index 4ef56392fad33..6d5df208007a8 100644 --- a/library/core/src/ub_checks.rs +++ b/library/core/src/ub_checks.rs @@ -1,7 +1,7 @@ //! Provides the [`assert_unsafe_precondition`] macro as well as some utility functions that cover //! common preconditions. -use crate::intrinsics::const_eval_select; +use crate::intrinsics::{self, const_eval_select}; /// Check that the preconditions of an unsafe function are followed. The check is enabled at /// runtime if debug assertions are enabled when the caller is monomorphized. In const-eval/Miri @@ -45,7 +45,7 @@ use crate::intrinsics::const_eval_select; /// order to call it. Since the precompiled standard library is built with full debuginfo and these /// variables cannot be optimized out in MIR, an innocent-looking `let` can produce enough /// debuginfo to have a measurable compile-time impact on debug builds. -#[allow_internal_unstable(ub_checks)] // permit this to be called in stably-const fn +#[allow_internal_unstable(const_ub_checks)] // permit this to be called in stably-const fn macro_rules! assert_unsafe_precondition { ($kind:ident, $message:expr, ($($name:ident:$ty:ty = $arg:expr),*$(,)?) => $e:expr $(,)?) => { { @@ -69,7 +69,7 @@ macro_rules! assert_unsafe_precondition { #[cfg_attr(not(bootstrap), rustc_no_mir_inline)] #[cfg_attr(not(bootstrap), inline)] #[rustc_nounwind] - #[rustc_const_unstable(feature = "ub_checks", issue = "none")] + #[rustc_const_unstable(feature = "const_ub_checks", issue = "none")] const fn precondition_check($($name:$ty),*) { if !$e { ::core::panicking::panic_nounwind( @@ -78,7 +78,7 @@ macro_rules! assert_unsafe_precondition { } } - if ::core::intrinsics::$kind() { + if ::core::ub_checks::$kind() { precondition_check($($arg,)*); } } @@ -86,6 +86,36 @@ macro_rules! assert_unsafe_precondition { } pub(crate) use assert_unsafe_precondition; +/// Checking library UB is always enabled when UB-checking is done +/// (and we use a reexport so that there is no unnecessary wrapper function). +pub(crate) use intrinsics::ub_checks as check_library_ub; + +/// Determines whether we should check for language UB. +/// +/// The intention is to not do that when running in the interpreter, as that one has its own +/// language UB checks which generally produce better errors. +#[rustc_const_unstable(feature = "const_ub_checks", issue = "none")] +pub(crate) const fn check_language_ub() -> bool { + #[inline] + fn runtime() -> bool { + // Disable UB checks in Miri. + !cfg!(miri) + } + + #[inline] + const fn comptime() -> bool { + // Always disable UB checks. + false + } + + #[cfg_attr(not(bootstrap), allow(unused_unsafe))] // on bootstrap bump, remove unsafe block + // SAFETY: `const_eval_select` is only used to toggle UB checks here, not to provide any + // observable behavior differences. + unsafe { + intrinsics::ub_checks() && const_eval_select((), comptime, runtime) + } +} + /// Checks whether `ptr` is properly aligned with respect to /// `align_of::()`. ///