rustc_const_eval/interpret/
projection.rs1use std::marker::PhantomData;
11use std::ops::Range;
12
13use rustc_abi::{self as abi, FieldIdx, Size, VariantIdx};
14use rustc_middle::ty::Ty;
15use rustc_middle::ty::layout::TyAndLayout;
16use rustc_middle::{bug, mir, span_bug, ty};
17use tracing::{debug, instrument};
18
19use super::{
20    InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, Provenance, Scalar, err_ub,
21    interp_ok, throw_ub, throw_unsup,
22};
23
24#[derive(Copy, Clone, Debug)]
26pub enum OffsetMode {
27    Inbounds,
29    Wrapping,
31}
32
33pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
35    fn layout(&self) -> TyAndLayout<'tcx>;
37
38    fn meta(&self) -> MemPlaceMeta<Prov>;
40
41    fn len<M: Machine<'tcx, Provenance = Prov>>(
43        &self,
44        ecx: &InterpCx<'tcx, M>,
45    ) -> InterpResult<'tcx, u64> {
46        let layout = self.layout();
47        if layout.is_unsized() {
48            match layout.ty.kind() {
50                ty::Slice(..) | ty::Str => self.meta().unwrap_meta().to_target_usize(ecx),
51                _ => bug!("len not supported on unsized type {:?}", layout.ty),
52            }
53        } else {
54            match layout.fields {
57                abi::FieldsShape::Array { count, .. } => interp_ok(count),
58                _ => bug!("len not supported on sized type {:?}", layout.ty),
59            }
60        }
61    }
62
63    fn offset_with_meta<M: Machine<'tcx, Provenance = Prov>>(
65        &self,
66        offset: Size,
67        mode: OffsetMode,
68        meta: MemPlaceMeta<Prov>,
69        layout: TyAndLayout<'tcx>,
70        ecx: &InterpCx<'tcx, M>,
71    ) -> InterpResult<'tcx, Self>;
72
73    fn offset<M: Machine<'tcx, Provenance = Prov>>(
74        &self,
75        offset: Size,
76        layout: TyAndLayout<'tcx>,
77        ecx: &InterpCx<'tcx, M>,
78    ) -> InterpResult<'tcx, Self> {
79        assert!(layout.is_sized());
80        self.offset_with_meta(offset, OffsetMode::Inbounds, MemPlaceMeta::None, layout, ecx)
83    }
84
85    fn transmute<M: Machine<'tcx, Provenance = Prov>>(
90        &self,
91        layout: TyAndLayout<'tcx>,
92        ecx: &InterpCx<'tcx, M>,
93    ) -> InterpResult<'tcx, Self> {
94        assert!(self.layout().is_sized() && layout.is_sized());
95        assert_eq!(self.layout().size, layout.size);
96        self.offset_with_meta(Size::ZERO, OffsetMode::Wrapping, MemPlaceMeta::None, layout, ecx)
97    }
98
99    fn to_op<M: Machine<'tcx, Provenance = Prov>>(
102        &self,
103        ecx: &InterpCx<'tcx, M>,
104    ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>>;
105}
106
107pub struct ArrayIterator<'a, 'tcx, Prov: Provenance, P: Projectable<'tcx, Prov>> {
109    base: &'a P,
110    range: Range<u64>,
111    stride: Size,
112    field_layout: TyAndLayout<'tcx>,
113    _phantom: PhantomData<Prov>, }
115
116impl<'a, 'tcx, Prov: Provenance, P: Projectable<'tcx, Prov>> ArrayIterator<'a, 'tcx, Prov, P> {
117    pub fn next<M: Machine<'tcx, Provenance = Prov>>(
119        &mut self,
120        ecx: &InterpCx<'tcx, M>,
121    ) -> InterpResult<'tcx, Option<(u64, P)>> {
122        let Some(idx) = self.range.next() else { return interp_ok(None) };
123        interp_ok(Some((
125            idx,
126            self.base.offset_with_meta(
127                self.stride * idx,
128                OffsetMode::Wrapping,
129                MemPlaceMeta::None,
130                self.field_layout,
131                ecx,
132            )?,
133        )))
134    }
135}
136
137impl<'tcx, Prov, M> InterpCx<'tcx, M>
139where
140    Prov: Provenance,
141    M: Machine<'tcx, Provenance = Prov>,
142{
143    pub fn project_field<P: Projectable<'tcx, M::Provenance>>(
150        &self,
151        base: &P,
152        field: FieldIdx,
153    ) -> InterpResult<'tcx, P> {
154        debug_assert!(
156            !matches!(base.layout().ty.kind(), ty::Slice(..)),
157            "`field` projection called on a slice -- call `index` projection instead"
158        );
159        let offset = base.layout().fields.offset(field.as_usize());
160        let field_layout = base.layout().field(self, field.as_usize());
163
164        let (meta, offset) = if field_layout.is_unsized() {
166            assert!(!base.layout().is_sized());
167            let base_meta = base.meta();
168            match self.size_and_align_from_meta(&base_meta, &field_layout)? {
172                Some((_, align)) => {
173                    let align = if let ty::Adt(def, _) = base.layout().ty.kind()
175                        && let Some(packed) = def.repr().pack
176                    {
177                        align.min(packed)
178                    } else {
179                        align
180                    };
181                    (base_meta, offset.align_to(align))
182                }
183                None if offset == Size::ZERO => {
184                    (base_meta, offset)
187                }
188                None => {
189                    throw_unsup!(ExternTypeField)
191                }
192            }
193        } else {
194            (MemPlaceMeta::None, offset)
197        };
198
199        base.offset_with_meta(offset, OffsetMode::Inbounds, meta, field_layout, self)
200    }
201
202    pub fn project_downcast<P: Projectable<'tcx, M::Provenance>>(
204        &self,
205        base: &P,
206        variant: VariantIdx,
207    ) -> InterpResult<'tcx, P> {
208        assert!(!base.meta().has_meta());
209        let layout = base.layout().for_variant(self, variant);
214        base.offset(Size::ZERO, layout, self)
219    }
220
221    pub fn project_index<P: Projectable<'tcx, M::Provenance>>(
223        &self,
224        base: &P,
225        index: u64,
226    ) -> InterpResult<'tcx, P> {
227        let (offset, field_layout) = match base.layout().fields {
229            abi::FieldsShape::Array { stride, count: _ } => {
230                let len = base.len(self)?;
232                if index >= len {
233                    throw_ub!(BoundsCheckFailed { len, index });
235                }
236                let offset = self
238                    .compute_size_in_bytes(stride, index)
239                    .ok_or_else(|| err_ub!(PointerArithOverflow))?;
240
241                let field_layout = base.layout().field(self, 0);
243                (offset, field_layout)
244            }
245            _ => span_bug!(
246                self.cur_span(),
247                "`project_index` called on non-array type {:?}",
248                base.layout().ty
249            ),
250        };
251
252        base.offset(offset, field_layout, self)
253    }
254
255    pub fn project_to_simd<P: Projectable<'tcx, M::Provenance>>(
258        &self,
259        base: &P,
260    ) -> InterpResult<'tcx, (P, u64)> {
261        assert!(base.layout().ty.ty_adt_def().unwrap().repr().simd());
262        let array = self.project_field(base, FieldIdx::ZERO)?;
264        let len = array.len(self)?;
265        interp_ok((array, len))
266    }
267
268    fn project_constant_index<P: Projectable<'tcx, M::Provenance>>(
269        &self,
270        base: &P,
271        offset: u64,
272        min_length: u64,
273        from_end: bool,
274    ) -> InterpResult<'tcx, P> {
275        let n = base.len(self)?;
276        if n < min_length {
277            throw_ub!(BoundsCheckFailed { len: min_length, index: n });
279        }
280
281        let index = if from_end {
282            assert!(0 < offset && offset <= min_length);
283            n.checked_sub(offset).unwrap()
284        } else {
285            assert!(offset < min_length);
286            offset
287        };
288
289        self.project_index(base, index)
290    }
291
292    pub fn project_array_fields<'a, P: Projectable<'tcx, M::Provenance>>(
295        &self,
296        base: &'a P,
297    ) -> InterpResult<'tcx, ArrayIterator<'a, 'tcx, M::Provenance, P>> {
298        let abi::FieldsShape::Array { stride, .. } = base.layout().fields else {
299            span_bug!(
300                self.cur_span(),
301                "project_array_fields: expected an array layout, got {:#?}",
302                base.layout()
303            );
304        };
305        let len = base.len(self)?;
306        let field_layout = base.layout().field(self, 0);
307        debug!("project_array_fields: {base:?} {len}");
309        base.offset(len * stride, self.layout_of(self.tcx.types.unit).unwrap(), self)?;
310        interp_ok(ArrayIterator {
312            base,
313            range: 0..len,
314            stride,
315            field_layout,
316            _phantom: PhantomData,
317        })
318    }
319
320    fn project_subslice<P: Projectable<'tcx, M::Provenance>>(
322        &self,
323        base: &P,
324        from: u64,
325        to: u64,
326        from_end: bool,
327    ) -> InterpResult<'tcx, P> {
328        let len = base.len(self)?; let actual_to = if from_end {
330            if from.checked_add(to).is_none_or(|to| to > len) {
331                throw_ub!(BoundsCheckFailed { len, index: from.saturating_add(to) });
333            }
334            len.checked_sub(to).unwrap()
335        } else {
336            to
337        };
338
339        let from_offset = match base.layout().fields {
342            abi::FieldsShape::Array { stride, .. } => stride * from, _ => {
344                span_bug!(
345                    self.cur_span(),
346                    "unexpected layout of index access: {:#?}",
347                    base.layout()
348                )
349            }
350        };
351
352        let inner_len = actual_to.checked_sub(from).unwrap();
354        let (meta, ty) = match base.layout().ty.kind() {
355            ty::Array(inner, _) => {
358                (MemPlaceMeta::None, Ty::new_array(self.tcx.tcx, *inner, inner_len))
359            }
360            ty::Slice(..) => {
361                let len = Scalar::from_target_usize(inner_len, self);
362                (MemPlaceMeta::Meta(len), base.layout().ty)
363            }
364            _ => {
365                span_bug!(
366                    self.cur_span(),
367                    "cannot subslice non-array type: `{:?}`",
368                    base.layout().ty
369                )
370            }
371        };
372        let layout = self.layout_of(ty)?;
373
374        base.offset_with_meta(from_offset, OffsetMode::Inbounds, meta, layout, self)
375    }
376
377    #[instrument(skip(self), level = "trace")]
379    pub fn project<P>(&self, base: &P, proj_elem: mir::PlaceElem<'tcx>) -> InterpResult<'tcx, P>
380    where
381        P: Projectable<'tcx, M::Provenance> + From<MPlaceTy<'tcx, M::Provenance>> + std::fmt::Debug,
382    {
383        use rustc_middle::mir::ProjectionElem::*;
384        interp_ok(match proj_elem {
385            OpaqueCast(ty) => {
386                span_bug!(self.cur_span(), "OpaqueCast({ty}) encountered after borrowck")
387            }
388            UnwrapUnsafeBinder(target) => base.transmute(self.layout_of(target)?, self)?,
389            Subtype(_) => base.transmute(base.layout(), self)?,
391            Field(field, _) => self.project_field(base, field)?,
392            Downcast(_, variant) => self.project_downcast(base, variant)?,
393            Deref => self.deref_pointer(&base.to_op(self)?)?.into(),
394            Index(local) => {
395                let layout = self.layout_of(self.tcx.types.usize)?;
396                let n = self.local_to_op(local, Some(layout))?;
397                let n = self.read_target_usize(&n)?;
398                self.project_index(base, n)?
399            }
400            ConstantIndex { offset, min_length, from_end } => {
401                self.project_constant_index(base, offset, min_length, from_end)?
402            }
403            Subslice { from, to, from_end } => self.project_subslice(base, from, to, from_end)?,
404        })
405    }
406}