From c49d0aaf33a17d6ed0cdeb143e619f8bbace3d21 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 20 Jun 2023 13:13:21 +0200 Subject: [PATCH] add wrapping_offset_from which allows wrapping but still requires ptrs to be for the same allocation --- .../rustc_codegen_ssa/src/mir/intrinsic.rs | 31 +++-- .../src/interpret/intrinsics.rs | 89 ++++++++------ .../rustc_hir_analysis/src/check/intrinsic.rs | 2 +- compiler/rustc_span/src/symbol.rs | 1 + library/core/src/intrinsics.rs | 6 + library/core/src/lib.rs | 1 + library/core/src/ptr/const_ptr.rs | 114 +++++++++++++++++- library/core/src/ptr/mut_ptr.rs | 112 ++++++++++++++++- src/tools/miri/tests/pass/ptr_offset.rs | 9 +- tests/codegen/intrinsics/offset_from.rs | 12 ++ tests/ui/consts/offset_from_ub.rs | 49 +++++++- tests/ui/consts/offset_from_ub.stderr | 14 ++- 12 files changed, 378 insertions(+), 62 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs index 8a65dd593b8c1..f5c1b5d2baf4e 100644 --- a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs +++ b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs @@ -438,7 +438,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bx.icmp(IntPredicate::IntEQ, a, b) } - sym::ptr_offset_from | sym::ptr_offset_from_unsigned => { + sym::ptr_offset_from | sym::ptr_offset_from_unsigned | sym::ptr_wrapping_offset_from => { let ty = substs.type_at(0); let pointee_size = bx.layout_of(ty).size; @@ -447,18 +447,23 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let a = bx.ptrtoint(a, bx.type_isize()); let b = bx.ptrtoint(b, bx.type_isize()); let pointee_size = bx.const_usize(pointee_size.bytes()); - if name == sym::ptr_offset_from { - // This is the same sequence that Clang emits for pointer subtraction. - // It can be neither `nsw` nor `nuw` because the input is treated as - // unsigned but then the output is treated as signed, so neither works. - let d = bx.sub(a, b); - // this is where the signed magic happens (notice the `s` in `exactsdiv`) - bx.exactsdiv(d, pointee_size) - } else { - // The `_unsigned` version knows the relative ordering of the pointers, - // so can use `sub nuw` and `udiv exact` instead of dealing in signed. - let d = bx.unchecked_usub(a, b); - bx.exactudiv(d, pointee_size) + match name { + sym::ptr_offset_from | sym::ptr_wrapping_offset_from => { + // This is the same sequence that Clang emits for pointer subtraction. + // Even for `ptr_offset_from`, which cannot wrap, this can be neither `nsw` + // nor `nuw` because the input is treated as unsigned but then the output is + // treated as signed, so neither works. + let d = bx.sub(a, b); + // this is where the signed magic happens (notice the `s` in `exactsdiv`) + bx.exactsdiv(d, pointee_size) + } + sym::ptr_offset_from_unsigned => { + // The `_unsigned` version knows the relative ordering of the pointers, + // so can use `sub nuw` and `udiv exact` instead of dealing in signed. + let d = bx.unchecked_usub(a, b); + bx.exactudiv(d, pointee_size) + } + _ => bug!(), } } diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 8f4cf23770e3d..5476c10f3e2d8 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -270,7 +270,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let offset_ptr = ptr.wrapping_signed_offset(offset_bytes, self); self.write_pointer(offset_ptr, dest)?; } - sym::ptr_offset_from | sym::ptr_offset_from_unsigned => { + sym::ptr_offset_from + | sym::ptr_offset_from_unsigned + | sym::ptr_wrapping_offset_from => { let a = self.read_pointer(&args[0])?; let b = self.read_pointer(&args[1])?; @@ -288,6 +290,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { (Err(_), _) | (_, Err(_)) => { // We managed to find a valid allocation for one pointer, but not the other. // That means they are definitely not pointing to the same allocation. + // FIXME: if at least one pointer was a wildcard pointer, we should not throw UB here + // but just use their absolute addresses instead. throw_ub_custom!( fluent::const_eval_different_allocations, name = intrinsic_name, @@ -315,57 +319,62 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { let b_offset = ImmTy::from_uint(b_offset, usize_layout); self.overflowing_binary_op(BinOp::Sub, &a_offset, &b_offset)? }; - if overflowed { - // a < b - if intrinsic_name == sym::ptr_offset_from_unsigned { - throw_ub_custom!( - fluent::const_eval_unsigned_offset_from_overflow, - a_offset = a_offset, - b_offset = b_offset, - ); - } - // The signed form of the intrinsic allows this. If we interpret the - // difference as isize, we'll get the proper signed difference. If that - // seems *positive*, they were more than isize::MAX apart. - let dist = val.to_target_isize(self)?; - if dist >= 0 { - throw_ub_custom!( - fluent::const_eval_offset_from_underflow, - name = intrinsic_name, - ); - } - dist - } else { - // b >= a - let dist = val.to_target_isize(self)?; - // If converting to isize produced a *negative* result, we had an overflow - // because they were more than isize::MAX apart. - if dist < 0 { - throw_ub_custom!( - fluent::const_eval_offset_from_overflow, - name = intrinsic_name, - ); + let dist = val.to_target_isize(self)?; + if intrinsic_name != sym::ptr_wrapping_offset_from { + // Overflow check. + if overflowed { + // a < b + if intrinsic_name == sym::ptr_offset_from_unsigned { + throw_ub_custom!( + fluent::const_eval_unsigned_offset_from_overflow, + a_offset = a_offset, + b_offset = b_offset, + ); + } + // The signed form of the intrinsic allows this. If we interpret the + // difference as isize, we'll get the proper signed difference. If that + // seems *positive*, they were more than isize::MAX apart. + if dist >= 0 { + throw_ub_custom!( + fluent::const_eval_offset_from_underflow, + name = intrinsic_name, + ); + } + } else { + // b >= a, no overflow during subtraction. + // If converting to isize produced a *negative* result, we had an overflow + // when converting to `isize` because they were more than isize::MAX apart. + if dist < 0 { + throw_ub_custom!( + fluent::const_eval_offset_from_overflow, + name = intrinsic_name, + ); + } } - dist } + dist }; // Check that the range between them is dereferenceable ("in-bounds or one past the // end of the same allocation"). This is like the check in ptr_offset_inbounds. - let min_ptr = if dist >= 0 { b } else { a }; - self.check_ptr_access_align( - min_ptr, - Size::from_bytes(dist.unsigned_abs()), - Align::ONE, - CheckInAllocMsg::OffsetFromTest, - )?; + if intrinsic_name != sym::ptr_wrapping_offset_from { + let min_ptr = if dist >= 0 { b } else { a }; + self.check_ptr_access_align( + min_ptr, + Size::from_bytes(dist.unsigned_abs()), + Align::ONE, + CheckInAllocMsg::OffsetFromTest, + )?; + } // Perform division by size to compute return value. let ret_layout = if intrinsic_name == sym::ptr_offset_from_unsigned { assert!(0 <= dist && dist <= self.target_isize_max()); usize_layout } else { - assert!(self.target_isize_min() <= dist && dist <= self.target_isize_max()); + if intrinsic_name != sym::ptr_wrapping_offset_from { + assert!(self.target_isize_min() <= dist && dist <= self.target_isize_max()); + } isize_layout }; let pointee_layout = self.layout_of(substs.type_at(0))?; diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 36c468e778986..c4d42d1a2d8fb 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -355,7 +355,7 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) { tcx.mk_unit(), ), - sym::ptr_offset_from => { + sym::ptr_offset_from | sym::ptr_wrapping_offset_from => { (1, vec![tcx.mk_imm_ptr(param(0)), tcx.mk_imm_ptr(param(0))], tcx.types.isize) } sym::ptr_offset_from_unsigned => { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index bd39cbf17cec1..ddb28f26a08b2 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1156,6 +1156,7 @@ symbols! { ptr_offset_from, ptr_offset_from_unsigned, ptr_unique, + ptr_wrapping_offset_from, pub_macro_rules, pub_restricted, public, diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs index 9b8612485ac1e..02ec5de3fd204 100644 --- a/library/core/src/intrinsics.rs +++ b/library/core/src/intrinsics.rs @@ -2337,6 +2337,12 @@ extern "rust-intrinsic" { #[rustc_nounwind] pub fn ptr_offset_from_unsigned(ptr: *const T, base: *const T) -> usize; + /// See documentation of `<*const T>::wrapping_offset_from` for details. + #[rustc_const_unstable(feature = "ptr_wrapping_offset_from", issue = "none")] + #[rustc_nounwind] + #[cfg(not(bootstrap))] + pub fn ptr_wrapping_offset_from(ptr: *const T, base: *const T) -> isize; + /// See documentation of `<*const T>::guaranteed_eq` for details. /// Returns `2` if the result is unknown. /// Returns `1` if the pointers are guaranteed equal diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 6c419eb16f3b9..3b8166bf49887 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -99,6 +99,7 @@ // // Library features: // tidy-alphabetical-start +#![cfg_attr(not(bootstrap), feature(ptr_wrapping_offset_from))] #![feature(char_indices_offset)] #![feature(const_align_of_val)] #![feature(const_align_of_val_raw)] diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs index 926189a17b204..168e9aea78bcb 100644 --- a/library/core/src/ptr/const_ptr.rs +++ b/library/core/src/ptr/const_ptr.rs @@ -605,7 +605,9 @@ impl *const T { /// Calculates the distance between two pointers. The returned value is in /// units of T: the distance in bytes divided by `mem::size_of::()`. /// - /// This function is the inverse of [`offset`]. + /// This function is the inverse of [`offset`]: it is valid to call if and only if + /// `self` could have been computed as `origin.offset(n)` for some `n`, and it will + /// then return that `n`. /// /// [`offset`]: #method.offset /// @@ -644,6 +646,12 @@ impl *const T { /// (Note that [`offset`] and [`add`] also have a similar limitation and hence cannot be used on /// such large allocations either.) /// + /// The requirement for pointers to be derived from the same allocated object is primarily + /// needed for `const`-compatibility: at compile-time, pointers into *different* allocated + /// object do not have a known distance to each other. However, the requirement also exists at + /// runtime, and may be exploited by optimizations. You can use `(self as usize).sub(origin as + /// usize) / mem::size_of::()` to avoid this requirement. + /// /// [`add`]: #method.add /// [allocated object]: crate::ptr#allocated-object /// @@ -701,7 +709,7 @@ impl *const T { /// units of **bytes**. /// /// This is purely a convenience for casting to a `u8` pointer and - /// using [offset_from][pointer::offset_from] on it. See that method for + /// using [`offset_from`][pointer::offset_from] on it. See that method for /// documentation and safety requirements. /// /// For non-`Sized` pointees this operation considers only the data pointers, @@ -799,6 +807,108 @@ impl *const T { unsafe { intrinsics::ptr_offset_from_unsigned(self, origin) } } + /// Calculates the distance between two pointers using wrapping arithmetic. The returned value + /// is in units of T: the distance in bytes divided by `mem::size_of::()`. + /// + /// This function is the inverse of [`wrapping_offset`]: it is valid to call if and only if + /// `self` could have been computed as `origin.wrapping_offset(n)` for some `n`, and it will + /// then return that `n`. + /// + /// [`wrapping_offset`]: #method.wrapping_offset + /// + /// # Safety + /// + /// If any of the following conditions are violated, the result is Undefined + /// Behavior: + /// + /// * Both pointers must be *derived from* a pointer to the same [allocated object]. + /// (See below for an example.) + /// + /// * The distance between the pointers, in bytes, must be an exact multiple + /// of the size of `T`. + /// + /// Unlike [`offset_from`][pointer::offset_from], this method does *not* require the pointers to + /// be in-bounds of the object they are derived from, nor does it impose any restrictions + /// regarding the maximum distance or wrapping around the address space. + /// + /// The requirement for pointers to be derived from the same allocated object is primarily + /// needed for `const`-compatibility: at compile-time, pointers into *different* allocated + /// object do not have a known distance to each other. However, the requirement also exists at + /// runtime, and may be exploited by optimizations. You can use `(self as usize).sub(origin as + /// usize) / mem::size_of::()` to avoid this requirement. + /// + /// [allocated object]: crate::ptr#allocated-object + /// + /// # Panics + /// + /// This function panics if `T` is a Zero-Sized Type ("ZST"). + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(ptr_wrapping_offset_from)] + /// let a = [0; 2]; + /// let ptr1: *const i32 = &a[1]; + /// let ptr2: *const i32 = a.as_ptr().wrapping_offset(3); // out-of-bounds! + /// unsafe { + /// assert_eq!(ptr2.wrapping_offset_from(ptr1), 2); + /// assert_eq!(ptr1.wrapping_offset_from(ptr2), -2); + /// assert_eq!(ptr1.wrapping_offset(2), ptr2); + /// assert_eq!(ptr2.wrapping_offset(-2), ptr1); + /// } + /// ``` + /// + /// *Incorrect* usage: + /// + /// ```rust,no_run + /// #![feature(ptr_wrapping_offset_from)] + /// let ptr1 = Box::into_raw(Box::new(0u8)) as *const u8; + /// let ptr2 = Box::into_raw(Box::new(1u8)) as *const u8; + /// let diff = (ptr2 as isize).wrapping_sub(ptr1 as isize); + /// // Make ptr2_other an "alias" of ptr2, but derived from ptr1. + /// let ptr2_other = (ptr1 as *const u8).wrapping_offset(diff); + /// assert_eq!(ptr2 as usize, ptr2_other as usize); + /// // Since ptr2_other and ptr2 are derived from pointers to different objects, + /// // computing their offset is undefined behavior, even though + /// // they point to the same address! + /// unsafe { + /// let zero = ptr2_other.wrapping_offset_from(ptr2); // Undefined Behavior + /// } + /// ``` + #[unstable(feature = "ptr_wrapping_offset_from", issue = "none")] + #[rustc_const_unstable(feature = "ptr_wrapping_offset_from", issue = "none")] + #[inline] + #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces + #[cfg(not(bootstrap))] + pub const unsafe fn wrapping_offset_from(self, origin: *const T) -> isize + where + T: Sized, + { + // SAFETY: the caller must uphold the safety contract for `ptr_offset_from`. + unsafe { intrinsics::ptr_wrapping_offset_from(self, origin) } + } + + /// Calculates the distance between two pointers using wrapping arithmetic. The returned value + /// is in units of **bytes**. + /// + /// This is purely a convenience for casting to a `u8` pointer and using + /// [`wrapping_offset_from`][pointer::wrapping_offset_from] on it. See that method for + /// documentation and safety requirements. + /// + /// For non-`Sized` pointees this operation considers only the data pointers, + /// ignoring the metadata. + #[inline(always)] + #[unstable(feature = "ptr_wrapping_offset_from", issue = "none")] + #[rustc_const_unstable(feature = "ptr_wrapping_offset_from", issue = "none")] + #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces + #[cfg(not(bootstrap))] + pub const unsafe fn wrapping_byte_offset_from(self, origin: *const U) -> isize { + // SAFETY: the caller must uphold the safety contract for `wrapping_offset_from`. + unsafe { self.cast::().wrapping_offset_from(origin.cast::()) } + } + /// Returns whether two pointers are guaranteed to be equal. /// /// At runtime this function behaves like `Some(self == other)`. diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs index c6f43857887c9..7cbeb674bd5d9 100644 --- a/library/core/src/ptr/mut_ptr.rs +++ b/library/core/src/ptr/mut_ptr.rs @@ -777,7 +777,9 @@ impl *mut T { /// Calculates the distance between two pointers. The returned value is in /// units of T: the distance in bytes divided by `mem::size_of::()`. /// - /// This function is the inverse of [`offset`]. + /// This function is the inverse of [`offset`]: it is valid to call if and only if + /// `self` could have been computed as `origin.offset(n)` for some `n`, and it will + /// then return that `n`. /// /// [`offset`]: #method.offset-1 /// @@ -816,6 +818,12 @@ impl *mut T { /// (Note that [`offset`] and [`add`] also have a similar limitation and hence cannot be used on /// such large allocations either.) /// + /// The requirement for pointers to be derived from the same allocated object is primarily + /// needed for `const`-compatibility: at compile-time, pointers into *different* allocated + /// object do not have a known distance to each other. However, the requirement also exists at + /// runtime, and may be exploited by optimizations. You can use `(self as usize).sub(origin as + /// usize) / mem::size_of::()` to avoid this requirement. + /// /// [`add`]: #method.add /// [allocated object]: crate::ptr#allocated-object /// @@ -958,6 +966,108 @@ impl *mut T { unsafe { (self as *const T).sub_ptr(origin) } } + /// Calculates the distance between two pointers using wrapping arithmetic. The returned value + /// is in units of T: the distance in bytes divided by `mem::size_of::()`. + /// + /// This function is the inverse of [`wrapping_offset`]: it is valid to call if and only if + /// `self` could have been computed as `origin.wrapping_offset(n)` for some `n`, and it will + /// then return that `n`. + /// + /// [`wrapping_offset`]: #method.wrapping_offset-1 + /// + /// # Safety + /// + /// If any of the following conditions are violated, the result is Undefined + /// Behavior: + /// + /// * Both pointers must be *derived from* a pointer to the same [allocated object]. + /// (See below for an example.) + /// + /// * The distance between the pointers, in bytes, must be an exact multiple + /// of the size of `T`. + /// + /// Unlike [`offset_from`][pointer::offset_from], this method does *not* require the pointers to + /// be in-bounds of the object they are derived from, nor does it impose any restrictions + /// regarding the maximum distance or wrapping around the address space. + /// + /// The requirement for pointers to be derived from the same allocated object is primarily + /// needed for `const`-compatibility: at compile-time, pointers into *different* allocated + /// object do not have a known distance to each other. However, the requirement also exists at + /// runtime, and may be exploited by optimizations. You can use `(self as usize).sub(origin as + /// usize) / mem::size_of::()` to avoid this requirement. + /// + /// [allocated object]: crate::ptr#allocated-object + /// + /// # Panics + /// + /// This function panics if `T` is a Zero-Sized Type ("ZST"). + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// #![feature(ptr_wrapping_offset_from)] + /// let mut a = [0; 2]; + /// let ptr1: *mut i32 = &mut a[1]; + /// let ptr2: *mut i32 = ptr1.wrapping_offset(2); // out-of-bounds! + /// unsafe { + /// assert_eq!(ptr2.wrapping_offset_from(ptr1), 2); + /// assert_eq!(ptr1.wrapping_offset_from(ptr2), -2); + /// assert_eq!(ptr1.wrapping_offset(2), ptr2); + /// assert_eq!(ptr2.wrapping_offset(-2), ptr1); + /// } + /// ``` + /// + /// *Incorrect* usage: + /// + /// ```rust,no_run + /// #![feature(ptr_wrapping_offset_from)] + /// let ptr1 = Box::into_raw(Box::new(0u8)); + /// let ptr2 = Box::into_raw(Box::new(1u8)); + /// let diff = (ptr2 as isize).wrapping_sub(ptr1 as isize); + /// // Make ptr2_other an "alias" of ptr2, but derived from ptr1. + /// let ptr2_other = (ptr1 as *mut u8).wrapping_offset(diff); + /// assert_eq!(ptr2 as usize, ptr2_other as usize); + /// // Since ptr2_other and ptr2 are derived from pointers to different objects, + /// // computing their offset is undefined behavior, even though + /// // they point to the same address! + /// unsafe { + /// let zero = ptr2_other.wrapping_offset_from(ptr2); // Undefined Behavior + /// } + /// ``` + #[unstable(feature = "ptr_wrapping_offset_from", issue = "none")] + #[rustc_const_unstable(feature = "ptr_wrapping_offset_from", issue = "none")] + #[inline] + #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces + #[cfg(not(bootstrap))] + pub const unsafe fn wrapping_offset_from(self, origin: *const T) -> isize + where + T: Sized, + { + // SAFETY: the caller must uphold the safety contract for `ptr_offset_from`. + unsafe { (self as *const T).wrapping_offset_from(origin) } + } + + /// Calculates the distance between two pointers using wrapping arithmetic. The returned value + /// is in units of **bytes**. + /// + /// This is purely a convenience for casting to a `u8` pointer and using + /// [`wrapping_offset_from`][pointer::wrapping_offset_from] on it. See that method for + /// documentation and safety requirements. + /// + /// For non-`Sized` pointees this operation considers only the data pointers, + /// ignoring the metadata. + #[inline(always)] + #[unstable(feature = "ptr_wrapping_offset_from", issue = "none")] + #[rustc_const_unstable(feature = "ptr_wrapping_offset_from", issue = "none")] + #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces + #[cfg(not(bootstrap))] + pub const unsafe fn wrapping_byte_offset_from(self, origin: *const U) -> isize { + // SAFETY: the caller must uphold the safety contract for `wrapping_offset_from`. + unsafe { self.cast::().wrapping_offset_from(origin.cast::()) } + } + /// Calculates the offset from a pointer (convenience for `.offset(count as isize)`). /// /// `count` is in units of T; e.g., a `count` of 3 represents a pointer diff --git a/src/tools/miri/tests/pass/ptr_offset.rs b/src/tools/miri/tests/pass/ptr_offset.rs index 92b275b00327d..b7e8cab20bb2e 100644 --- a/src/tools/miri/tests/pass/ptr_offset.rs +++ b/src/tools/miri/tests/pass/ptr_offset.rs @@ -1,5 +1,5 @@ //@compile-flags: -Zmiri-permissive-provenance -#![feature(ptr_sub_ptr)] +#![feature(ptr_sub_ptr, ptr_wrapping_offset_from)] use std::{mem, ptr}; fn main() { @@ -37,11 +37,16 @@ fn test_offset_from() { assert_eq!(x.offset_from(y), -12); assert_eq!((y as *const u32).offset_from(x as *const u32), 12 / 4); assert_eq!((x as *const u32).offset_from(y as *const u32), -12 / 4); + assert_eq!(y.wrapping_add(12).wrapping_offset_from(x), 24); + assert_eq!(x.wrapping_sub(12).wrapping_offset_from(y), -24); - let x = (((x as usize) * 2) / 2) as *const u8; + let x = (((x as usize) * 2) / 2) as *const u8; // lose track of provenance assert_eq!(y.offset_from(x), 12); assert_eq!(y.sub_ptr(x), 12); assert_eq!(x.offset_from(y), -12); + assert_eq!(y.wrapping_add(12).wrapping_offset_from(x), 24); + //FIXME: this doesn't work yet as exposed ptrs are not handled correctly + //assert_eq!(x.wrapping_sub(12).wrapping_offset_from(y), -24); } } diff --git a/tests/codegen/intrinsics/offset_from.rs b/tests/codegen/intrinsics/offset_from.rs index d0de4c8355d05..d1ee311372fb6 100644 --- a/tests/codegen/intrinsics/offset_from.rs +++ b/tests/codegen/intrinsics/offset_from.rs @@ -34,3 +34,15 @@ pub unsafe fn offset_from_unsigned_odd_size(a: *const RGB, b: *const RGB) -> usi // CHECK-NEXT: ret i64 ptr_offset_from_unsigned(a, b) } + +// CHECK-LABEL: @wrapping_offset_from_odd_size +#[no_mangle] +pub unsafe fn wrapping_offset_from_odd_size(a: *const RGB, b: *const RGB) -> isize { + // CHECK: start + // CHECK-NEXT: ptrtoint + // CHECK-NEXT: ptrtoint + // CHECK-NEXT: sub i64 + // CHECK-NEXT: sdiv exact i64 %{{[0-9]+}}, 3 + // CHECK-NEXT: ret i64 + ptr_wrapping_offset_from(a, b) +} diff --git a/tests/ui/consts/offset_from_ub.rs b/tests/ui/consts/offset_from_ub.rs index 51163e650d6aa..60fc9a88da76b 100644 --- a/tests/ui/consts/offset_from_ub.rs +++ b/tests/ui/consts/offset_from_ub.rs @@ -1,7 +1,7 @@ -#![feature(const_ptr_sub_ptr)] +#![feature(const_ptr_sub_ptr, ptr_wrapping_offset_from)] #![feature(core_intrinsics)] -use std::intrinsics::{ptr_offset_from, ptr_offset_from_unsigned}; +use std::intrinsics::{ptr_offset_from, ptr_offset_from_unsigned, ptr_wrapping_offset_from}; use std::ptr; #[repr(C)] @@ -122,4 +122,49 @@ pub const OFFSET_VERY_FAR2: isize = { //~^ inside }; + +pub const DIFFERENT_ALLOC_WRAPPING: isize = { + let uninit = std::mem::MaybeUninit::::uninit(); + let base_ptr: *const Struct = &uninit as *const _ as *const Struct; + let uninit2 = std::mem::MaybeUninit::::uninit(); + let field_ptr: *const Struct = &uninit2 as *const _ as *const Struct; + unsafe { ptr_wrapping_offset_from(field_ptr, base_ptr) } //~ERROR evaluation of constant value failed + //~| pointers into different allocations +}; + +pub const NOT_MULTIPLE_OF_SIZE_WRAPPING: isize = { + let data = [5u8, 6, 7]; + let base_ptr = data.as_ptr(); + let field_ptr = base_ptr.wrapping_offset(1) as *const u16; + unsafe { ptr_wrapping_offset_from(field_ptr, base_ptr as *const u16) } //~ERROR evaluation of constant value failed + //~| 1_isize cannot be divided by 2_isize without remainder +}; + +pub const OFFSET_FROM_NULL_WRAPPING: isize = { + let ptr = 0 as *const u8; + unsafe { ptr_wrapping_offset_from(ptr, ptr) } +}; + +const OUT_OF_BOUNDS_WRAPPING_: () = { + let start_ptr = &4 as *const _ as *const u8; + let length = 10; + let end_ptr = start_ptr.wrapping_offset(length); + let start_ptr = start_ptr.wrapping_offset(-length); + // Both pointers are out of bounds. + assert!(2*length == unsafe { ptr_wrapping_offset_from(end_ptr, start_ptr) }); + assert!(-2*length == unsafe { ptr_wrapping_offset_from(start_ptr, end_ptr) }); +}; + +pub const FAR_APART_WRAPPING1: isize = { + let ptr1 = ptr::null::(); + let ptr2 = ptr1.wrapping_add(isize::MAX as usize + 42); + unsafe { ptr_wrapping_offset_from(ptr2, ptr1) } +}; +pub const FAR_APART_WRAPPING2: isize = { + let ptr1 = ptr::null::(); + let ptr2 = ptr1.wrapping_add(isize::MAX as usize + 42); + unsafe { ptr_wrapping_offset_from(ptr1, ptr2) } +}; + + fn main() {} diff --git a/tests/ui/consts/offset_from_ub.stderr b/tests/ui/consts/offset_from_ub.stderr index 97ff6efdd791c..b74846c2bd1bd 100644 --- a/tests/ui/consts/offset_from_ub.stderr +++ b/tests/ui/consts/offset_from_ub.stderr @@ -109,6 +109,18 @@ note: inside `OFFSET_VERY_FAR2` LL | unsafe { ptr1.offset_from(ptr2.wrapping_offset(1)) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 15 previous errors +error[E0080]: evaluation of constant value failed + --> $DIR/offset_from_ub.rs:131:14 + | +LL | unsafe { ptr_wrapping_offset_from(field_ptr, base_ptr) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ptr_wrapping_offset_from` called on pointers into different allocations + +error[E0080]: evaluation of constant value failed + --> $DIR/offset_from_ub.rs:139:14 + | +LL | unsafe { ptr_wrapping_offset_from(field_ptr, base_ptr as *const u16) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ exact_div: 1_isize cannot be divided by 2_isize without remainder + +error: aborting due to 17 previous errors For more information about this error, try `rustc --explain E0080`.