//! This file implements "place projections"; basically a symmetric API for 3 types: MPlaceTy, OpTy, PlaceTy. //! //! OpTy and PlaceTy generally work by "let's see if we are actually an MPlaceTy, and do something custom if not". //! For PlaceTy, the custom thing is basically always to call `force_allocation` and then use the MPlaceTy logic anyway. //! For OpTy, the custom thing on field pojections has to be pretty clever (since `Operand::Immediate` can have fields), //! but for array/slice operations it only has to worry about `Operand::Uninit`. That makes the value part trivial, //! but we still need to do bounds checking and adjust the layout. To not duplicate that with MPlaceTy, we actually //! implement the logic on OpTy, and MPlaceTy calls that. use rustc_middle::mir; use rustc_middle::ty; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; use rustc_middle::ty::Ty; use rustc_target::abi::Size; use rustc_target::abi::{self, VariantIdx}; use super::{ InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy, PlaceTy, Provenance, Scalar, }; // FIXME: Working around https://github.com/rust-lang/rust/issues/54385 impl<'mir, 'tcx: 'mir, Prov, M> InterpCx<'mir, 'tcx, M> where Prov: Provenance + 'static, M: Machine<'mir, 'tcx, Provenance = Prov>, { //# Field access fn project_field( &self, base_layout: TyAndLayout<'tcx>, base_meta: MemPlaceMeta, field: usize, ) -> InterpResult<'tcx, (Size, MemPlaceMeta, TyAndLayout<'tcx>)> { let offset = base_layout.fields.offset(field); let field_layout = base_layout.field(self, field); // Offset may need adjustment for unsized fields. let (meta, offset) = if field_layout.is_unsized() { if base_layout.is_sized() { // An unsized field of a sized type? Sure... // But const-prop actually feeds us such nonsense MIR! throw_inval!(ConstPropNonsense); } // Re-use parent metadata to determine dynamic field layout. // With custom DSTS, this *will* execute user-defined code, but the same // happens at run-time so that's okay. match self.size_and_align_of(&base_meta, &field_layout)? { Some((_, align)) => (base_meta, offset.align_to(align)), None => { // For unsized types with an extern type tail we perform no adjustments. // NOTE: keep this in sync with `PlaceRef::project_field` in the codegen backend. assert!(matches!(base_meta, MemPlaceMeta::None)); (base_meta, offset) } } } else { // base_meta could be present; we might be accessing a sized field of an unsized // struct. (MemPlaceMeta::None, offset) }; Ok((offset, meta, field_layout)) } /// Offset a pointer to project to a field of a struct/union. Unlike `place_field`, this is /// always possible without allocating, so it can take `&self`. Also return the field's layout. /// This supports both struct and array fields. /// /// This also works for arrays, but then the `usize` index type is restricting. /// For indexing into arrays, use `mplace_index`. pub fn mplace_field( &self, base: &MPlaceTy<'tcx, M::Provenance>, field: usize, ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { let (offset, meta, field_layout) = self.project_field(base.layout, base.meta, field)?; // We do not look at `base.layout.align` nor `field_layout.align`, unlike // codegen -- mostly to see if we can get away with that base.offset_with_meta(offset, meta, field_layout, self) } /// Gets the place of a field inside the place, and also the field's type. pub fn place_field( &self, base: &PlaceTy<'tcx, M::Provenance>, field: usize, ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { let (offset, meta, field_layout) = self.project_field(base.layout, self.place_meta(base)?, field)?; base.offset_with_meta(offset, meta, field_layout, self) } pub fn operand_field( &self, base: &OpTy<'tcx, M::Provenance>, field: usize, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { let (offset, meta, field_layout) = self.project_field(base.layout, base.meta()?, field)?; base.offset_with_meta(offset, meta, field_layout, self) } //# Downcasting pub fn mplace_downcast( &self, base: &MPlaceTy<'tcx, M::Provenance>, variant: VariantIdx, ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { // Downcasts only change the layout. // (In particular, no check about whether this is even the active variant -- that's by design, // see https://github.com/rust-lang/rust/issues/93688#issuecomment-1032929496.) assert!(!base.meta.has_meta()); let mut base = *base; base.layout = base.layout.for_variant(self, variant); Ok(base) } pub fn place_downcast( &self, base: &PlaceTy<'tcx, M::Provenance>, variant: VariantIdx, ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { // Downcast just changes the layout let mut base = base.clone(); base.layout = base.layout.for_variant(self, variant); Ok(base) } pub fn operand_downcast( &self, base: &OpTy<'tcx, M::Provenance>, variant: VariantIdx, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { // Downcast just changes the layout let mut base = base.clone(); base.layout = base.layout.for_variant(self, variant); Ok(base) } //# Slice and array indexing /// Compute the offset and field layout for accessing the given index. fn project_index( &self, base_layout: TyAndLayout<'tcx>, base_meta: MemPlaceMeta, index: u64, ) -> InterpResult<'tcx, (Size, TyAndLayout<'tcx>)> { // Not using the layout method because we want to compute on u64 match base_layout.fields { abi::FieldsShape::Array { stride, count: _ } => { // `count` is nonsense for slices, use the dynamic length instead. let len = base_meta.len(base_layout, self)?; if index >= len { // This can only be reached in ConstProp and non-rustc-MIR. throw_ub!(BoundsCheckFailed { len, index }); } let offset = stride * index; // `Size` multiplication // All fields have the same layout. let field_layout = base_layout.field(self, 0); Ok((offset, field_layout)) } _ => span_bug!( self.cur_span(), "`mplace_index` called on non-array type {:?}", base_layout.ty ), } } #[inline(always)] pub fn operand_index( &self, base: &OpTy<'tcx, M::Provenance>, index: u64, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { let (offset, field_layout) = self.project_index(base.layout, base.meta()?, index)?; base.offset(offset, field_layout, self) } /// Index into an array. pub fn mplace_index( &self, base: &MPlaceTy<'tcx, M::Provenance>, index: u64, ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> { let (offset, field_layout) = self.project_index(base.layout, base.meta, index)?; base.offset(offset, field_layout, self) } pub fn place_index( &self, base: &PlaceTy<'tcx, M::Provenance>, index: u64, ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { let (offset, field_layout) = self.project_index(base.layout, self.place_meta(base)?, index)?; base.offset(offset, field_layout, self) } /// Iterates over all fields of an array. Much more efficient than doing the /// same by repeatedly calling `operand_index`. pub fn operand_array_fields<'a>( &self, base: &'a OpTy<'tcx, Prov>, ) -> InterpResult<'tcx, impl Iterator>> + 'a> { let abi::FieldsShape::Array { stride, .. } = base.layout.fields else { span_bug!(self.cur_span(), "operand_array_fields: expected an array layout"); }; let len = base.len(self)?; let field_layout = base.layout.field(self, 0); let dl = &self.tcx.data_layout; // `Size` multiplication Ok((0..len).map(move |i| base.offset(stride * i, field_layout, dl))) } /// Iterates over all fields of an array. Much more efficient than doing the /// same by repeatedly calling `place_index`. pub fn place_array_fields<'a>( &self, base: &'a PlaceTy<'tcx, Prov>, ) -> InterpResult<'tcx, impl Iterator>> + 'a> { let abi::FieldsShape::Array { stride, .. } = base.layout.fields else { span_bug!(self.cur_span(), "place_array_fields: expected an array layout"); }; let len = self.place_meta(base)?.len(base.layout, self)?; let field_layout = base.layout.field(self, 0); let dl = &self.tcx.data_layout; // `Size` multiplication Ok((0..len).map(move |i| base.offset(stride * i, field_layout, dl))) } //# ConstantIndex support fn project_constant_index( &self, base_layout: TyAndLayout<'tcx>, base_meta: MemPlaceMeta, offset: u64, min_length: u64, from_end: bool, ) -> InterpResult<'tcx, (Size, TyAndLayout<'tcx>)> { let n = base_meta.len(base_layout, self)?; if n < min_length { // This can only be reached in ConstProp and non-rustc-MIR. throw_ub!(BoundsCheckFailed { len: min_length, index: n }); } let index = if from_end { assert!(0 < offset && offset <= min_length); n.checked_sub(offset).unwrap() } else { assert!(offset < min_length); offset }; self.project_index(base_layout, base_meta, index) } fn operand_constant_index( &self, base: &OpTy<'tcx, M::Provenance>, offset: u64, min_length: u64, from_end: bool, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { let (offset, layout) = self.project_constant_index(base.layout, base.meta()?, offset, min_length, from_end)?; base.offset(offset, layout, self) } fn place_constant_index( &self, base: &PlaceTy<'tcx, M::Provenance>, offset: u64, min_length: u64, from_end: bool, ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { let (offset, layout) = self.project_constant_index( base.layout, self.place_meta(base)?, offset, min_length, from_end, )?; base.offset(offset, layout, self) } //# Subslicing fn project_subslice( &self, base_layout: TyAndLayout<'tcx>, base_meta: MemPlaceMeta, from: u64, to: u64, from_end: bool, ) -> InterpResult<'tcx, (Size, MemPlaceMeta, TyAndLayout<'tcx>)> { let len = base_meta.len(base_layout, self)?; // also asserts that we have a type where this makes sense let actual_to = if from_end { if from.checked_add(to).map_or(true, |to| to > len) { // This can only be reached in ConstProp and non-rustc-MIR. throw_ub!(BoundsCheckFailed { len: len, index: from.saturating_add(to) }); } len.checked_sub(to).unwrap() } else { to }; // Not using layout method because that works with usize, and does not work with slices // (that have count 0 in their layout). let from_offset = match base_layout.fields { abi::FieldsShape::Array { stride, .. } => stride * from, // `Size` multiplication is checked _ => { span_bug!(self.cur_span(), "unexpected layout of index access: {:#?}", base_layout) } }; // Compute meta and new layout let inner_len = actual_to.checked_sub(from).unwrap(); let (meta, ty) = match base_layout.ty.kind() { // It is not nice to match on the type, but that seems to be the only way to // implement this. ty::Array(inner, _) => { (MemPlaceMeta::None, Ty::new_array(self.tcx.tcx, *inner, inner_len)) } ty::Slice(..) => { let len = Scalar::from_target_usize(inner_len, self); (MemPlaceMeta::Meta(len), base_layout.ty) } _ => { span_bug!(self.cur_span(), "cannot subslice non-array type: `{:?}`", base_layout.ty) } }; let layout = self.layout_of(ty)?; Ok((from_offset, meta, layout)) } fn operand_subslice( &self, base: &OpTy<'tcx, M::Provenance>, from: u64, to: u64, from_end: bool, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { let (from_offset, meta, layout) = self.project_subslice(base.layout, base.meta()?, from, to, from_end)?; base.offset_with_meta(from_offset, meta, layout, self) } pub fn place_subslice( &self, base: &PlaceTy<'tcx, M::Provenance>, from: u64, to: u64, from_end: bool, ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { let (from_offset, meta, layout) = self.project_subslice(base.layout, self.place_meta(base)?, from, to, from_end)?; base.offset_with_meta(from_offset, meta, layout, self) } //# Applying a general projection /// Projects into a place. #[instrument(skip(self), level = "trace")] pub fn place_projection( &self, base: &PlaceTy<'tcx, M::Provenance>, proj_elem: mir::PlaceElem<'tcx>, ) -> InterpResult<'tcx, PlaceTy<'tcx, M::Provenance>> { use rustc_middle::mir::ProjectionElem::*; Ok(match proj_elem { OpaqueCast(ty) => { let mut place = base.clone(); place.layout = self.layout_of(ty)?; place } Field(field, _) => self.place_field(base, field.index())?, Downcast(_, variant) => self.place_downcast(base, variant)?, Deref => self.deref_operand(&self.place_to_op(base)?)?.into(), Index(local) => { let layout = self.layout_of(self.tcx.types.usize)?; let n = self.local_to_op(self.frame(), local, Some(layout))?; let n = self.read_target_usize(&n)?; self.place_index(base, n)? } ConstantIndex { offset, min_length, from_end } => { self.place_constant_index(base, offset, min_length, from_end)? } Subslice { from, to, from_end } => self.place_subslice(base, from, to, from_end)?, }) } #[instrument(skip(self), level = "trace")] pub fn operand_projection( &self, base: &OpTy<'tcx, M::Provenance>, proj_elem: mir::PlaceElem<'tcx>, ) -> InterpResult<'tcx, OpTy<'tcx, M::Provenance>> { use rustc_middle::mir::ProjectionElem::*; Ok(match proj_elem { OpaqueCast(ty) => { let mut op = base.clone(); op.layout = self.layout_of(ty)?; op } Field(field, _) => self.operand_field(base, field.index())?, Downcast(_, variant) => self.operand_downcast(base, variant)?, Deref => self.deref_operand(base)?.into(), Index(local) => { let layout = self.layout_of(self.tcx.types.usize)?; let n = self.local_to_op(self.frame(), local, Some(layout))?; let n = self.read_target_usize(&n)?; self.operand_index(base, n)? } ConstantIndex { offset, min_length, from_end } => { self.operand_constant_index(base, offset, min_length, from_end)? } Subslice { from, to, from_end } => self.operand_subslice(base, from, to, from_end)?, }) } }