@@ -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" ) ]
2643+ #[ unstable( feature = "core_intrinsics" , issue = "none" ) ]
2644+ #[ inline( always) ]
2645+ #[ cfg_attr( not( bootstrap) , rustc_intrinsic) ]
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" ) ]
26452659#[ unstable( feature = "core_intrinsics" , issue = "none" ) ]
26462660#[ inline( always) ]
26472661#[ cfg_attr( not( bootstrap) , rustc_intrinsic) ]
2648- pub ( crate ) const fn debug_assertions ( ) -> bool {
2662+ pub ( crate ) const fn check_language_ub ( ) -> bool {
26492663 cfg ! ( debug_assertions)
26502664}
26512665
@@ -2700,13 +2714,24 @@ pub unsafe fn vtable_size(_ptr: *const ()) -> usize {
27002714// (`transmute` also falls into this category, but it cannot be wrapped due to the
27012715// check that `T` and `U` have the same size.)
27022716
2703- /// Check that the preconditions of an unsafe function are followed, if debug_assertions are on,
2704- /// and only at runtime.
2717+ /// Check that the preconditions of an unsafe function are followed. The check is enabled at
2718+ /// runtime if debug assertions are enabled when the caller is monomorphized. In const-eval/Miri
2719+ /// checks implemented with this macro for language UB are always ignored.
27052720///
27062721/// This macro should be called as
2707- /// `assert_unsafe_precondition!((expr => name: Type, expr => name: Type) => Expression)`
2708- /// where each `expr` will be evaluated and passed in as function argument `name: Type`. Then all
2709- /// those arguments are passed to a function via [`const_eval_select`].
2722+ /// `assert_unsafe_precondition!(check_{library,lang}_ub, "message", (ident: type = expr, ident: type = expr) => check_expr)`
2723+ /// where each `expr` will be evaluated and passed in as function argument `ident: type`. Then all
2724+ /// those arguments are passed to a function with the body `check_expr`.
2725+ /// Pick `check_language_ub` when this is guarding a violation of language UB, i.e., immediate UB
2726+ /// according to the Rust Abstract Machine. Pick `check_library_ub` when this is guarding a violation
2727+ /// of a documented library precondition that does not *immediately* lead to language UB.
2728+ ///
2729+ /// If `check_library_ub` is used but the check is actually guarding language UB, the check will
2730+ /// slow down const-eval/Miri and we'll get the panic message instead of the interpreter's nice
2731+ /// diagnostic, but our ability to detect UB is unchanged.
2732+ /// But if `check_language_ub` is used when the check is actually for library UB, the check is
2733+ /// omitted in const-eval/Miri and thus if we eventually execute language UB which relies on the
2734+ /// library UB, the backtrace Miri reports may be far removed from original cause.
27102735///
27112736/// These checks are behind a condition which is evaluated at codegen time, not expansion time like
27122737/// [`debug_assert`]. This means that a standard library built with optimizations and debug
@@ -2715,31 +2740,25 @@ pub unsafe fn vtable_size(_ptr: *const ()) -> usize {
27152740/// this macro, that monomorphization will contain the check.
27162741///
27172742/// Since these checks cannot be optimized out in MIR, some care must be taken in both call and
2718- /// implementation to mitigate their compile-time overhead. The runtime function that we
2719- /// [`const_eval_select`] to is monomorphic, `#[inline(never)]`, and `#[rustc_nounwind]`. That
2720- /// combination of properties ensures that the code for the checks is only compiled once, and has a
2721- /// minimal impact on the caller's code size.
2743+ /// implementation to mitigate their compile-time overhead. Calls to this macro always expand to
2744+ /// this structure:
2745+ /// ```ignore (pseudocode)
2746+ /// if ::core::intrinsics::check_language_ub() {
2747+ /// precondition_check(args)
2748+ /// }
2749+ /// ```
2750+ /// where `precondition_check` is monomorphic with the attributes `#[rustc_nounwind]`, `#[inline]` and
2751+ /// `#[rustc_no_mir_inline]`. This combination of attributes ensures that the actual check logic is
2752+ /// compiled only once and generates a minimal amount of IR because the check cannot be inlined in
2753+ /// MIR, but *can* be inlined and fully optimized by a codegen backend.
27222754///
2723- /// Callers should also avoid introducing any other `let` bindings or any code outside this macro in
2755+ /// Callers should avoid introducing any other `let` bindings or any code outside this macro in
27242756/// order to call it. Since the precompiled standard library is built with full debuginfo and these
27252757/// variables cannot be optimized out in MIR, an innocent-looking `let` can produce enough
27262758/// debuginfo to have a measurable compile-time impact on debug builds.
2727- ///
2728- /// # Safety
2729- ///
2730- /// Invoking this macro is only sound if the following code is already UB when the passed
2731- /// expression evaluates to false.
2732- ///
2733- /// This macro expands to a check at runtime if debug_assertions is set. It has no effect at
2734- /// compile time, but the semantics of the contained `const_eval_select` must be the same at
2735- /// runtime and at compile time. Thus if the expression evaluates to false, this macro produces
2736- /// different behavior at compile time and at runtime, and invoking it is incorrect.
2737- ///
2738- /// So in a sense it is UB if this macro is useful, but we expect callers of `unsafe fn` to make
2739- /// the occasional mistake, and this check should help them figure things out.
2740- #[ allow_internal_unstable( const_eval_select, delayed_debug_assertions) ] // permit this to be called in stably-const fn
2759+ #[ allow_internal_unstable( ub_checks) ] // permit this to be called in stably-const fn
27412760macro_rules! assert_unsafe_precondition {
2742- ( $message: expr, ( $( $name: ident: $ty: ty = $arg: expr) ,* $( , ) ?) => $e: expr $( , ) ?) => {
2761+ ( $kind : ident , $ message: expr, ( $( $name: ident: $ty: ty = $arg: expr) ,* $( , ) ?) => $e: expr $( , ) ?) => {
27432762 {
27442763 // #[cfg(bootstrap)] (this comment)
27452764 // When the standard library is compiled with debug assertions, we want the check to inline for better performance.
@@ -2761,17 +2780,17 @@ macro_rules! assert_unsafe_precondition {
27612780 #[ cfg_attr( not( bootstrap) , rustc_no_mir_inline) ]
27622781 #[ cfg_attr( not( bootstrap) , inline) ]
27632782 #[ rustc_nounwind]
2764- fn precondition_check( $( $name: $ty) ,* ) {
2783+ #[ rustc_const_unstable( feature = "ub_checks" , issue = "none" ) ]
2784+ const fn precondition_check( $( $name: $ty) ,* ) {
27652785 if !$e {
27662786 :: core:: panicking:: panic_nounwind(
27672787 concat!( "unsafe precondition(s) violated: " , $message)
27682788 ) ;
27692789 }
27702790 }
2771- const fn comptime( $( _: $ty) ,* ) { }
27722791
2773- if :: core:: intrinsics:: debug_assertions ( ) {
2774- :: core :: intrinsics :: const_eval_select ( ( $( $arg, ) * ) , comptime , precondition_check ) ;
2792+ if :: core:: intrinsics:: $kind ( ) {
2793+ precondition_check ( $( $arg, ) * ) ;
27752794 }
27762795 }
27772796 } ;
@@ -2781,31 +2800,55 @@ pub(crate) use assert_unsafe_precondition;
27812800/// Checks whether `ptr` is properly aligned with respect to
27822801/// `align_of::<T>()`.
27832802#[ inline]
2784- pub ( crate ) fn is_aligned_and_not_null ( ptr : * const ( ) , align : usize ) -> bool {
2803+ pub ( crate ) const fn is_aligned_and_not_null ( ptr : * const ( ) , align : usize ) -> bool {
27852804 !ptr. is_null ( ) && ptr. is_aligned_to ( align)
27862805}
27872806
27882807#[ inline]
2789- pub ( crate ) fn is_valid_allocation_size ( size : usize , len : usize ) -> bool {
2808+ pub ( crate ) const fn is_valid_allocation_size ( size : usize , len : usize ) -> bool {
27902809 let max_len = if size == 0 { usize:: MAX } else { isize:: MAX as usize / size } ;
27912810 len <= max_len
27922811}
27932812
27942813/// Checks whether the regions of memory starting at `src` and `dst` of size
27952814/// `count * size` do *not* overlap.
2815+ ///
2816+ /// # Safety
2817+ /// This function must only be called such that if it returns false, we will execute UB.
27962818#[ inline]
2797- pub ( crate ) fn is_nonoverlapping ( src : * const ( ) , dst : * const ( ) , size : usize , count : usize ) -> bool {
2798- let src_usize = src. addr ( ) ;
2799- let dst_usize = dst. addr ( ) ;
2800- let Some ( size) = size. checked_mul ( count) else {
2801- crate :: panicking:: panic_nounwind (
2802- "is_nonoverlapping: `size_of::<T>() * count` overflows a usize" ,
2803- )
2804- } ;
2805- let diff = src_usize. abs_diff ( dst_usize) ;
2806- // If the absolute distance between the ptrs is at least as big as the size of the buffer,
2807- // they do not overlap.
2808- diff >= size
2819+ pub ( crate ) const unsafe fn is_nonoverlapping (
2820+ src : * const ( ) ,
2821+ dst : * const ( ) ,
2822+ size : usize ,
2823+ count : usize ,
2824+ ) -> bool {
2825+ #[ inline]
2826+ fn runtime ( src : * const ( ) , dst : * const ( ) , size : usize , count : usize ) -> bool {
2827+ let src_usize = src. addr ( ) ;
2828+ let dst_usize = dst. addr ( ) ;
2829+ let Some ( size) = size. checked_mul ( count) else {
2830+ crate :: panicking:: panic_nounwind (
2831+ "is_nonoverlapping: `size_of::<T>() * count` overflows a usize" ,
2832+ )
2833+ } ;
2834+ let diff = src_usize. abs_diff ( dst_usize) ;
2835+ // If the absolute distance between the ptrs is at least as big as the size of the buffer,
2836+ // they do not overlap.
2837+ diff >= size
2838+ }
2839+
2840+ #[ inline]
2841+ const fn comptime ( _: * const ( ) , _: * const ( ) , _: usize , _: usize ) -> bool {
2842+ true
2843+ }
2844+
2845+ #[ cfg_attr( not( bootstrap) , allow( unused_unsafe) ) ]
2846+ // SAFETY: This function's precondition is equivalent to that of `const_eval_select`.
2847+ // Programs which do not execute UB will only see this function return `true`, which makes the
2848+ // const and runtime implementation indistinguishable.
2849+ unsafe {
2850+ const_eval_select ( ( src, dst, size, count) , comptime, runtime)
2851+ }
28092852}
28102853
28112854/// Copies `count * size_of::<T>()` bytes from `src` to `dst`. The source
@@ -2906,25 +2949,26 @@ pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: us
29062949 pub fn copy_nonoverlapping < T > ( src : * const T , dst : * mut T , count : usize ) ;
29072950 }
29082951
2952+ assert_unsafe_precondition ! (
2953+ check_language_ub,
2954+ "ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \
2955+ and the specified memory ranges do not overlap",
2956+ (
2957+ src: * const ( ) = src as * const ( ) ,
2958+ dst: * mut ( ) = dst as * mut ( ) ,
2959+ size: usize = size_of:: <T >( ) ,
2960+ align: usize = align_of:: <T >( ) ,
2961+ count: usize = count,
2962+ ) =>
2963+ is_aligned_and_not_null( src, align)
2964+ && is_aligned_and_not_null( dst, align)
2965+ // SAFETY: If this returns false, we're about to execute UB.
2966+ && unsafe { is_nonoverlapping( src, dst, size, count) }
2967+ ) ;
2968+
29092969 // SAFETY: the safety contract for `copy_nonoverlapping` must be
29102970 // upheld by the caller.
2911- unsafe {
2912- assert_unsafe_precondition ! (
2913- "ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \
2914- and the specified memory ranges do not overlap",
2915- (
2916- src: * const ( ) = src as * const ( ) ,
2917- dst: * mut ( ) = dst as * mut ( ) ,
2918- size: usize = size_of:: <T >( ) ,
2919- align: usize = align_of:: <T >( ) ,
2920- count: usize = count,
2921- ) =>
2922- is_aligned_and_not_null( src, align)
2923- && is_aligned_and_not_null( dst, align)
2924- && is_nonoverlapping( src, dst, size, count)
2925- ) ;
2926- copy_nonoverlapping ( src, dst, count)
2927- }
2971+ unsafe { copy_nonoverlapping ( src, dst, count) }
29282972}
29292973
29302974/// Copies `count * size_of::<T>()` bytes from `src` to `dst`. The source
@@ -3011,6 +3055,7 @@ pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
30113055 // SAFETY: the safety contract for `copy` must be upheld by the caller.
30123056 unsafe {
30133057 assert_unsafe_precondition ! (
3058+ check_language_ub,
30143059 "ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \
30153060 and the specified memory ranges do not overlap",
30163061 (
@@ -3091,6 +3136,7 @@ pub const unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize) {
30913136 // SAFETY: the safety contract for `write_bytes` must be upheld by the caller.
30923137 unsafe {
30933138 assert_unsafe_precondition ! (
3139+ check_language_ub,
30943140 "ptr::write_bytes requires that the destination pointer is aligned and non-null" ,
30953141 (
30963142 addr: * const ( ) = dst as * const ( ) ,
0 commit comments