Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions compiler/rustc_const_eval/src/interpret/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_middle/src/mir/interpret/allocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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.
Expand Down
38 changes: 37 additions & 1 deletion library/core/src/ptr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 5 additions & 4 deletions library/coretests/tests/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
}

Expand Down
1 change: 1 addition & 0 deletions tests/ui/consts/const-eval/ptr_fragments.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
1 change: 1 addition & 0 deletions tests/ui/consts/const-eval/ptr_fragments_in_final.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down
28 changes: 28 additions & 0 deletions tests/ui/consts/const-eval/ptr_fragments_mixed.rs
Original file line number Diff line number Diff line change
@@ -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);
}
23 changes: 23 additions & 0 deletions tests/ui/consts/const-eval/ptr_fragments_mixed.stderr
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions tests/ui/consts/const-eval/read_partial_ptr.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Loading