Skip to content

Commit bdd5855

Browse files
committed
interpret: fix projecting into an unsized field of a local
new invariant: Place::Local never refers to something unsized
1 parent 61efe9d commit bdd5855

File tree

6 files changed

+124
-97
lines changed

6 files changed

+124
-97
lines changed

compiler/rustc_const_eval/src/interpret/eval_context.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ pub enum LocalValue<Prov: Provenance = AllocId> {
177177

178178
impl<'tcx, Prov: Provenance + 'static> LocalState<'tcx, Prov> {
179179
/// Read the local's value or error if the local is not yet live or not live anymore.
180-
#[inline]
180+
#[inline(always)]
181181
pub fn access(&self) -> InterpResult<'tcx, &Operand<Prov>> {
182182
match &self.value {
183183
LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
@@ -190,7 +190,7 @@ impl<'tcx, Prov: Provenance + 'static> LocalState<'tcx, Prov> {
190190
///
191191
/// Note: This may only be invoked from the `Machine::access_local_mut` hook and not from
192192
/// anywhere else. You may be invalidating machine invariants if you do!
193-
#[inline]
193+
#[inline(always)]
194194
pub fn access_mut(&mut self) -> InterpResult<'tcx, &mut Operand<Prov>> {
195195
match &mut self.value {
196196
LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?

compiler/rustc_const_eval/src/interpret/operand.rs

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -291,23 +291,21 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for ImmTy<'tcx, Prov> {
291291
self.layout
292292
}
293293

294-
fn meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
295-
&self,
296-
_ecx: &InterpCx<'mir, 'tcx, M>,
297-
) -> InterpResult<'tcx, MemPlaceMeta<M::Provenance>> {
298-
assert!(self.layout.is_sized()); // unsized ImmTy can only exist temporarily and should never reach this here
294+
#[inline(always)]
295+
fn meta(&self) -> InterpResult<'tcx, MemPlaceMeta<Prov>> {
296+
debug_assert!(self.layout.is_sized()); // unsized ImmTy can only exist temporarily and should never reach this here
299297
Ok(MemPlaceMeta::None)
300298
}
301299

302-
fn offset_with_meta(
300+
fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
303301
&self,
304302
offset: Size,
305303
meta: MemPlaceMeta<Prov>,
306304
layout: TyAndLayout<'tcx>,
307-
cx: &impl HasDataLayout,
305+
ecx: &InterpCx<'mir, 'tcx, M>,
308306
) -> InterpResult<'tcx, Self> {
309307
assert_matches!(meta, MemPlaceMeta::None); // we can't store this anywhere anyway
310-
Ok(self.offset_(offset, layout, cx))
308+
Ok(self.offset_(offset, layout, ecx))
311309
}
312310

313311
fn to_op<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
@@ -318,49 +316,39 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for ImmTy<'tcx, Prov> {
318316
}
319317
}
320318

321-
impl<'tcx, Prov: Provenance> OpTy<'tcx, Prov> {
322-
// Provided as inherent method since it doesn't need the `ecx` of `Projectable::meta`.
323-
pub fn meta(&self) -> InterpResult<'tcx, MemPlaceMeta<Prov>> {
324-
Ok(if self.layout.is_unsized() {
325-
if matches!(self.op, Operand::Immediate(_)) {
326-
// Unsized immediate OpTy cannot occur. We create a MemPlace for all unsized locals during argument passing.
327-
// However, ConstProp doesn't do that, so we can run into this nonsense situation.
328-
throw_inval!(ConstPropNonsense);
329-
}
330-
// There are no unsized immediates.
331-
self.assert_mem_place().meta
332-
} else {
333-
MemPlaceMeta::None
334-
})
335-
}
336-
}
337-
338319
impl<'tcx, Prov: Provenance + 'static> Projectable<'tcx, Prov> for OpTy<'tcx, Prov> {
339320
#[inline(always)]
340321
fn layout(&self) -> TyAndLayout<'tcx> {
341322
self.layout
342323
}
343324

