rustc_const_eval/interpret/
projection.rs

1//! This file implements "place projections"; basically a symmetric API for 3 types: MPlaceTy, OpTy, PlaceTy.
2//!
3//! OpTy and PlaceTy generally work by "let's see if we are actually an MPlaceTy, and do something custom if not".
4//! For PlaceTy, the custom thing is basically always to call `force_allocation` and then use the MPlaceTy logic anyway.
5//! For OpTy, the custom thing on field pojections has to be pretty clever (since `Operand::Immediate` can have fields),
6//! but for array/slice operations it only has to worry about `Operand::Uninit`. That makes the value part trivial,
7//! but we still need to do bounds checking and adjust the layout. To not duplicate that with MPlaceTy, we actually
8//! implement the logic on OpTy, and MPlaceTy calls that.
9
10use 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/// Describes the constraints placed on offset-projections.
25#[derive(Copy, Clone, Debug)]
26pub enum OffsetMode {
27    /// The offset has to be inbounds, like `ptr::offset`.
28    Inbounds,
29    /// No constraints, just wrap around the edge of the address space.
30    Wrapping,
31}
32
33/// A thing that we can project into, and that has a layout.
34pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
35    /// Get the layout.
36    fn layout(&self) -> TyAndLayout<'tcx>;
37
38    /// Get the metadata of a wide value.
39    fn meta(&self) -> MemPlaceMeta<Prov>;
40
41    /// Get the length of a slice/string/array stored here.
42    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            // We need to consult `meta` metadata
49            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            // Go through the layout. There are lots of types that support a length,
55            // e.g., SIMD types. (But not all repr(simd) types even have FieldsShape::Array!)
56            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    /// Offset the value by the given amount, replacing the layout and metadata.
64    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        // We sometimes do pointer arithmetic with this function, disregarding the source type.
81        // So we don't check the sizes here.
82        self.offset_with_meta(offset, OffsetMode::Inbounds, MemPlaceMeta::None, layout, ecx)
83    }
84
85    /// This does an offset-by-zero, which is effectively a transmute. Note however that
86    /// not all transmutes are supported by all projectables -- specifically, if this is an
87    /// `OpTy` or `ImmTy`, the new layout must have almost the same ABI as the old one
88    /// (only changing the `valid_range` is allowed and turning integers into pointers).
89    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    /// Convert this to an `OpTy`. This might be an irreversible transformation, but is useful for
100    /// reading from this thing.
101    fn to_op<M: Machine<'tcx, Provenance = Prov>>(
102        &self,
103        ecx: &InterpCx<'tcx, M>,
104    ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>>;
105}
106
107/// A type representing iteration over the elements of an array.
108pub 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>, // otherwise it says `Prov` is never used...
114}
115
116impl<'a, 'tcx, Prov: Provenance, P: Projectable<'tcx, Prov>> ArrayIterator<'a, 'tcx, Prov, P> {
117    /// Should be the same `ecx` on each call, and match the one used to create the iterator.
118    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        // We use `Wrapping` here since the offset has already been checked when the iterator was created.
124        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
137// FIXME: Working around https://github.com/rust-lang/rust/issues/54385
138impl<'tcx, Prov, M> InterpCx<'tcx, M>
139where
140    Prov: Provenance,
141    M: Machine<'tcx, Provenance = Prov>,
142{
143    /// Offset a pointer to project to a field of a struct/union. Unlike `place_field`, this is
144    /// always possible without allocating, so it can take `&self`. Also return the field's layout.
145    /// This supports both struct and array fields, but not slices!
146    ///
147    /// This also works for arrays, but then the `FieldIdx` index type is restricting.
148    /// For indexing into arrays, use [`Self::project_index`].
149    pub fn project_field<P: Projectable<'tcx, M::Provenance>>(
150        &self,
151        base: &P,
152        field: FieldIdx,
153    ) -> InterpResult<'tcx, P> {
154        // Slices nominally have length 0, so they will panic somewhere in `fields.offset`.
155        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        // Computing the layout does normalization, so we get a normalized type out of this
161        // even if the field type is non-normalized (possible e.g. via associated types).
162        let field_layout = base.layout().field(self, field.as_usize());
163
164        // Offset may need adjustment for unsized fields.
165        let (meta, offset) = if field_layout.is_unsized() {
166            assert!(!base.layout().is_sized());
167            let base_meta = base.meta();
168            // Re-use parent metadata to determine dynamic field layout.
169            // With custom DSTS, this *will* execute user-defined code, but the same
170            // happens at run-time so that's okay.
171            match self.size_and_align_from_meta(&base_meta, &field_layout)? {
172                Some((_, align)) => {
173                    // For packed types, we need to cap alignment.
174                    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                    // If the offset is 0, then rounding it up to alignment wouldn't change anything,
185                    // so we can do this even for types where we cannot determine the alignment.
186                    (base_meta, offset)
187                }
188                None => {
189                    // We cannot know the alignment of this field, so we cannot adjust.
190                    throw_unsup!(ExternTypeField)
191                }
192            }
193        } else {
194            // base_meta could be present; we might be accessing a sized field of an unsized
195            // struct.
196            (MemPlaceMeta::None, offset)
197        };
198
199        base.offset_with_meta(offset, OffsetMode::Inbounds, meta, field_layout, self)
200    }
201
202    /// Projects multiple fields at once. See [`Self::project_field`] for details.
203    pub fn project_fields<P: Projectable<'tcx, M::Provenance>, const N: usize>(
204        &self,
205        base: &P,
206        fields: [FieldIdx; N],
207    ) -> InterpResult<'tcx, [P; N]> {
208        fields.try_map(|field| self.project_field(base, field))
209    }
210
211    /// Downcasting to an enum variant.
212    pub fn project_downcast<P: Projectable<'tcx, M::Provenance>>(
213        &self,
214        base: &P,
215        variant: VariantIdx,
216    ) -> InterpResult<'tcx, P> {
217        assert!(!base.meta().has_meta());
218        // Downcasts only change the layout.
219        // (In particular, no check about whether this is even the active variant -- that's by design,
220        // see https://github.com/rust-lang/rust/issues/93688#issuecomment-1032929496.)
221        // So we just "offset" by 0.
222        let layout = base.layout().for_variant(self, variant);
223        // This variant may in fact be uninhabited.
224        // See <https://github.com/rust-lang/rust/issues/120337>.
225
226        // This cannot be `transmute` as variants *can* have a smaller size than the entire enum.
227        base.offset(Size::ZERO, layout, self)
228    }
229
230    /// Compute the offset and field layout for accessing the given index.
231    pub fn project_index<P: Projectable<'tcx, M::Provenance>>(
232        &self,
233        base: &P,
234        index: u64,
235    ) -> InterpResult<'tcx, P> {
236        // Not using the layout method because we want to compute on u64
237        let (offset, field_layout) = match base.layout().fields {
238            abi::FieldsShape::Array { stride, count: _ } => {
239                // `count` is nonsense for slices, use the dynamic length instead.
240                let len = base.len(self)?;
241                if index >= len {
242                    // This can only be reached in ConstProp and non-rustc-MIR.
243                    throw_ub!(BoundsCheckFailed { len, index });
244                }
245                // With raw slices, `len` can be so big that this *can* overflow.
246                let offset = self
247                    .compute_size_in_bytes(stride, index)
248                    .ok_or_else(|| err_ub!(PointerArithOverflow))?;
249
250                // All fields have the same layout.
251                let field_layout = base.layout().field(self, 0);
252                (offset, field_layout)
253            }
254            _ => span_bug!(
255                self.cur_span(),
256                "`project_index` called on non-array type {:?}",
257                base.layout().ty
258            ),
259        };
260
261        base.offset(offset, field_layout, self)
262    }
263
264    /// Converts a repr(simd) value into an array of the right size, such that `project_index`
265    /// accesses the SIMD elements. Also returns the number of elements.
266    pub fn project_to_simd<P: Projectable<'tcx, M::Provenance>>(
267        &self,
268        base: &P,
269    ) -> InterpResult<'tcx, (P, u64)> {
270        assert!(base.layout().ty.ty_adt_def().unwrap().repr().simd());
271        // SIMD types must be newtypes around arrays, so all we have to do is project to their only field.
272        let array = self.project_field(base, FieldIdx::ZERO)?;
273        let len = array.len(self)?;
274        interp_ok((array, len))
275    }
276
277    fn project_constant_index<P: Projectable<'tcx, M::Provenance>>(
278        &self,
279        base: &P,
280        offset: u64,
281        min_length: u64,
282        from_end: bool,
283    ) -> InterpResult<'tcx, P> {
284        let n = base.len(self)?;
285        if n < min_length {
286            // This can only be reached in ConstProp and non-rustc-MIR.
287            throw_ub!(BoundsCheckFailed { len: min_length, index: n });
288        }
289
290        let index = if from_end {
291            assert!(0 < offset && offset <= min_length);
292            n.checked_sub(offset).unwrap()
293        } else {
294            assert!(offset < min_length);
295            offset
296        };
297
298        self.project_index(base, index)
299    }
300
301    /// Iterates over all fields of an array. Much more efficient than doing the
302    /// same by repeatedly calling `project_index`.
303    pub fn project_array_fields<'a, P: Projectable<'tcx, M::Provenance>>(
304        &self,
305        base: &'a P,
306    ) -> InterpResult<'tcx, ArrayIterator<'a, 'tcx, M::Provenance, P>> {
307        let abi::FieldsShape::Array { stride, .. } = base.layout().fields else {
308            span_bug!(
309                self.cur_span(),
310                "project_array_fields: expected an array layout, got {:#?}",
311                base.layout()
312            );
313        };
314        let len = base.len(self)?;
315        let field_layout = base.layout().field(self, 0);
316        // Ensure that all the offsets are in-bounds once, up-front.
317        debug!("project_array_fields: {base:?} {len}");
318        base.offset(len * stride, self.layout_of(self.tcx.types.unit).unwrap(), self)?;
319        // Create the iterator.
320        interp_ok(ArrayIterator {
321            base,
322            range: 0..len,
323            stride,
324            field_layout,
325            _phantom: PhantomData,
326        })
327    }
328
329    /// Subslicing
330    fn project_subslice<P: Projectable<'tcx, M::Provenance>>(
331        &self,
332        base: &P,
333        from: u64,
334        to: u64,
335        from_end: bool,
336    ) -> InterpResult<'tcx, P> {
337        let len = base.len(self)?; // also asserts that we have a type where this makes sense
338        let actual_to = if from_end {
339            if from.checked_add(to).is_none_or(|to| to > len) {
340                // This can only be reached in ConstProp and non-rustc-MIR.
341                throw_ub!(BoundsCheckFailed { len, index: from.saturating_add(to) });
342            }
343            len.checked_sub(to).unwrap()
344        } else {
345            to
346        };
347
348        // Not using layout method because that works with usize, and does not work with slices
349        // (that have count 0 in their layout).
350        let from_offset = match base.layout().fields {
351            abi::FieldsShape::Array { stride, .. } => stride * from, // `Size` multiplication is checked
352            _ => {
353                span_bug!(
354                    self.cur_span(),
355                    "unexpected layout of index access: {:#?}",
356                    base.layout()
357                )
358            }
359        };
360
361        // Compute meta and new layout
362        let inner_len = actual_to.checked_sub(from).unwrap();
363        let (meta, ty) = match base.layout().ty.kind() {
364            // It is not nice to match on the type, but that seems to be the only way to
365            // implement this.
366            ty::Array(inner, _) => {
367                (MemPlaceMeta::None, Ty::new_array(self.tcx.tcx, *inner, inner_len))
368            }
369            ty::Slice(..) => {
370                let len = Scalar::from_target_usize(inner_len, self);
371                (MemPlaceMeta::Meta(len), base.layout().ty)
372            }
373            _ => {
374                span_bug!(
375                    self.cur_span(),
376                    "cannot subslice non-array type: `{:?}`",
377                    base.layout().ty
378                )
379            }
380        };
381        let layout = self.layout_of(ty)?;
382
383        base.offset_with_meta(from_offset, OffsetMode::Inbounds, meta, layout, self)
384    }
385
386    /// Applying a general projection
387    #[instrument(skip(self), level = "trace")]
388    pub fn project<P>(&self, base: &P, proj_elem: mir::PlaceElem<'tcx>) -> InterpResult<'tcx, P>
389    where
390        P: Projectable<'tcx, M::Provenance> + From<MPlaceTy<'tcx, M::Provenance>> + std::fmt::Debug,
391    {
392        use rustc_middle::mir::ProjectionElem::*;
393        interp_ok(match proj_elem {
394            OpaqueCast(ty) => {
395                span_bug!(self.cur_span(), "OpaqueCast({ty}) encountered after borrowck")
396            }
397            UnwrapUnsafeBinder(target) => base.transmute(self.layout_of(target)?, self)?,
398            // We don't want anything happening here, this is here as a dummy.
399            Subtype(_) => base.transmute(base.layout(), self)?,
400            Field(field, _) => self.project_field(base, field)?,
401            Downcast(_, variant) => self.project_downcast(base, variant)?,
402            Deref => self.deref_pointer(&base.to_op(self)?)?.into(),
403            Index(local) => {
404                let layout = self.layout_of(self.tcx.types.usize)?;
405                let n = self.local_to_op(local, Some(layout))?;
406                let n = self.read_target_usize(&n)?;
407                self.project_index(base, n)?
408            }
409            ConstantIndex { offset, min_length, from_end } => {
410                self.project_constant_index(base, offset, min_length, from_end)?
411            }
412            Subslice { from, to, from_end } => self.project_subslice(base, from, to, from_end)?,
413        })
414    }
415}