@@ -2628,24 +2628,38 @@ pub const fn is_val_statically_known<T: Copy>(_arg: T) -> bool {
2628
2628
false
2629
2629
}
2630
2630
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
2643
2637
/// 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" ) ]
2645
2643
#[ unstable( feature = "core_intrinsics" , issue = "none" ) ]
2646
2644
#[ inline( always) ]
2647
2645
#[ 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 {
2649
2663
cfg ! ( debug_assertions)
2650
2664
}
2651
2665
@@ -2713,13 +2727,24 @@ pub unsafe fn retag_box_to_raw<T: ?Sized, A>(ptr: *mut T) -> *mut T {
2713
2727
// (`transmute` also falls into this category, but it cannot be wrapped due to the
2714
2728
// check that `T` and `U` have the same size.)
2715
2729
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.
2718
2733
///
2719
2734
/// 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.
2723
2748
///
2724
2749
/// These checks are behind a condition which is evaluated at codegen time, not expansion time like
2725
2750
/// [`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 {
2728
2753
/// this macro, that monomorphization will contain the check.
2729
2754
///
2730
2755
/// 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.
2735
2767
///
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
2737
2769
/// order to call it. Since the precompiled standard library is built with full debuginfo and these
2738
2770
/// variables cannot be optimized out in MIR, an innocent-looking `let` can produce enough
2739
2771
/// 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
2754
2773
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 $( , ) ?) => {
2756
2775
{
2757
2776
// #[cfg(bootstrap)] (this comment)
2758
2777
// 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 {
2774
2793
#[ cfg_attr( not( bootstrap) , rustc_no_mir_inline) ]
2775
2794
#[ cfg_attr( not( bootstrap) , inline) ]
2776
2795
#[ rustc_nounwind]
2777
- fn precondition_check( $( $name: $ty) ,* ) {
2796
+ #[ rustc_const_unstable( feature = "ub_checks" , issue = "none" ) ]
2797
+ const fn precondition_check( $( $name: $ty) ,* ) {
2778
2798
if !$e {
2779
2799
:: core:: panicking:: panic_nounwind(
2780
2800
concat!( "unsafe precondition(s) violated: " , $message)
2781
2801
) ;
2782
2802
}
2783
2803
}
2784
- const fn comptime( $( _: $ty) ,* ) { }
2785
2804
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, ) * ) ;
2788
2807
}
2789
2808
}
2790
2809
} ;
@@ -2793,32 +2812,60 @@ pub(crate) use assert_unsafe_precondition;
2793
2812
2794
2813
/// Checks whether `ptr` is properly aligned with respect to
2795
2814
/// `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`.
2796
2819
#[ 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 {
2798
2821
!ptr. is_null ( ) && ptr. is_aligned_to ( align)
2799
2822
}
2800
2823
2801
2824
#[ 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 {
2803
2826
let max_len = if size == 0 { usize:: MAX } else { isize:: MAX as usize / size } ;
2804
2827
len <= max_len
2805
2828
}
2806
2829
2807
2830
/// Checks whether the regions of memory starting at `src` and `dst` of size
2808
2831
/// `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`.
2809
2835
#[ 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
+ }
2822
2869
}
2823
2870
2824
2871
/// 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
2919
2966
pub fn copy_nonoverlapping < T > ( src : * const T , dst : * mut T , count : usize ) ;
2920
2967
}
2921
2968
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
+
2922
2985
// SAFETY: the safety contract for `copy_nonoverlapping` must be
2923
2986
// 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) }
2941
2988
}
2942
2989
2943
2990
/// 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) {
3024
3071
// SAFETY: the safety contract for `copy` must be upheld by the caller.
3025
3072
unsafe {
3026
3073
assert_unsafe_precondition ! (
3074
+ check_language_ub,
3027
3075
"ptr::copy_nonoverlapping requires that both pointer arguments are aligned and non-null \
3028
3076
and the specified memory ranges do not overlap",
3029
3077
(
@@ -3104,6 +3152,7 @@ pub const unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize) {
3104
3152
// SAFETY: the safety contract for `write_bytes` must be upheld by the caller.
3105
3153
unsafe {
3106
3154
assert_unsafe_precondition ! (
3155
+ check_language_ub,
3107
3156
"ptr::write_bytes requires that the destination pointer is aligned and non-null" ,
3108
3157
(
3109
3158
addr: * const ( ) = dst as * const ( ) ,
0 commit comments