344-
fn meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
345-
&self,
346-
_ecx: &InterpCx<'mir, 'tcx, M>,
347-
) -> InterpResult<'tcx, MemPlaceMeta<M::Provenance>> {
348-
self.meta()
325+
fn meta(&self) -> InterpResult<'tcx, MemPlaceMeta<Prov>> {
326+
Ok(match self.as_mplace_or_imm() {
327+
Left(mplace) => mplace.meta,
328+
Right(_) => {
329+
if self.layout.is_unsized() {
330+
// Unsized immediate OpTy cannot occur. We create a MemPlace for all unsized locals during argument passing.
331+
// However, ConstProp doesn't do that, so we can run into this nonsense situation.
332+
throw_inval!(ConstPropNonsense);
333+
}
334+
MemPlaceMeta::None
335+
}
336+
})
349337
}
350338

351-
fn offset_with_meta(
339+
fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
352340
&self,
353341
offset: Size,
354342
meta: MemPlaceMeta<Prov>,
355343
layout: TyAndLayout<'tcx>,
356-
cx: &impl HasDataLayout,
344+
ecx: &InterpCx<'mir, 'tcx, M>,
357345
) -> InterpResult<'tcx, Self> {
358346
match self.as_mplace_or_imm() {
359-
Left(mplace) => Ok(mplace.offset_with_meta(offset, meta, layout, cx)?.into()),
347+
Left(mplace) => Ok(mplace.offset_with_meta(offset, meta, layout, ecx)?.into()),
360348
Right(imm) => {
361349
assert!(!meta.has_meta()); // no place to store metadata here
362350
// Every part of an uninit is uninit.
363-
Ok(imm.offset(offset, layout, cx)?.into())
351+
Ok(imm.offset(offset, layout, ecx)?.into())
364352
}
365353
}
366354
}

compiler/rustc_const_eval/src/interpret/place.rs

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ pub enum Place<Prov: Provenance = AllocId> {
111111
/// (Without that optimization, we'd just always be a `MemPlace`.)
112112
/// Note that this only stores the frame index, not the thread this frame belongs to -- that is
113113
/// implicit. This means a `Place` must never be moved across interpreter thread boundaries!
114+
///
115+
/// This variant shall not be used for unsized types -- those must always live in memory.
114116
Local { frame: usize, local: mir::Local, offset: Option<Size> },
115117
}
116118

@@ -157,7 +159,7 @@ impl<Prov: Provenance> MemPlace<Prov> {
157159
}
158160

159161
/// Turn a mplace into a (thin or wide) pointer, as a reference, pointing to the same space.
160-
#[inline(always)]
162+
#[inline]
161163
pub fn to_ref(self, cx: &impl HasDataLayout) -> Immediate<Prov> {
162164
match self.meta {
163165
MemPlaceMeta::None => Immediate::from(Scalar::from_maybe_pointer(self.ptr, cx)),
@@ -220,22 +222,20 @@ impl<'tcx, Prov: Provenance + 'static> Projectable<'tcx, Prov> for MPlaceTy<'tcx
220222
self.layout
221223
}
222224

223-
fn meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
224-
&self,
225-
_ecx: &InterpCx<'mir, 'tcx, M>,
226-
) -> InterpResult<'tcx, MemPlaceMeta<M::Provenance>> {
225+
#[inline(always)]
226+
fn meta(&self) -> InterpResult<'tcx, MemPlaceMeta<Prov>> {
227227
Ok(self.meta)
228228
}
229229

