about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2025-09-08 10:40:18 +0200
committerRalf Jung <post@ralfj.de>2025-09-08 13:22:52 +0200
commitaed0ed4c93d661fc7b66dc4a39690948476e8a4a (patch)
treedefedf24675aa706b949f66950d41ac2ffa1fbc0
parentebdf2abea4b46fcbba959eee1207c979c77dc95c (diff)
downloadrust-aed0ed4c93d661fc7b66dc4a39690948476e8a4a.tar.gz
rust-aed0ed4c93d661fc7b66dc4a39690948476e8a4a.zip
const-eval: disable pointer fragment support
-rw-r--r--compiler/rustc_const_eval/src/interpret/memory.rs6
-rw-r--r--compiler/rustc_middle/src/mir/interpret/allocation.rs5
-rw-r--r--compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs16
-rw-r--r--library/core/src/ptr/mod.rs38
-rw-r--r--library/coretests/tests/ptr.rs9
-rw-r--r--tests/ui/consts/const-eval/ptr_fragments.rs1
-rw-r--r--tests/ui/consts/const-eval/ptr_fragments_in_final.rs1
-rw-r--r--tests/ui/consts/const-eval/ptr_fragments_mixed.rs28
-rw-r--r--tests/ui/consts/const-eval/ptr_fragments_mixed.stderr23
-rw-r--r--tests/ui/consts/const-eval/read_partial_ptr.rs1
10 files changed, 119 insertions, 9 deletions
diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs
index 2c1e5087e1c..6ec85648d6d 100644
--- a/compiler/rustc_const_eval/src/interpret/memory.rs
+++ b/compiler/rustc_const_eval/src/interpret/memory.rs
@@ -1501,8 +1501,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         // `get_bytes_mut` will clear the provenance, which is correct,
         // since we don't want to keep any provenance at the target.
         // This will also error if copying partial provenance is not supported.
-        let provenance =
-            src_alloc.provenance().prepare_copy(src_range, dest_offset, num_copies, self);
+        let provenance = src_alloc
+            .provenance()
+            .prepare_copy(src_range, dest_offset, num_copies, self)
+            .map_err(|e| e.to_interp_error(src_alloc_id))?;
         // Prepare a copy of the initialization mask.
         let init = src_alloc.init_mask().prepare_copy(src_range);
 
diff --git a/compiler/rustc_middle/src/mir/interpret/allocation.rs b/compiler/rustc_middle/src/mir/interpret/allocation.rs
index 2ea92a39d48..67962813ae4 100644
--- a/compiler/rustc_middle/src/mir/interpret/allocation.rs
+++ b/compiler/rustc_middle/src/mir/interpret/allocation.rs
@@ -724,6 +724,11 @@ impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes>
             }
             // If we get here, we have to check per-byte provenance, and join them together.
             let prov = 'prov: {
