about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2020-05-29 21:58:22 +0200
committerGitHub <noreply@github.com>2020-05-29 21:58:22 +0200
commit1cfe0e9c635c41309ec852c5f296f7e77fec6940 (patch)
treef41ae6454e76f77ff5fa2e2fe765033e6a672937
parentb387a1163691a97abb47db0b99dc6073a8076675 (diff)
parent7d5415b5a2faf5b8f76156eedfd66f94b91660a0 (diff)
downloadrust-1cfe0e9c635c41309ec852c5f296f7e77fec6940.tar.gz
rust-1cfe0e9c635c41309ec852c5f296f7e77fec6940.zip
Rollup merge of #71500 - josephlr:offset, r=oli-obk,RalfJung
Make pointer offset methods/intrinsics const

Implements #71499 using [the implementations from miri](https://github.com/rust-lang/miri/blob/52f5d202bdcfe8986f0615845f8d1647ab8a2c6a/src/shims/intrinsics.rs#L96-L112).

I added some tests what's allowed and what's UB. Let me know if any other cases should be added.

CC: @RalfJung @oli-obk
-rw-r--r--src/libcore/intrinsics.rs2
-rw-r--r--src/libcore/lib.rs1
-rw-r--r--src/libcore/ptr/const_ptr.rs18
-rw-r--r--src/libcore/ptr/mut_ptr.rs18
-rw-r--r--src/librustc_middle/mir/interpret/mod.rs9
-rw-r--r--src/librustc_middle/mir/interpret/pointer.rs26
-rw-r--r--src/librustc_mir/interpret/intrinsics.rs57
-rw-r--r--src/librustc_span/symbol.rs2
-rw-r--r--src/test/ui/consts/miri_unleashed/ptr_arith.rs10
-rw-r--r--src/test/ui/consts/miri_unleashed/ptr_arith.stderr21
-rw-r--r--src/test/ui/consts/offset.rs115
-rw-r--r--src/test/ui/consts/offset_ub.rs25
-rw-r--r--src/test/ui/consts/offset_ub.stderr169
13 files changed, 424 insertions, 49 deletions
diff --git a/src/libcore/intrinsics.rs b/src/libcore/intrinsics.rs
index 9006e4cfaf7..2d97fecf8a7 100644
--- a/src/libcore/intrinsics.rs
+++ b/src/libcore/intrinsics.rs
@@ -1314,6 +1314,7 @@ extern "rust-intrinsic" {
     /// The stabilized version of this intrinsic is
     /// [`std::pointer::offset`](../../std/primitive.pointer.html#method.offset).
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     pub fn offset<T>(dst: *const T, offset: isize) -> *const T;
 
     /// Calculates the offset from a pointer, potentially wrapping.
@@ -1331,6 +1332,7 @@ extern "rust-intrinsic" {
     /// The stabilized version of this intrinsic is
     /// [`std::pointer::wrapping_offset`](../../std/primitive.pointer.html#method.wrapping_offset).
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     pub fn arith_offset<T>(dst: *const T, offset: isize) -> *const T;
 
     /// Equivalent to the appropriate `llvm.memcpy.p0i8.0i8.*` intrinsic, with
diff --git a/src/libcore/lib.rs b/src/libcore/lib.rs
index ca13433caec..7d21f9a9a66 100644
--- a/src/libcore/lib.rs
+++ b/src/libcore/lib.rs
@@ -85,6 +85,7 @@
 #![feature(const_panic)]
 #![feature(const_fn_union)]
 #![feature(const_generics)]
+#![feature(const_ptr_offset)]
 #![feature(const_ptr_offset_from)]
 #![feature(const_result)]
 #![feature(const_slice_from_raw_parts)]
diff --git a/src/libcore/ptr/const_ptr.rs b/src/libcore/ptr/const_ptr.rs
index 85ba5fc0638..835183d171a 100644
--- a/src/libcore/ptr/const_ptr.rs
+++ b/src/libcore/ptr/const_ptr.rs
@@ -151,8 +151,9 @@ impl<T: ?Sized> *const T {
     /// ```
     #[stable(feature = "rust1", since = "1.0.0")]
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     #[inline]
-    pub unsafe fn offset(self, count: isize) -> *const T
+    pub const unsafe fn offset(self, count: isize) -> *const T
     where
         T: Sized,
     {
@@ -210,8 +211,9 @@ impl<T: ?Sized> *const T {
     /// ```
     #[stable(feature = "ptr_wrapping_offset", since = "1.16.0")]
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     #[inline]
-    pub fn wrapping_offset(self, count: isize) -> *const T
+    pub const fn wrapping_offset(self, count: isize) -> *const T
     where
         T: Sized,
     {
@@ -393,8 +395,9 @@ impl<T: ?Sized> *const T {
     /// ```
     #[stable(feature = "pointer_methods", since = "1.26.0")]
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     #[inline]
-    pub unsafe fn add(self, count: usize) -> Self
+    pub const unsafe fn add(self, count: usize) -> Self
     where
         T: Sized,
     {
@@ -455,8 +458,9 @@ impl<T: ?Sized> *const T {
     /// ```
     #[stable(feature = "pointer_methods", since = "1.26.0")]
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     #[inline]
-    pub unsafe fn sub(self, count: usize) -> Self
+    pub const unsafe fn sub(self, count: usize) -> Self
     where
         T: Sized,
     {
@@ -511,8 +515,9 @@ impl<T: ?Sized> *const T {
     /// ```
     #[stable(feature = "pointer_methods", since = "1.26.0")]
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     #[inline]
-    pub fn wrapping_add(self, count: usize) -> Self
+    pub const fn wrapping_add(self, count: usize) -> Self
     where
         T: Sized,
     {
@@ -567,8 +572,9 @@ impl<T: ?Sized> *const T {
     /// ```
     #[stable(feature = "pointer_methods", since = "1.26.0")]
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     #[inline]
-    pub fn wrapping_sub(self, count: usize) -> Self
+    pub const fn wrapping_sub(self, count: usize) -> Self
     where
         T: Sized,
     {
diff --git a/src/libcore/ptr/mut_ptr.rs b/src/libcore/ptr/mut_ptr.rs
index 0781d7e6cac..40b5e4e2234 100644
--- a/src/libcore/ptr/mut_ptr.rs
+++ b/src/libcore/ptr/mut_ptr.rs
@@ -145,8 +145,9 @@ impl<T: ?Sized> *mut T {
     /// ```
     #[stable(feature = "rust1", since = "1.0.0")]
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     #[inline]
-    pub unsafe fn offset(self, count: isize) -> *mut T
+    pub const unsafe fn offset(self, count: isize) -> *mut T
     where
         T: Sized,
     {
@@ -203,8 +204,9 @@ impl<T: ?Sized> *mut T {
     /// ```
     #[stable(feature = "ptr_wrapping_offset", since = "1.16.0")]
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     #[inline]
-    pub fn wrapping_offset(self, count: isize) -> *mut T
+    pub const fn wrapping_offset(self, count: isize) -> *mut T
     where
         T: Sized,
     {
@@ -439,8 +441,9 @@ impl<T: ?Sized> *mut T {
     /// ```
     #[stable(feature = "pointer_methods", since = "1.26.0")]
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     #[inline]
-    pub unsafe fn add(self, count: usize) -> Self
+    pub const unsafe fn add(self, count: usize) -> Self
     where
         T: Sized,
     {
@@ -501,8 +504,9 @@ impl<T: ?Sized> *mut T {
     /// ```
     #[stable(feature = "pointer_methods", since = "1.26.0")]
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     #[inline]
-    pub unsafe fn sub(self, count: usize) -> Self
+    pub const unsafe fn sub(self, count: usize) -> Self
     where
         T: Sized,
     {
@@ -557,8 +561,9 @@ impl<T: ?Sized> *mut T {
     /// ```
     #[stable(feature = "pointer_methods", since = "1.26.0")]
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     #[inline]
-    pub fn wrapping_add(self, count: usize) -> Self
+    pub const fn wrapping_add(self, count: usize) -> Self
     where
         T: Sized,
     {
@@ -613,8 +618,9 @@ impl<T: ?Sized> *mut T {
     /// ```
     #[stable(feature = "pointer_methods", since = "1.26.0")]
     #[must_use = "returns a new pointer rather than modifying its argument"]
+    #[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
     #[inline]
-    pub fn wrapping_sub(self, count: usize) -> Self
+    pub const fn wrapping_sub(self, count: usize) -> Self
     where
         T: Sized,
     {
diff --git a/src/librustc_middle/mir/interpret/mod.rs b/src/librustc_middle/mir/interpret/mod.rs
index d9e52af8900..061bc9750e1 100644
--- a/src/librustc_middle/mir/interpret/mod.rs
+++ b/src/librustc_middle/mir/interpret/mod.rs
@@ -598,3 +598,12 @@ pub fn truncate(value: u128, size: Size) -> u128 {
     // Truncate (shift left to drop out leftover values, shift right to fill with zeroes).
     (value << shift) >> shift
 }
+
+/// Computes the unsigned absolute value without wrapping or panicking.
+#[inline]
+pub fn uabs(value: i64) -> u64 {
+    // The only tricky part here is if value == i64::MIN. In that case,
+    // wrapping_abs() returns i64::MIN == -2^63. Casting this value to a u64
+    // gives 2^63, the correct value.
+    value.wrapping_abs() as u64
+}
diff --git a/src/librustc_middle/mir/interpret/pointer.rs b/src/librustc_middle/mir/interpret/pointer.rs
index 70cc546199b..ccad4f0a135 100644
--- a/src/librustc_middle/mir/interpret/pointer.rs
+++ b/src/librustc_middle/mir/interpret/pointer.rs
@@ -1,4 +1,4 @@
-use super::{AllocId, InterpResult};
+use super::{uabs, AllocId, InterpResult};
 
 use rustc_macros::HashStable;
 use rustc_target::abi::{HasDataLayout, Size};
@@ -25,6 +25,12 @@ pub trait PointerArithmetic: HasDataLayout {
     }
 
     #[inline]
+    fn machine_isize_min(&self) -> i64 {
+        let max_isize_plus_1 = 1i128 << (self.pointer_size().bits() - 1);
+        i64::try_from(-max_isize_plus_1).unwrap()
+    }
+
+    #[inline]
     fn machine_isize_max(&self) -> i64 {
         let max_isize_plus_1 = 1u128 << (self.pointer_size().bits() - 1);
         i64::try_from(max_isize_plus_1 - 1).unwrap()
@@ -42,21 +48,23 @@ pub trait PointerArithmetic: HasDataLayout {
 
     #[inline]
     fn overflowing_offset(&self, val: u64, i: u64) -> (u64, bool) {
+        // We do not need to check if i fits in a machine usize. If it doesn't,
+        // either the wrapping_add will wrap or res will not fit in a pointer.
         let res = val.overflowing_add(i);
         self.truncate_to_ptr(res)
     }
 
     #[inline]
     fn overflowing_signed_offset(&self, val: u64, i: i64) -> (u64, bool) {
-        if i < 0 {
-            // Trickery to ensure that `i64::MIN` works fine: compute `n = -i`.
-            // This formula only works for true negative values; it overflows for zero!
-            let n = u64::MAX - (i as u64) + 1;
-            let res = val.overflowing_sub(n);
-            self.truncate_to_ptr(res)
+        // We need to make sure that i fits in a machine isize.
+        let n = uabs(i);
+        if i >= 0 {
+            let (val, over) = self.overflowing_offset(val, n);
+            (val, over || i > self.machine_isize_max())
         } else {
-            // `i >= 0`, so the cast is safe.
-            self.overflowing_offset(val, i as u64)
+            let res = val.overflowing_sub(n);
+            let (val, over) = self.truncate_to_ptr(res);
+            (val, over || i < self.machine_isize_min())
         }
     }
 
diff --git a/src/librustc_mir/interpret/intrinsics.rs b/src/librustc_mir/interpret/intrinsics.rs
index fc4be82ad90..55f254f5732 100644
--- a/src/librustc_mir/interpret/intrinsics.rs
+++ b/src/librustc_mir/interpret/intrinsics.rs
@@ -2,19 +2,21 @@
 //! looking at their MIR. Intrinsics/functions supported here are shared by CTFE
 //! and miri.
 
+use std::convert::TryFrom;
+
 use rustc_hir::def_id::DefId;
 use rustc_middle::mir::{
     self,
-    interpret::{ConstValue, GlobalId, InterpResult, Scalar},
+    interpret::{uabs, ConstValue, GlobalId, InterpResult, Scalar},
     BinOp,
 };
 use rustc_middle::ty;
 use rustc_middle::ty::subst::SubstsRef;
-use rustc_middle::ty::TyCtxt;
+use rustc_middle::ty::{Ty, TyCtxt};
 use rustc_span::symbol::{sym, Symbol};
 use rustc_target::abi::{Abi, LayoutOf as _, Primitive, Size};
 
-use super::{ImmTy, InterpCx, Machine, OpTy, PlaceTy};
+use super::{CheckInAllocMsg, ImmTy, InterpCx, Machine, OpTy, PlaceTy};
 
 mod caller_location;
 mod type_name;
@@ -279,7 +281,24 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let result = Scalar::from_uint(truncated_bits, layout.size);
                 self.write_scalar(result, dest)?;
             }
+            sym::offset => {
+                let ptr = self.read_scalar(args[0])?.not_undef()?;
+                let offset_count = self.read_scalar(args[1])?.to_machine_isize(self)?;
+                let pointee_ty = substs.type_at(0);
+
+                let offset_ptr = self.ptr_offset_inbounds(ptr, pointee_ty, offset_count)?;
+                self.write_scalar(offset_ptr, dest)?;
+            }
+            sym::arith_offset => {
+                let ptr = self.read_scalar(args[0])?.not_undef()?;
+                let offset_count = self.read_scalar(args[1])?.to_machine_isize(self)?;
+                let pointee_ty = substs.type_at(0);
 
+                let pointee_size = i64::try_from(self.layout_of(pointee_ty)?.size.bytes()).unwrap();
+                let offset_bytes = offset_count.wrapping_mul(pointee_size);
+                let offset_ptr = ptr.ptr_wrapping_signed_offset(offset_bytes, self);
+                self.write_scalar(offset_ptr, dest)?;
+            }
             sym::ptr_offset_from => {
                 let a = self.read_immediate(args[0])?.to_scalar()?;
                 let b = self.read_immediate(args[1])?.to_scalar()?;
@@ -409,4 +428,36 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
         // `Rem` says this is all right, so we can let `Div` do its job.
         self.binop_ignore_overflow(BinOp::Div, a, b, dest)
     }
+
+    /// Offsets a pointer by some multiple of its type, returning an error if the pointer leaves its
+    /// allocation. For integer pointers, we consider each of them their own tiny allocation of size
+    /// 0, so offset-by-0 (and only 0) is okay -- except that NULL cannot be offset by _any_ value.
+    pub fn ptr_offset_inbounds(
+        &self,
+        ptr: Scalar<M::PointerTag>,
+        pointee_ty: Ty<'tcx>,
+        offset_count: i64,
+    ) -> InterpResult<'tcx, Scalar<M::PointerTag>> {
+        // We cannot overflow i64 as a type's size must be <= isize::MAX.
+        let pointee_size = i64::try_from(self.layout_of(pointee_ty)?.size.bytes()).unwrap();
+        // The computed offset, in bytes, cannot overflow an isize.
+        let offset_bytes =
+            offset_count.checked_mul(pointee_size).ok_or(err_ub!(PointerArithOverflow))?;
+        // The offset being in bounds cannot rely on "wrapping around" the address space.
+        // So, first rule out overflows in the pointer arithmetic.
+        let offset_ptr = ptr.ptr_signed_offset(offset_bytes, self)?;
+        // ptr and offset_ptr must be in bounds of the same allocated object. This means all of the
+        // memory between these pointers must be accessible. Note that we do not require the
+        // pointers to be properly aligned (unlike a read/write operation).
+        let min_ptr = if offset_bytes >= 0 { ptr } else { offset_ptr };
+        let size: u64 = uabs(offset_bytes);
+        // This call handles checking for integer/NULL pointers.
+        self.memory.check_ptr_access_align(
+            min_ptr,
+            Size::from_bytes(size),
+            None,
+            CheckInAllocMsg::InboundsTest,
+        )?;
+        Ok(offset_ptr)
+    }
 }
diff --git a/src/librustc_span/symbol.rs b/src/librustc_span/symbol.rs
index 6a6098710e8..9b055beb99d 100644
--- a/src/librustc_span/symbol.rs
+++ b/src/librustc_span/symbol.rs
@@ -147,6 +147,7 @@ symbols! {
         Arc,
         Arguments,
         ArgumentV1,
+        arith_offset,
         arm_target_feature,
         asm,
         assert,
@@ -516,6 +517,7 @@ symbols! {
         not,
         note,
         object_safe_for_dispatch,
+        offset,
         Ok,
         omit_gdb_pretty_printer_section,
         on,
diff --git a/src/test/ui/consts/miri_unleashed/ptr_arith.rs b/src/test/ui/consts/miri_unleashed/ptr_arith.rs
index 81985f9f625..65fc49c0b27 100644
--- a/src/test/ui/consts/miri_unleashed/ptr_arith.rs
+++ b/src/test/ui/consts/miri_unleashed/ptr_arith.rs
@@ -2,8 +2,7 @@
 #![feature(core_intrinsics)]
 #![allow(const_err)]
 
-// A test demonstrating that we prevent doing even trivial
-// pointer arithmetic or comparison during CTFE.
+// During CTFE, we prevent pointer comparison and pointer-to-int casts.
 
 static CMP: () = {
     let x = &0 as *const _;
@@ -19,11 +18,4 @@ static INT_PTR_ARITH: () = unsafe {
     //~| NOTE pointer-to-integer cast
 };
 
-static PTR_ARITH: () = unsafe {
-    let x = &0 as *const _;
-    let _v = core::intrinsics::offset(x, 0);
-    //~^ ERROR could not evaluate static initializer
-    //~| NOTE calling intrinsic `offset`
-};
-
 fn main() {}
diff --git a/src/test/ui/consts/miri_unleashed/ptr_arith.stderr b/src/test/ui/consts/miri_unleashed/ptr_arith.stderr
index 5bd534a16b8..805ba9c6b03 100644
--- a/src/test/ui/consts/miri_unleashed/ptr_arith.stderr
+++ b/src/test/ui/consts/miri_unleashed/ptr_arith.stderr
@@ -1,39 +1,28 @@
 error[E0080]: could not evaluate static initializer
-  --> $DIR/ptr_arith.rs:10:14
+  --> $DIR/ptr_arith.rs:9:14
    |
 LL |     let _v = x == x;
    |              ^^^^^^ "pointer arithmetic or comparison" needs an rfc before being allowed inside constants
 
 error[E0080]: could not evaluate static initializer
-  --> $DIR/ptr_arith.rs:17:14
+  --> $DIR/ptr_arith.rs:16:14
    |
 LL |     let _v = x + 0;
    |              ^^^^^ "pointer-to-integer cast" needs an rfc before being allowed inside constants
 
-error[E0080]: could not evaluate static initializer
-  --> $DIR/ptr_arith.rs:24:14
-   |
-LL |     let _v = core::intrinsics::offset(x, 0);
-   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ "calling intrinsic `offset`" needs an rfc before being allowed inside constants
-
 warning: skipping const checks
    |
 help: skipping check for `const_compare_raw_pointers` feature
-  --> $DIR/ptr_arith.rs:10:14
+  --> $DIR/ptr_arith.rs:9:14
    |
 LL |     let _v = x == x;
    |              ^^^^^^
 help: skipping check that does not even have a feature gate
-  --> $DIR/ptr_arith.rs:16:20
+  --> $DIR/ptr_arith.rs:15:20
    |
 LL |     let x: usize = std::mem::transmute(&0);
    |                    ^^^^^^^^^^^^^^^^^^^^^^^
-help: skipping check that does not even have a feature gate
-  --> $DIR/ptr_arith.rs:24:14
-   |
-LL |     let _v = core::intrinsics::offset(x, 0);
-   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: aborting due to 3 previous errors; 1 warning emitted
+error: aborting due to 2 previous errors; 1 warning emitted
 
 For more information about this error, try `rustc --explain E0080`.
diff --git a/src/test/ui/consts/offset.rs b/src/test/ui/consts/offset.rs
new file mode 100644
index 00000000000..f64242d568e
--- /dev/null
+++ b/src/test/ui/consts/offset.rs
@@ -0,0 +1,115 @@
+// run-pass
+#![feature(const_ptr_offset)]
+#![feature(const_ptr_offset_from)]
+#![feature(ptr_offset_from)]
+use std::ptr;
+
+#[repr(C)]
+struct Struct {
+    a: u32,
+    b: u32,
+    c: u32,
+}
+static S: Struct = Struct { a: 0, b: 0, c: 0 };
+
+// For these tests we use offset_from to check that two pointers are equal.
+// Rust doesn't currently support comparing pointers in const fn.
+
+static OFFSET_NO_CHANGE: bool = unsafe {
+    let p1 = &S.b as *const u32;
+    let p2 = p1.offset(2).offset(-2);
+    p1.offset_from(p2) == 0
+};
+static OFFSET_MIDDLE: bool = unsafe {
+    let p1 = (&S.a as *const u32).offset(1);
+    let p2 = (&S.c as *const u32).offset(-1);
+    p1.offset_from(p2) == 0
+};
+// Pointing to the end of the allocation is OK
+static OFFSET_END: bool = unsafe {
+    let p1 = (&S.a as *const u32).offset(3);
+    let p2 = (&S.c as *const u32).offset(1);
+    p1.offset_from(p2) == 0
+};
+// Casting though a differently sized type is OK
+static OFFSET_U8_PTR: bool = unsafe {
+    let p1 = (&S.a as *const u32 as *const u8).offset(5);
+    let p2 = (&S.c as *const u32 as *const u8).offset(-3);
+    p1.offset_from(p2) == 0
+};
+// Any offset with a ZST does nothing
+const OFFSET_ZST: bool = unsafe {
+    let pz = &() as *const ();
+    // offset_from can't work with ZSTs, so cast to u8 ptr
+    let p1 = pz.offset(5) as *const u8;
+    let p2 = pz.offset(isize::MIN) as *const u8;
+    p1.offset_from(p2) == 0
+};
+const OFFSET_ZERO: bool = unsafe {
+    let p = [0u8; 0].as_ptr();
+    p.offset(0).offset_from(p) == 0
+};
+const OFFSET_ONE: bool = unsafe {
+    let p = &42u32 as *const u32;
+    p.offset(1).offset_from(p) == 1
+};
+const OFFSET_DANGLING: bool = unsafe {
+    let p = ptr::NonNull::<u8>::dangling().as_ptr();
+    p.offset(0).offset_from(p) == 0
+};
+const OFFSET_UNALIGNED: bool = unsafe {
+    let arr = [0u8; 32];
+    let p1 = arr.as_ptr();
+    let p2 = (p1.offset(2) as *const u32).offset(1);
+    (p2 as *const u8).offset_from(p1) == 6
+};
+
+const WRAP_OFFSET_NO_CHANGE: bool = unsafe {
+    let p1 = &42u32 as *const u32;
+    let p2 = p1.wrapping_offset(1000).wrapping_offset(-1000);
+    let p3 = p1.wrapping_offset(-1000).wrapping_offset(1000);
+    (p1.offset_from(p2) == 0) & (p1.offset_from(p3) == 0)
+};
+const WRAP_ADDRESS_SPACE: bool = unsafe {
+    let p1 = &42u8 as *const u8;
+    let p2 = p1.wrapping_offset(isize::MIN).wrapping_offset(isize::MIN);
+    p1.offset_from(p2) == 0
+};
+// Wrap on the count*size_of::<T>() calculation.
+const WRAP_SIZE_OF: bool = unsafe {
+    // Make sure that if p1 moves backwards, we are still in range
+    let arr = [0u32; 2];
+    let p = &arr[1] as *const u32;
+    // With wrapping arithmetic, isize::MAX * 4 == -4
+    let wrapped = p.wrapping_offset(isize::MAX);
+    let backward = p.wrapping_offset(-1);
+    wrapped.offset_from(backward) == 0
+};
+const WRAP_INTEGER_POINTER: bool = unsafe {
+    let p1 = (0x42 as *const u32).wrapping_offset(4);
+    let p2 = 0x52 as *const u32;
+    p1.offset_from(p2) == 0
+};
+const WRAP_NULL: bool = unsafe {
+    let p1 = ptr::null::<u32>().wrapping_offset(1);
+    let p2 = 0x4 as *const u32;
+    p1.offset_from(p2) == 0
+};
+
+fn main() {
+    assert!(OFFSET_NO_CHANGE);
+    assert!(OFFSET_MIDDLE);
+    assert!(OFFSET_END);
+    assert!(OFFSET_U8_PTR);
+    assert!(OFFSET_ZST);
+    assert!(OFFSET_ZERO);
+    assert!(OFFSET_ONE);
+    assert!(OFFSET_DANGLING);
+    assert!(OFFSET_UNALIGNED);
+
+    assert!(WRAP_OFFSET_NO_CHANGE);
+    assert!(WRAP_ADDRESS_SPACE);
+    assert!(WRAP_SIZE_OF);
+    assert!(WRAP_INTEGER_POINTER);
+    assert!(WRAP_NULL);
+}
diff --git a/src/test/ui/consts/offset_ub.rs b/src/test/ui/consts/offset_ub.rs
new file mode 100644
index 00000000000..4f943ed9ad1
--- /dev/null
+++ b/src/test/ui/consts/offset_ub.rs
@@ -0,0 +1,25 @@
+// ignore-tidy-linelength
+#![feature(const_ptr_offset)]
+use std::ptr;
+
+// normalize-stderr-test "alloc\d+" -> "allocN"
+
+pub const BEFORE_START: *const u8 = unsafe { (&0u8 as *const u8).offset(-1) }; //~NOTE
+pub const AFTER_END: *const u8 = unsafe { (&0u8 as *const u8).offset(2) }; //~NOTE
+pub const AFTER_ARRAY: *const u8 = unsafe { [0u8; 100].as_ptr().offset(101) }; //~NOTE
+
+pub const OVERFLOW: *const u16 = unsafe { [0u16; 1].as_ptr().offset(isize::MAX) }; //~NOTE
+pub const UNDERFLOW: *const u16 = unsafe { [0u16; 1].as_ptr().offset(isize::MIN) }; //~NOTE
+pub const OVERFLOW_ADDRESS_SPACE: *const u8 = unsafe { (usize::MAX as *const u8).offset(2) }; //~NOTE
+pub const UNDERFLOW_ADDRESS_SPACE: *const u8 = unsafe { (1 as *const u8).offset(-2) }; //~NOTE
+
+pub const ZERO_SIZED_ALLOC: *const u8 = unsafe { [0u8; 0].as_ptr().offset(1) }; //~NOTE
+pub const DANGLING: *const u8 = unsafe { ptr::NonNull::<u8>::dangling().as_ptr().offset(4) }; //~NOTE
+
+// Right now, a zero offset from null is UB
+pub const NULL_OFFSET_ZERO: *const u8 = unsafe { ptr::null::<u8>().offset(0) }; //~NOTE
+
+// Make sure that we don't panic when computing abs(offset*size_of::<T>())
+pub const UNDERFLOW_ABS: *const u8 = unsafe { (usize::MAX as *const u8).offset(isize::MIN) }; //~NOTE
+
+fn main() {}
diff --git a/src/test/ui/consts/offset_ub.stderr b/src/test/ui/consts/offset_ub.stderr
new file mode 100644
index 00000000000..0ab81cc0c5b
--- /dev/null
+++ b/src/test/ui/consts/offset_ub.stderr
@@ -0,0 +1,169 @@
+error: any use of this value will cause an error
+  --> $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |
+LL |         intrinsics::offset(self, count)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         overflowing in-bounds pointer arithmetic
+   |         inside `std::ptr::const_ptr::<impl *const u8>::offset` at $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |         inside `BEFORE_START` at $DIR/offset_ub.rs:7:46
+   | 
+  ::: $DIR/offset_ub.rs:7:1
+   |
+LL | pub const BEFORE_START: *const u8 = unsafe { (&0u8 as *const u8).offset(-1) };
+   | ------------------------------------------------------------------------------
+   |
+   = note: `#[deny(const_err)]` on by default
+
+error: any use of this value will cause an error
+  --> $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |
+LL |         intrinsics::offset(self, count)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         inbounds test failed: pointer must be in-bounds at offset 2, but is outside bounds of allocN which has size 1
+   |         inside `std::ptr::const_ptr::<impl *const u8>::offset` at $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |         inside `AFTER_END` at $DIR/offset_ub.rs:8:43
+   | 
+  ::: $DIR/offset_ub.rs:8:1
+   |
+LL | pub const AFTER_END: *const u8 = unsafe { (&0u8 as *const u8).offset(2) };
+   | --------------------------------------------------------------------------
+
+error: any use of this value will cause an error
+  --> $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |
+LL |         intrinsics::offset(self, count)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         inbounds test failed: pointer must be in-bounds at offset 101, but is outside bounds of allocN which has size 100
+   |         inside `std::ptr::const_ptr::<impl *const u8>::offset` at $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |         inside `AFTER_ARRAY` at $DIR/offset_ub.rs:9:45
+   | 
+  ::: $DIR/offset_ub.rs:9:1
+   |
+LL | pub const AFTER_ARRAY: *const u8 = unsafe { [0u8; 100].as_ptr().offset(101) };
+   | ------------------------------------------------------------------------------
+
+error: any use of this value will cause an error
+  --> $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |
+LL |         intrinsics::offset(self, count)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         overflowing in-bounds pointer arithmetic
+   |         inside `std::ptr::const_ptr::<impl *const u16>::offset` at $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |         inside `OVERFLOW` at $DIR/offset_ub.rs:11:43
+   | 
+  ::: $DIR/offset_ub.rs:11:1
+   |
+LL | pub const OVERFLOW: *const u16 = unsafe { [0u16; 1].as_ptr().offset(isize::MAX) };
+   | ----------------------------------------------------------------------------------
+
+error: any use of this value will cause an error
+  --> $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |
+LL |         intrinsics::offset(self, count)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         overflowing in-bounds pointer arithmetic
+   |         inside `std::ptr::const_ptr::<impl *const u16>::offset` at $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |         inside `UNDERFLOW` at $DIR/offset_ub.rs:12:44
+   | 
+  ::: $DIR/offset_ub.rs:12:1
+   |
+LL | pub const UNDERFLOW: *const u16 = unsafe { [0u16; 1].as_ptr().offset(isize::MIN) };
+   | -----------------------------------------------------------------------------------
+
+error: any use of this value will cause an error
+  --> $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |
+LL |         intrinsics::offset(self, count)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         overflowing in-bounds pointer arithmetic
+   |         inside `std::ptr::const_ptr::<impl *const u8>::offset` at $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |         inside `OVERFLOW_ADDRESS_SPACE` at $DIR/offset_ub.rs:13:56
+   | 
+  ::: $DIR/offset_ub.rs:13:1
+   |
+LL | pub const OVERFLOW_ADDRESS_SPACE: *const u8 = unsafe { (usize::MAX as *const u8).offset(2) };
+   | ---------------------------------------------------------------------------------------------
+
+error: any use of this value will cause an error
+  --> $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |
+LL |         intrinsics::offset(self, count)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         overflowing in-bounds pointer arithmetic
+   |         inside `std::ptr::const_ptr::<impl *const u8>::offset` at $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |         inside `UNDERFLOW_ADDRESS_SPACE` at $DIR/offset_ub.rs:14:57
+   | 
+  ::: $DIR/offset_ub.rs:14:1
+   |
+LL | pub const UNDERFLOW_ADDRESS_SPACE: *const u8 = unsafe { (1 as *const u8).offset(-2) };
+   | --------------------------------------------------------------------------------------
+
+error: any use of this value will cause an error
+  --> $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |
+LL |         intrinsics::offset(self, count)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         inbounds test failed: pointer must be in-bounds at offset 1, but is outside bounds of allocN which has size 0
+   |         inside `std::ptr::const_ptr::<impl *const u8>::offset` at $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |         inside `ZERO_SIZED_ALLOC` at $DIR/offset_ub.rs:16:50
+   | 
+  ::: $DIR/offset_ub.rs:16:1
+   |
+LL | pub const ZERO_SIZED_ALLOC: *const u8 = unsafe { [0u8; 0].as_ptr().offset(1) };
+   | -------------------------------------------------------------------------------
+
+error: any use of this value will cause an error
+  --> $SRC_DIR/libcore/ptr/mut_ptr.rs:LL:COL
+   |
+LL |         intrinsics::offset(self, count) as *mut T
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         unable to turn bytes into a pointer
+   |         inside `std::ptr::mut_ptr::<impl *mut u8>::offset` at $SRC_DIR/libcore/ptr/mut_ptr.rs:LL:COL
+   |         inside `DANGLING` at $DIR/offset_ub.rs:17:42
+   | 
+  ::: $DIR/offset_ub.rs:17:1
+   |
+LL | pub const DANGLING: *const u8 = unsafe { ptr::NonNull::<u8>::dangling().as_ptr().offset(4) };
+   | ---------------------------------------------------------------------------------------------
+
+error: any use of this value will cause an error
+  --> $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |
+LL |         intrinsics::offset(self, count)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         inbounds test failed: 0x0 is not a valid pointer
+   |         inside `std::ptr::const_ptr::<impl *const u8>::offset` at $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |         inside `NULL_OFFSET_ZERO` at $DIR/offset_ub.rs:20:50
+   | 
+  ::: $DIR/offset_ub.rs:20:1
+   |
+LL | pub const NULL_OFFSET_ZERO: *const u8 = unsafe { ptr::null::<u8>().offset(0) };
+   | -------------------------------------------------------------------------------
+
+error: any use of this value will cause an error
+  --> $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |
+LL |         intrinsics::offset(self, count)
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |         |
+   |         unable to turn bytes into a pointer
+   |         inside `std::ptr::const_ptr::<impl *const u8>::offset` at $SRC_DIR/libcore/ptr/const_ptr.rs:LL:COL
+   |         inside `UNDERFLOW_ABS` at $DIR/offset_ub.rs:23:47
+   | 
+  ::: $DIR/offset_ub.rs:23:1
+   |
+LL | pub const UNDERFLOW_ABS: *const u8 = unsafe { (usize::MAX as *const u8).offset(isize::MIN) };
+   | ---------------------------------------------------------------------------------------------
+
+error: aborting due to 11 previous errors
+