230-
fn offset_with_meta(
230+
fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
231231
&self,
232232
offset: Size,
233233
meta: MemPlaceMeta<Prov>,
234234
layout: TyAndLayout<'tcx>,
235-
cx: &impl HasDataLayout,
235+
ecx: &InterpCx<'mir, 'tcx, M>,
236236
) -> InterpResult<'tcx, Self> {
237237
Ok(MPlaceTy {
238-
mplace: self.mplace.offset_with_meta_(offset, meta, cx)?,
238+
mplace: self.mplace.offset_with_meta_(offset, meta, ecx)?,
239239
align: self.align.restrict_for_offset(offset),
240240
layout,
241241
})
@@ -255,25 +255,33 @@ impl<'tcx, Prov: Provenance + 'static> Projectable<'tcx, Prov> for PlaceTy<'tcx,
255255
self.layout
256256
}
257257

258-
fn meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
259-
&self,
260-
ecx: &InterpCx<'mir, 'tcx, M>,
261-
) -> InterpResult<'tcx, MemPlaceMeta<M::Provenance>> {
262-
ecx.place_meta(self)
258+
fn meta(&self) -> InterpResult<'tcx, MemPlaceMeta<Prov>> {
259+
Ok(match self.as_mplace_or_local() {
260+
Left(mplace) => mplace.meta,
261+
Right(_) => {
262+
if self.layout.is_unsized() {
263+
// Unsized `Place::Local` cannot occur. We create a MemPlace for all unsized locals during argument passing.
264+
// However, ConstProp doesn't do that, so we can run into this nonsense situation.
265+
throw_inval!(ConstPropNonsense);
266+
}
267+
MemPlaceMeta::None
268+
}
269+
})
263270
}
264271

265-
fn offset_with_meta(
272+
fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
266273
&self,
267274
offset: Size,
268275
meta: MemPlaceMeta<Prov>,
269276
layout: TyAndLayout<'tcx>,
270-
cx: &impl HasDataLayout,
277+
ecx: &InterpCx<'mir, 'tcx, M>,
271278
) -> InterpResult<'tcx, Self> {
272279
Ok(match self.as_mplace_or_local() {
273-
Left(mplace) => mplace.offset_with_meta(offset, meta, layout, cx)?.into(),
280+
Left(mplace) => mplace.offset_with_meta(offset, meta, layout, ecx)?.into(),
274281
Right((frame, local, old_offset)) => {
282+
debug_assert!(layout.is_sized(), "unsized locals should live in memory");
275283
assert_matches!(meta, MemPlaceMeta::None); // we couldn't store it anyway...
276-
let new_offset = cx
284+
let new_offset = ecx
277285
.data_layout()
278286
.offset(old_offset.unwrap_or(Size::ZERO).bytes(), offset.bytes())?;
279287
PlaceTy {
@@ -399,20 +407,6 @@ where
399407
Prov: Provenance + 'static,
400408
M: Machine<'mir, 'tcx, Provenance = Prov>,
401409
{
402-
/// Get the metadata of the given place.
403-
pub(super) fn place_meta(
404-
&self,
405-
place: &PlaceTy<'tcx, M::Provenance>,
406-
) -> InterpResult<'tcx, MemPlaceMeta<M::Provenance>> {
407-
if place.layout.is_unsized() {
408-
// For `Place::Local`, the metadata is stored with the local, not the place. So we have
409-
// to look that up first.
410-
self.place_to_op(place)?.meta()
411-
} else {
412-
Ok(MemPlaceMeta::None)
413-
}
414-
}
415-
416410
/// Take a value, which represents a (thin or wide) reference, and make it a place.
417411
/// Alignment is just based on the type. This is the inverse of `mplace_to_ref()`.
418412
///
@@ -537,8 +531,14 @@ where
537531
frame: usize,
538532
local: mir::Local,
539533
) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> {
540-
let layout = self.layout_of_local(&self.stack()[frame], local, None)?;
541-
let place = Place::Local { frame, local, offset: None };
534+
// Other parts of the system rely on `Place::Local` never being unsized.
535+
// So we eagerly check here if this local has an MPlace, and if yes we use it.
536+
let frame_ref = &self.stack()[frame];
537+
let layout = self.layout_of_local(frame_ref, local, None)?;
538+
let place = match frame_ref.locals[local].access()? {
539+
Operand::Immediate(_) => Place::Local { frame, local, offset: None },
540+
Operand::Indirect(mplace) => Place::Ptr(*mplace),
541+
};
542542
Ok(PlaceTy { place, layout, align: layout.align.abi })
543543
}
544544

compiler/rustc_const_eval/src/interpret/projection.rs

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
//! but we still need to do bounds checking and adjust the layout. To not duplicate that with MPlaceTy, we actually
88
//! implement the logic on OpTy, and MPlaceTy calls that.
99
10+
use std::marker::PhantomData;
11+
use std::ops::Range;
12+
1013
use rustc_middle::mir;
1114
use rustc_middle::ty;
1215
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
1316
use rustc_middle::ty::Ty;
14-
use rustc_middle::ty::TyCtxt;
15-
use rustc_target::abi::HasDataLayout;
1617
use rustc_target::abi::Size;
1718
use rustc_target::abi::{self, VariantIdx};
1819

@@ -24,44 +25,42 @@ pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
2425
fn layout(&self) -> TyAndLayout<'tcx>;
2526

2627
/// Get the metadata of a wide value.
27-
fn meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
28-
&self,
29-
ecx: &InterpCx<'mir, 'tcx, M>,
30-
) -> InterpResult<'tcx, MemPlaceMeta<M::Provenance>>;
28+
fn meta(&self) -> InterpResult<'tcx, MemPlaceMeta<Prov>>;
3129

3230
fn len<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
3331
&self,
3432
ecx: &InterpCx<'mir, 'tcx, M>,
3533
) -> InterpResult<'tcx, u64> {
36-
self.meta(ecx)?.len(self.layout(), ecx)
34+
self.meta()?.len(self.layout(), ecx)
3735
}
3836

