about summary refs log tree commit diff
diff options
context:
space:
mode:
authorStuart Cook <Zalathar@users.noreply.github.com>2025-09-09 14:35:05 +1000
committerGitHub <noreply@github.com>2025-09-09 14:35:05 +1000
commit915f9ff1602e6c8cf3b5ae29da58b932a91449c6 (patch)
treebbfeefde2d69ec46b62bf25eb71a6a5548555a88
parentb543b1444b998cabb5d60823ae73dde8e577d10f (diff)
parentaed0ed4c93d661fc7b66dc4a39690948476e8a4a (diff)
downloadrust-915f9ff1602e6c8cf3b5ae29da58b932a91449c6.tar.gz
rust-915f9ff1602e6c8cf3b5ae29da58b932a91449c6.zip
Rollup merge of #146324 - RalfJung:no-ptr-fragment, r=oli-obk
const-eval: disable pointer fragment support

This fixes https://github.com/rust-lang/rust/issues/146291 by disabling pointer fragment support for const-eval. I want to properly fix this eventually, but won't get to it in the next few weeks, so this is an emergency patch to prevent the buggy implementation from landing on stable. The beta cutoff is on Sep 12th so if this PR lands after that, we'll need a backport.
-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;