const-eval: disable pointer fragment support

This commit is contained in:
Ralf Jung 2025-09-08 10:40:18 +02:00
parent ebdf2abea4
commit aed0ed4c93
10 changed files with 119 additions and 9 deletions

View file

@ -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);

View file

@ -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;

View file

@ -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.

View file

@ -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 {

View file

@ -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,
);
}

View file

@ -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;

View file

@ -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};

View file

@ -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);
}

View file

@ -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`.

View file

@ -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;