+                if !Prov::OFFSET_IS_ADDR {
+                    // FIXME(#146291): We need to ensure that we don't mix different pointers with
+                    // the same provenance.
+                    return Err(AllocError::ReadPartialPointer(range.start));
+                }
                 // Initialize with first fragment. Must have index 0.
                 let Some((mut joint_prov, 0)) = self.provenance.get_byte(range.start, cx) else {
                     break 'prov None;
diff --git a/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs b/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs
index dbbd95408c8..720e58d7aa0 100644
--- a/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs
+++ b/compiler/rustc_middle/src/mir/interpret/allocation/provenance_map.rs
@@ -11,6 +11,7 @@ use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
 use tracing::trace;
 
 use super::{AllocRange, CtfeProvenance, Provenance, alloc_range};
+use crate::mir::interpret::{AllocError, AllocResult};
 
 /// Stores the provenance information of pointers stored in memory.
 #[derive(Clone, PartialEq, Eq, Hash, Debug)]
@@ -137,6 +138,11 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
         let Some(bytes) = self.bytes.as_deref_mut() else {
             return true;
         };
+        if !Prov::OFFSET_IS_ADDR {
+            // FIXME(#146291): We need to ensure that we don't mix different pointers with
+            // the same provenance.
+            return false;
+        }
         let ptr_size = cx.data_layout().pointer_size();
         while let Some((offset, (prov, _))) = bytes.iter().next().copied() {
             // Check if this fragment starts a pointer.
@@ -285,7 +291,7 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
         dest: Size,
         count: u64,
         cx: &impl HasDataLayout,
-    ) -> ProvenanceCopy<Prov> {
+    ) -> AllocResult<ProvenanceCopy<Prov>> {
         let shift_offset = move |idx, offset| {
             // compute offset for current repetition
             let dest_offset = dest + src.size * idx; // `Size` operations
@@ -363,6 +369,12 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
             }
             trace!("byte provenances: {bytes:?}");
 
+            if !bytes.is_empty() && !Prov::OFFSET_IS_ADDR {
+                // FIXME(#146291): We need to ensure that we don't mix different pointers with
+                // the same provenance.
+                return Err(AllocError::ReadPartialPointer(src.start));
+            }
+
             // And again a buffer for the new list on the target side.
             let mut dest_bytes = Vec::with_capacity(bytes.len() * (count as usize));
             for i in 0..count {
@@ -373,7 +385,7 @@ impl<Prov: Provenance> ProvenanceMap<Prov> {
             dest_bytes_box = Some(dest_bytes.into_boxed_slice());
         }
 
-        ProvenanceCopy { dest_ptrs: dest_ptrs_box, dest_bytes: dest_bytes_box }
+        Ok(ProvenanceCopy { dest_ptrs: dest_ptrs_box, dest_bytes: dest_bytes_box })
     }
 
     /// Applies a provenance copy.
diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs
index 6b94088cb56..625024373ef 100644
--- a/library/core/src/ptr/mod.rs
+++ b/library/core/src/ptr/mod.rs
@@ -1348,6 +1348,40 @@ pub const unsafe fn swap<T>(x: *mut T, y: *mut T) {
 /// assert_eq!(x, [7, 8, 3, 4]);
 /// assert_eq!(y, [1, 2, 9]);
 /// ```
+///
+/// # Const evaluation limitations
+///
+/// If this function is invoked during const-evaluation, the current implementation has a small (and
+/// rarely relevant) limitation: if `count` is at least 2 and the data pointed to by `x` or `y`
+/// contains a pointer that crosses the boundary of two `T`-sized chunks of memory, the function may
+/// fail to evaluate (similar to a panic during const-evaluation). This behavior may change in the
+/// future.
+///
+/// The limitation is illustrated by the following example:
+///
+/// ```
+/// use std::mem::size_of;
+/// use std::ptr;
+///
+/// const { unsafe {
+///     const PTR_SIZE: usize = size_of::<*const i32>();
+///     let mut data1 = [0u8; PTR_SIZE];
+///     let mut data2 = [0u8; PTR_SIZE];
+///     // Store a pointer in `data1`.
+///     data1.as_mut_ptr().cast::<*const i32>().write_unaligned(&42);
+///     // Swap the contents of `data1` and `data2` by swapping `PTR_SIZE` many `u8`-sized chunks.
+///     // This call will fail, because the pointer in `data1` crosses the boundary
+///     // between several of the 1-byte chunks that are being swapped here.
+///     //ptr::swap_nonoverlapping(data1.as_mut_ptr(), data2.as_mut_ptr(), PTR_SIZE);
+///     // Swap the contents of `data1` and `data2` by swapping a single chunk of size
+///     // `[u8; PTR_SIZE]`. That works, as there is no pointer crossing the boundary between
+///     // two chunks.
+///     ptr::swap_nonoverlapping(&mut data1, &mut data2, 1);
+///     // Read the pointer from `data2` and dereference it.
+///     let ptr = data2.as_ptr().cast::<*const i32>().read_unaligned();
+///     assert!(*ptr == 42);
+/// } }
+/// ```
 #[inline]
 #[stable(feature = "swap_nonoverlapping", since = "1.27.0")]
 #[rustc_const_stable(feature = "const_swap_nonoverlapping", since = "1.88.0")]
@@ -1376,7 +1410,9 @@ pub const unsafe fn swap_nonoverlapping<T>(x: *mut T, y: *mut T, count: usize) {
     const_eval_select!(
         @capture[T] { x: *mut T, y: *mut T, count: usize }:
         if const {
-            // At compile-time we don't need all the special code below.
+            // At compile-time we want to always copy this in chunks of `T`, to ensure that if there
+            // are pointers inside `T` we will copy them in one go rather than trying to copy a part
+            // of a pointer (which would not work).
             // SAFETY: Same preconditions as this function
             unsafe { swap_nonoverlapping_const(x, y, count) }
         } else {
diff --git a/library/coretests/tests/ptr.rs b/library/coretests/tests/ptr.rs
index c13fb96a67f..f8774833d0a 100644
--- a/library/coretests/tests/ptr.rs
+++ b/library/coretests/tests/ptr.rs
@@ -936,12 +936,13 @@ fn test_const_swap_ptr() {
         assert!(*s1.0.ptr == 666);
         assert!(*s2.0.ptr == 1);
 
-        // Swap them back, byte-for-byte
+        // Swap them back, again as an array.
+        // FIXME(#146291): we should be swapping back at type `u8` but that currently does not work.
         unsafe {
             ptr::swap_nonoverlapping(
-                ptr::from_mut(&mut s1).cast::<u8>(),
-                ptr::from_mut(&mut s2).cast::<u8>(),
-                size_of::<A>(),
+                ptr::from_mut(&mut s1).cast::<T>(),
+                ptr::from_mut(&mut s2).cast::<T>(),
+                1,
             );
         }
 
diff --git a/tests/ui/consts/const-eval/ptr_fragments.rs b/tests/ui/consts/const-eval/ptr_fragments.rs
index 04dcbe55590..7dc5870f89e 100644
--- a/tests/ui/consts/const-eval/ptr_fragments.rs
+++ b/tests/ui/consts/const-eval/ptr_fragments.rs
@@ -1,5 +1,6 @@
 //! Test that various operations involving pointer fragments work as expected.
 //@ run-pass
+//@ ignore-test: disabled due to <https://github.com/rust-lang/rust/issues/146291>
 
 use std::mem::{self, MaybeUninit, transmute};
 use std::ptr;
diff --git a/tests/ui/consts/const-eval/ptr_fragments_in_final.rs b/tests/ui/consts/const-eval/ptr_fragments_in_final.rs
index e2f3f51b086..aed33d7ed8d 100644
--- a/tests/ui/consts/const-eval/ptr_fragments_in_final.rs
+++ b/tests/ui/consts/const-eval/ptr_fragments_in_final.rs
@@ -1,4 +1,5 @@
 //! Test that we properly error when there is a pointer fragment in the final value.
+//@ ignore-test: disabled due to <https://github.com/rust-lang/rust/issues/146291>
 
 use std::{mem::{self, MaybeUninit}, ptr};
 
diff --git a/tests/ui/consts/const-eval/ptr_fragments_mixed.rs b/tests/ui/consts/const-eval/ptr_fragments_mixed.rs
new file mode 100644
index 00000000000..79a42820f50
--- /dev/null
+++ b/tests/ui/consts/const-eval/ptr_fragments_mixed.rs
@@ -0,0 +1,28 @@
+//! This mixes fragments from different pointers to the same allocarion, in a way
+//! that we should not accept. See <https://github.com/rust-lang/rust/issues/146291>.
+static A: u8 = 123;
+
+const HALF_PTR: usize = std::mem::size_of::<*const ()>() / 2;
+
+const fn mix_ptr() -> *const u8 {
+    unsafe {
+        let x: *const u8 = &raw const A;
+        let mut y = x.wrapping_add(usize::MAX / 4);
+        core::ptr::copy_nonoverlapping(
+            (&raw const x).cast::<u8>(),
+            (&raw mut y).cast::<u8>(),
+            HALF_PTR,
+        );
+        y
+    }
+}
+
+const APTR: *const u8 = mix_ptr(); //~ERROR: unable to read parts of a pointer
+
+fn main() {
+    let a = APTR;
+    println!("{a:p}");
+    let b = mix_ptr();
+    println!("{b:p}");
+    assert_eq!(a, b);
+}
diff --git a/tests/ui/consts/const-eval/ptr_fragments_mixed.stderr b/tests/ui/consts/const-eval/ptr_fragments_mixed.stderr
new file mode 100644
index 00000000000..9e991ab7a7d
--- /dev/null
+++ b/tests/ui/consts/const-eval/ptr_fragments_mixed.stderr
@@ -0,0 +1,23 @@
+error[E0080]: unable to read parts of a pointer from memory at ALLOC0
+  --> $DIR/ptr_fragments_mixed.rs:20:25
+   |
+LL | const APTR: *const u8 = mix_ptr();
+   |                         ^^^^^^^^^ evaluation of `APTR` failed inside this call
+   |
+   = help: this code performed an operation that depends on the underlying bytes representing a pointer
+   = help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
+note: inside `mix_ptr`
+  --> $DIR/ptr_fragments_mixed.rs:11:9
+   |
+LL | /         core::ptr::copy_nonoverlapping(
+LL | |             (&raw const x).cast::<u8>(),
+LL | |             (&raw mut y).cast::<u8>(),
+LL | |             HALF_PTR,
+LL | |         );
+   | |_________^
+note: inside `std::ptr::copy_nonoverlapping::<u8>`
+  --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/consts/const-eval/read_partial_ptr.rs b/tests/ui/consts/const-eval/read_partial_ptr.rs
index bccef9c0bc6..c153e274e41 100644
--- a/tests/ui/consts/const-eval/read_partial_ptr.rs
+++ b/tests/ui/consts/const-eval/read_partial_ptr.rs
@@ -1,4 +1,5 @@
 //! Ensure we error when trying to load from a pointer whose provenance has been messed with.
+//@ ignore-test: disabled due to <https://github.com/rust-lang/rust/issues/146291>
 
 const PARTIAL_OVERWRITE: () = {
     let mut p = &42;