diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 1664ce83aef72..6fac5cc11e1e4 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -515,8 +515,13 @@ macro_rules! uint_impl { /// /// # Panics /// - /// This function will panic if `n` is greater than or equal to the number of - /// bits in `self`. + /// ## Overflow behavior + /// + /// If overflow checks are enabled (default in debug mode), this function will panic if `n` + /// is greater than or equal to the number of bits in `self`. If overflow checks are + /// disabled (default in release mode), there is no panic; instead, the value is shifted + /// by `n % Self::BITS`. + // FIXME(wrapping_funnel_shifts): link to `wrapping_funnel_shl` when stable. /// /// # Examples /// @@ -543,21 +548,31 @@ macro_rules! uint_impl { /// /// ```should_panic /// #![feature(funnel_shifts)] + /// # #![feature(cfg_overflow_checks)] + /// # #[cfg(overflow_checks)] { /// #[doc = concat!("let a = ", stringify!($SelfT), "::MAX;")] /// // Okay #[doc = concat!("let _ = a.rotate_left(", stringify!($SelfT), "::BITS);")] - /// // Panics + /// // Panics (only when overflow checks are enabled) #[doc = concat!("let _ = a.funnel_shl(a, ", stringify!($SelfT), "::BITS);")] + /// # } + /// # #[cfg(not(overflow_checks))] panic!("fulfill should_panic"); /// ``` #[rustc_const_unstable(feature = "funnel_shifts", issue = "145686")] #[unstable(feature = "funnel_shifts", issue = "145686")] #[must_use = "this returns the result of the operation, without modifying the original"] #[inline(always)] + #[rustc_inherit_overflow_checks] pub const fn funnel_shl(self, right: Self, n: u32) -> Self { - assert!(n < Self::BITS, "attempt to funnel shift left with overflow"); - // SAFETY: just checked that `shift` is in-range - unsafe { self.unchecked_funnel_shl(right, n) } + if intrinsics::overflow_checks() { + assert!(n < Self::BITS, "attempt to funnel shift left with overflow"); + } + // SAFETY: `n` is wrapped to within range + unsafe { + let n = n & (Self::BITS - 1); + self.unchecked_funnel_shl(right, n) + } } /// Performs a right funnel shift. @@ -571,8 +586,13 @@ macro_rules! uint_impl { /// /// # Panics /// - /// This function will panic if `n` is greater than or equal to the number of - /// bits in `self`. + /// ## Overflow behavior + /// + /// If overflow checks are enabled (default in debug mode), this function will panic if `n` + /// is greater than or equal to the number of bits in `self`. If overflow checks are + /// disabled (default in release mode), there is no panic; instead, the value is shifted + /// by `n % Self::BITS`. + // FIXME(wrapping_funnel_shifts): link to `wrapping_funnel_shr` when stable. /// /// # Examples /// @@ -599,21 +619,31 @@ macro_rules! uint_impl { /// /// ```should_panic /// #![feature(funnel_shifts)] + /// # #![feature(cfg_overflow_checks)] + /// # #[cfg(overflow_checks)] { /// #[doc = concat!("let a = ", stringify!($SelfT), "::MAX;")] /// // Okay #[doc = concat!("let _ = a.rotate_right(", stringify!($SelfT), "::BITS);")] - /// // Panics + /// // Panics (only when overflow checks are enabled) #[doc = concat!("let _ = a.funnel_shr(a, ", stringify!($SelfT), "::BITS);")] + /// # } + /// # #[cfg(not(overflow_checks))] panic!("fulfill should_panic"); /// ``` #[rustc_const_unstable(feature = "funnel_shifts", issue = "145686")] #[unstable(feature = "funnel_shifts", issue = "145686")] #[must_use = "this returns the result of the operation, without modifying the original"] #[inline(always)] + #[rustc_inherit_overflow_checks] pub const fn funnel_shr(self, right: Self, n: u32) -> Self { - assert!(n < Self::BITS, "attempt to funnel shift right with overflow"); - // SAFETY: just checked that `shift` is in-range - unsafe { self.unchecked_funnel_shr(right, n) } + if intrinsics::overflow_checks() { + assert!(n < Self::BITS, "attempt to funnel shift right with overflow"); + } + // SAFETY: `n` is wrapped to within range + unsafe { + let n = n & (Self::BITS - 1); + self.unchecked_funnel_shr(right, n) + } } /// Unchecked funnel shift left. diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 35327264ccd77..3d23e7bb7ee24 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -11,6 +11,7 @@ #![feature(borrowed_buf_init)] #![feature(bstr)] #![feature(casefold)] +#![feature(cfg_overflow_checks)] #![feature(cfg_target_has_reliable_f16_f128)] #![feature(char_internals)] #![feature(clone_to_uninit)] diff --git a/library/coretests/tests/num/uint_macros.rs b/library/coretests/tests/num/uint_macros.rs index 8189776807915..4dfcb1b8b4688 100644 --- a/library/coretests/tests/num/uint_macros.rs +++ b/library/coretests/tests/num/uint_macros.rs @@ -216,17 +216,31 @@ macro_rules! uint_module { } #[test] + #[cfg(overflow_checks)] #[should_panic = "attempt to funnel shift left with overflow"] fn test_funnel_shl_overflow() { let _ = <$T>::funnel_shl(A, B, $T::BITS); } #[test] + #[cfg(overflow_checks)] #[should_panic = "attempt to funnel shift right with overflow"] fn test_funnel_shr_overflow() { let _ = <$T>::funnel_shr(A, B, $T::BITS); } + #[test] + #[cfg(not(overflow_checks))] + fn test_funnel_shl_overflow() { + let _ = <$T>::funnel_shl(A, B, $T::BITS); + } + + #[test] + #[cfg(not(overflow_checks))] + fn test_funnel_shr_overflow() { + let _ = <$T>::funnel_shr(A, B, $T::BITS); + } + #[test] fn test_funnel_shifts_runtime() { for i in 0..$T::BITS - 1 { diff --git a/tests/ui/std/overflow-check-ops.rs b/tests/ui/std/overflow-check-ops.rs new file mode 100644 index 0000000000000..458c1b121664e --- /dev/null +++ b/tests/ui/std/overflow-check-ops.rs @@ -0,0 +1,45 @@ +//! Verify the behavior differences between enabling and disabling overflow checks. + +//@ run-pass +//@ needs-unwind +//@ revisions: ERROR WRAP +//@[ERROR] compile-flags: -C overflow-checks=true +//@[WRAP] compile-flags: -C overflow-checks=false + +#![feature(cfg_overflow_checks)] +#![feature(funnel_shifts)] + +use std::hint::black_box as bb; +use std::{assert_matches, fmt, panic}; + +#[track_caller] +fn check(func: fn() -> T, wrapping_res: T, name: &str) { + let type_name = std::any::type_name::(); + let res = panic::catch_unwind(func); + if cfg!(overflow_checks) { + assert_matches!(res, Err(_), "{type_name} {name}"); + } else { + assert_eq!(res.unwrap(), wrapping_res, "{type_name} {name}"); + } +} + +fn main() { + check(|| bb(u32::MAX) + bb(1), 0, "add"); + check(|| bb(0u32) - bb(1), u32::MAX, "sub"); + check(|| bb(u32::MAX) * bb(2), u32::MAX << 1, "mul"); + check(|| bb(1u32) << bb(32), 1, "shl"); + check(|| bb(u32::MAX) >> bb(32), u32::MAX, "shr"); + check(|| bb(1234u32).funnel_shl(4567, bb(32)), 1234, "funnel_shl"); + check(|| bb(1234u32).funnel_shr(4567, bb(32)), 4567, "funnel_shr"); + check(|| bb(u32::MAX).pow(bb(2)), 1, "pow"); + check(|| bb(u32::MAX).next_power_of_two(), 0, "next_power_of_two"); + + check(|| bb(i32::MAX) + bb(1), i32::MIN, "add"); + check(|| bb(i32::MIN) - bb(1), i32::MAX, "sub"); + check(|| bb(i32::MAX) * bb(2), i32::MAX << 1, "mul"); + check(|| -bb(i32::MIN), i32::MIN, "neg"); + check(|| bb(i32::MIN).abs(), i32::MIN, "abs"); + check(|| bb(1) << bb(32), 1, "shl"); + check(|| bb(i32::MAX) >> bb(32), i32::MAX, "shr"); + check(|| bb(i32::MAX).pow(bb(2)), 1, "pow"); +}