Skip to content

Commit 01194d7

Browse files
committed
const-eval: always do mem-to-mem copies if there might be padding involved
1 parent 42f4793 commit 01194d7

File tree

4 files changed

+82
-12
lines changed

4 files changed

+82
-12
lines changed

compiler/rustc_const_eval/src/interpret/place.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -876,10 +876,29 @@ where
876876
dest.layout().ty,
877877
);
878878
}
879+
// If the source has padding, we want to always do the mem-to-mem copy to ensure consistent
880+
// padding in the target independent of layout choices.
881+
let src_has_padding = match src.layout().backend_repr {
882+
BackendRepr::Scalar(_) => false,
883+
BackendRepr::ScalarPair(left, right) => {
884+
let left_size = left.size(self);
885+
let right_size = right.size(self);
886+
// We have padding if the sizes don't add up to the total.
887+
left_size + right_size != src.layout().size
888+
}
889+
// Everything else can only exist in memory anyway.
890+
_ => true,
891+
};
879892

880-
// Let us see if the layout is simple so we take a shortcut,
881-
// avoid force_allocation.
882-
let src = match self.read_immediate_raw(src)? {
893+
let src_val = if src_has_padding {
894+
// Do our best to get an mplace. If there's no mplace, then this is stored as an
895+
// "optimized" local, so its padding is definitely uninitialized and we are fine.
896+
src.to_op(self)?.as_mplace_or_imm()
897+
} else {
898+
// Do our best to get an immediate, to avoid having to force_allocate the destination.
899+
self.read_immediate_raw(src)?
900+
};
901+
let src = match src_val {
883902
Right(src_val) => {
884903
assert!(!src.layout().is_unsized());
885904
assert!(!dest.layout().is_unsized());

compiler/rustc_const_eval/src/interpret/projection.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
9797
}
9898

9999
/// Convert this to an `OpTy`. This might be an irreversible transformation, but is useful for
100-
/// reading from this thing.
100+
/// reading from this thing. This will never actually do a read from memory!
101101
fn to_op<M: Machine<'tcx, Provenance = Prov>>(
102102
&self,
103103
ecx: &InterpCx<'tcx, M>,

tests/ui/consts/const-eval/ptr_fragments_in_final.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
//! Test that we properly error when there is a pointer fragment in the final value.
2-
//@ ignore-test: disabled due to <https://github.com/rust-lang/rust/issues/146291>
32
43
use std::{mem::{self, MaybeUninit}, ptr};
54

@@ -13,14 +12,50 @@ const unsafe fn memcpy(dst: *mut Byte, src: *const Byte, n: usize) {
1312
}
1413
}
1514

16-
const MEMCPY_RET: MaybeUninit<*const i32> = unsafe { //~ERROR: partial pointer in final value
15+
const MEMCPY_RET: MaybeUninit<*const i32> = unsafe { //~vvv ERROR: unable to read parts of a pointer
1716
let ptr = &42;
1817
let mut ptr2 = MaybeUninit::new(ptr::null::<i32>());
1918
memcpy(&mut ptr2 as *mut _ as *mut _, &ptr as *const _ as *const _, mem::size_of::<&i32>() / 2);
2019
// Return in a MaybeUninit so it does not get treated as a scalar.
2120
ptr2
2221
};
2322

23+
/// This has pointer bytes in the padding of the memory that the final value is read from.
24+
/// To ensure consistent behavior, we want to *always* copy that padding, even if the value
25+
/// could be represented as a more efficient ScalarPair. Hence this must fail to compile.
26+
fn fragment_in_padding() -> impl Copy {
27+
#[cfg(target_pointer_width = "64")]
28+
type TwoUsize = u128;
29+
#[cfg(target_pointer_width = "32")]
30+
type TwoUsize = u64;
31+
32+
#[repr(C, align(16))]
33+
#[derive(Clone, Copy)]
34+
struct Thing {
35+
x: TwoUsize,
36+
y: usize,
37+
// one pointer worth of padding
38+
}
39+
const _: () = assert!(mem::size_of::<Thing>() == 4 * mem::size_of::<usize>());
40+
#[derive(Clone, Copy)]
41+
union PreservePad {
42+
thing: Thing,
43+
bytes: [u8; mem::size_of::<Thing>()],
44+
}
45+
46+
const A: Thing = unsafe { //~vvvvvv ERROR: unable to read parts of a pointer
47+
let mut buffer = [PreservePad { bytes: [0u8; mem::size_of::<Thing>()] }; 2];
48+
// The offset half-way through the padding, so that copying one `Thing` copies exactly
49+
// half the pointer.
50+
let offset = mem::size_of::<Thing>() - mem::size_of::<usize>()/2;
51+
(&raw mut buffer).cast::<&i32>().byte_add(offset).write_unaligned(&1);
52+
buffer[0].thing
53+
};
54+
55+
A
56+
}
57+
2458
fn main() {
2559
assert_eq!(unsafe { MEMCPY_RET.assume_init().read() }, 42);
60+
fragment_in_padding();
2661
}
Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
1-
error: encountered partial pointer in final value of constant
2-
--> $DIR/ptr_fragments_in_final.rs:15:1
1+
error[E0080]: unable to read parts of a pointer from memory at ALLOC0
2+
--> $DIR/ptr_fragments_in_final.rs:18:5
33
|
4-
LL | const MEMCPY_RET: MaybeUninit<*const i32> = unsafe {
5-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
LL | memcpy(&mut ptr2 as *mut _ as *mut _, &ptr as *const _ as *const _, mem::size_of::<&i32>() / 2);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `MEMCPY_RET` failed inside this call
66
|
7-
= note: while pointers can be broken apart into individual bytes during const-evaluation, only complete pointers (with all their bytes in the right order) are supported in the final value
7+
= help: this code performed an operation that depends on the underlying bytes representing a pointer
8+
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
9+
note: inside `memcpy`
10+
--> $DIR/ptr_fragments_in_final.rs:10:26
11+
|
12+
LL | dst.add(i).write(src.add(i).read());
13+
| ^^^^^^^^^^^^^^^^^ the failure occurred here
14+
15+
error[E0080]: unable to read parts of a pointer from memory at ALLOC1
16+
--> $DIR/ptr_fragments_in_final.rs:52:9
17+
|
18+
LL | buffer[0].thing
19+
| ^^^^^^^^^^^^^^^ evaluation of `fragment_in_padding::A` failed here
20+
|
21+
= help: this code performed an operation that depends on the underlying bytes representing a pointer
22+
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
823

9-
error: aborting due to 1 previous error
24+
error: aborting due to 2 previous errors
1025

26+
For more information about this error, try `rustc --explain E0080`.

0 commit comments

Comments
 (0)