3937
/// Offset the value by the given amount, replacing the layout and metadata.
40-
fn offset_with_meta(
38+
fn offset_with_meta<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
4139
&self,
4240
offset: Size,
4341
meta: MemPlaceMeta<Prov>,
4442
layout: TyAndLayout<'tcx>,
45-
cx: &impl HasDataLayout,
43+
ecx: &InterpCx<'mir, 'tcx, M>,
4644
) -> InterpResult<'tcx, Self>;
4745

48-
fn offset(
46+
fn offset<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
4947
&self,
5048
offset: Size,
5149
layout: TyAndLayout<'tcx>,
52-
cx: &impl HasDataLayout,
50+
ecx: &InterpCx<'mir, 'tcx, M>,
5351
) -> InterpResult<'tcx, Self> {
5452
assert!(layout.is_sized());
55-
self.offset_with_meta(offset, MemPlaceMeta::None, layout, cx)
53+
self.offset_with_meta(offset, MemPlaceMeta::None, layout, ecx)
5654
}
5755

58-
fn transmute(
56+
fn transmute<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
5957
&self,
6058
layout: TyAndLayout<'tcx>,
61-
cx: &impl HasDataLayout,
59+
ecx: &InterpCx<'mir, 'tcx, M>,
6260
) -> InterpResult<'tcx, Self> {
61+
assert!(self.layout().is_sized() && layout.is_sized());
6362
assert_eq!(self.layout().size, layout.size);
64-
self.offset_with_meta(Size::ZERO, MemPlaceMeta::None, layout, cx)
63+
self.offset_with_meta(Size::ZERO, MemPlaceMeta::None, layout, ecx)
6564
}
6665

6766
/// Convert this to an `OpTy`. This might be an irreversible transformation, but is useful for
@@ -72,6 +71,28 @@ pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
7271
) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>>;
7372
}
7473

