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.
This commit is contained in:
Stuart Cook
2025-09-09 14:35:05 +10:00
committed by GitHub
10 changed files with 119 additions and 9 deletions
@@ -1501,8 +1501,10 @@ pub fn mem_copy_repeatedly(
// `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);
@@ -724,6 +724,11 @@ pub fn read_scalar(
}
// 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;
@@ -11,6 +11,7 @@
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 @@ pub fn merge_bytes(&mut self, cx: &impl HasDataLayout) -> bool {
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 @@ pub fn prepare_copy(
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 @@ pub fn prepare_copy(
}
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 @@ pub fn prepare_copy(
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.
+37 -1
View File
@@ -1348,6 +1348,40 @@ pub const fn slice_from_raw_parts_mut<T>(data: *mut T, len: usize) -> *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 fn slice_from_raw_parts_mut<T>(data: *mut T, len: usize) -> *mut [T] {
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 {
+5 -4
View File
@@ -936,12 +936,13 @@ struct S {
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,
);
}
@@ -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;
@@ -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};
@@ -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);
}
@@ -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`.
@@ -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;