1use 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_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 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 let layout = base.layout().for_variant(self, variant);
223 base.offset(Size::ZERO, layout, self)
228 }
229
230 pub fn project_index<P: Projectable<'tcx, M::Provenance>>(
232 &self,
233 base: &P,
234 index: u64,
235 ) -> InterpResult<'tcx, P> {
236 let (offset, field_layout) = match base.layout().fields {
238 abi::FieldsShape::Array { stride, count: _ } => {
239 let len = base.len(self)?;
241 if index >= len {
242 throw_ub!(BoundsCheckFailed { len, index });
244 }
245 let offset = self
247 .compute_size_in_bytes(stride, index)
248 .ok_or_else(|| err_ub!(PointerArithOverflow))?;
249
250 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 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 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 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 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 debug!("project_array_fields: {base:?} {len}");
318 base.offset(len * stride, self.layout_of(self.tcx.types.unit).unwrap(), self)?;
319 interp_ok(ArrayIterator {
321 base,
322 range: 0..len,
323 stride,
324 field_layout,
325 _phantom: PhantomData,
326 })
327 }
328
329 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)?; let actual_to = if from_end {
339 if from.checked_add(to).is_none_or(|to| to > len) {
340 throw_ub!(BoundsCheckFailed { len, index: from.saturating_add(to) });
342 }
343 len.checked_sub(to).unwrap()
344 } else {
345 to
346 };
347
348 let from_offset = match base.layout().fields {
351 abi::FieldsShape::Array { stride, .. } => stride * from, _ => {
353 span_bug!(
354 self.cur_span(),
355 "unexpected layout of index access: {:#?}",
356 base.layout()
357 )
358 }
359 };
360
361 let inner_len = actual_to.checked_sub(from).unwrap();
363 let (meta, ty) = match base.layout().ty.kind() {
364 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 #[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 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}