74+
/// A type representing iteration over the elements of an array.
75+
pub struct ArrayIterator<'tcx, 'a, Prov: Provenance + 'static, P: Projectable<'tcx, Prov>> {
76+
base: &'a P,
77+
range: Range<u64>,
78+
stride: Size,
79+
field_layout: TyAndLayout<'tcx>,
80+
_phantom: PhantomData<Prov>, // otherwise it says `Prov` is never used...
81+
}
82+
83+
impl<'tcx, 'a, Prov: Provenance + 'static, P: Projectable<'tcx, Prov>>
84+
ArrayIterator<'tcx, 'a, Prov, P>
85+
{
86+
/// Should be the same `ecx` on each call, and match the one used to create the iterator.
87+
pub fn next<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
88+
&mut self,
89+
ecx: &InterpCx<'mir, 'tcx, M>,
90+
) -> InterpResult<'tcx, Option<(u64, P)>> {
91+
let Some(idx) = self.range.next() else { return Ok(None) };
92+
Ok(Some((idx, self.base.offset(self.stride * idx, self.field_layout, ecx)?)))
93+
}
94+
}
95+
7596
// FIXME: Working around https://github.com/rust-lang/rust/issues/54385
7697
impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M>
7798
where
@@ -104,7 +125,7 @@ where
104125
// But const-prop actually feeds us such nonsense MIR! (see test `const_prop/issue-86351.rs`)
105126
throw_inval!(ConstPropNonsense);
106127
}
107-
let base_meta = base.meta(self)?;
128+
let base_meta = base.meta()?;
108129
// Re-use parent metadata to determine dynamic field layout.
109130
// With custom DSTS, this *will* execute user-defined code, but the same
110131
// happens at run-time so that's okay.
@@ -132,7 +153,7 @@ where
132153
base: &P,
133154
variant: VariantIdx,
134155
) -> InterpResult<'tcx, P> {
135-
assert!(!base.meta(self)?.has_meta());
156+
assert!(!base.meta()?.has_meta());
136157
// Downcasts only change the layout.
137158
// (In particular, no check about whether this is even the active variant -- that's by design,
138159
// see https://github.com/rust-lang/rust/issues/93688#issuecomment-1032929496.)
@@ -206,20 +227,13 @@ where
206227
pub fn project_array_fields<'a, P: Projectable<'tcx, M::Provenance>>(
207228
&self,
208229
base: &'a P,
209-
) -> InterpResult<'tcx, impl Iterator<Item = InterpResult<'tcx, P>> + 'a>
210-
where
211-
'tcx: 'a,
212-
{
230+
) -> InterpResult<'tcx, ArrayIterator<'tcx, 'a, M::Provenance, P>> {
213231
let abi::FieldsShape::Array { stride, .. } = base.layout().fields else {
214232
span_bug!(self.cur_span(), "operand_array_fields: expected an array layout");
215233
};
216234
let len = base.len(self)?;
217235
let field_layout = base.layout().field(self, 0);
218-
let tcx: TyCtxt<'tcx> = *self.tcx;
219-
// `Size` multiplication
220-
Ok((0..len).map(move |i| {
221-
base.offset_with_meta(stride * i, MemPlaceMeta::None, field_layout, &tcx)
222-
}))
236+
Ok(ArrayIterator { base, range: 0..len, stride, field_layout, _phantom: PhantomData })
223237
}
224238

225239
/// Subslicing

compiler/rustc_const_eval/src/interpret/visitor.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,9 @@ pub trait ValueVisitor<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>>: Sized {
170170
}
171171
}
172172
FieldsShape::Array { .. } => {
173-
for (idx, field) in self.ecx().project_array_fields(v)?.enumerate() {
174-
self.visit_field(v, idx, &field?)?;
173+
let mut iter = self.ecx().project_array_fields(v)?;
174+
while let Some((idx, field)) = iter.next(self.ecx())? {
175+
self.visit_field(v, idx.try_into().unwrap(), &field)?;
175176
}
176177
}
177178
}

0 commit comments

Comments
 (0)