use std::fmt;
use std::mem;
-use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_hir::{self as hir, def_id::DefId, definitions::DefPathData};
use rustc_index::vec::IndexVec;
-use rustc_macros::HashStable;
use rustc_middle::mir;
use rustc_middle::mir::interpret::{InterpError, InvalidProgramInfo};
use rustc_middle::ty::layout::{
use rustc_middle::ty::{
self, query::TyCtxtAt, subst::SubstsRef, ParamEnv, Ty, TyCtxt, TypeFoldable,
};
-use rustc_mir_dataflow::storage::always_live_locals;
-use rustc_query_system::ich::StableHashingContext;
+use rustc_mir_dataflow::storage::always_storage_live_locals;
use rustc_session::Limit;
use rustc_span::{Pos, Span};
use rustc_target::abi::{call::FnAbi, Align, HasDataLayout, Size, TargetDataLayout};
}
/// A stack frame.
-pub struct Frame<'mir, 'tcx, Tag: Provenance = AllocId, Extra = ()> {
+pub struct Frame<'mir, 'tcx, Prov: Provenance = AllocId, Extra = ()> {
////////////////////////////////////////////////////////////////////////////////
// Function and callsite information
////////////////////////////////////////////////////////////////////////////////
/// The location where the result of the current stack frame should be written to,
/// and its layout in the caller.
- pub return_place: PlaceTy<'tcx, Tag>,
+ pub return_place: PlaceTy<'tcx, Prov>,
/// The list of locals for this stack frame, stored in order as
/// `[return_ptr, arguments..., variables..., temporaries...]`.
/// The locals are stored as `Option<Value>`s.
/// `None` represents a local that is currently dead, while a live local
/// can either directly contain `Scalar` or refer to some part of an `Allocation`.
- pub locals: IndexVec<mir::Local, LocalState<'tcx, Tag>>,
+ ///
+ /// Do *not* access this directly; always go through the machine hook!
+ pub locals: IndexVec<mir::Local, LocalState<'tcx, Prov>>,
/// The span of the `tracing` crate is stored here.
/// When the guard is dropped, the span is exited. This gives us
}
/// Unwind information.
-#[derive(Clone, Copy, Eq, PartialEq, Debug, HashStable)]
+#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum StackPopUnwind {
/// The cleanup block.
Cleanup(mir::BasicBlock),
NotAllowed,
}
-#[derive(Clone, Copy, Eq, PartialEq, Debug, HashStable)] // Miri debug-prints these
+#[derive(Clone, Copy, Eq, PartialEq, Debug)] // Miri debug-prints these
pub enum StackPopCleanup {
/// Jump to the next block in the caller, or cause UB if None (that's a function
/// that may never return). Also store layout of return place so
}
/// State of a local variable including a memoized layout
-#[derive(Clone, Debug, PartialEq, Eq, HashStable)]
-pub struct LocalState<'tcx, Tag: Provenance = AllocId> {
- pub value: LocalValue<Tag>,
+#[derive(Clone, Debug)]
+pub struct LocalState<'tcx, Prov: Provenance = AllocId> {
+ pub value: LocalValue<Prov>,
/// Don't modify if `Some`, this is only used to prevent computing the layout twice
- #[stable_hasher(ignore)]
pub layout: Cell<Option<TyAndLayout<'tcx>>>,
}
/// Current value of a local variable
-#[derive(Copy, Clone, PartialEq, Eq, HashStable, Debug)] // Miri debug-prints these
-pub enum LocalValue<Tag: Provenance = AllocId> {
+#[derive(Copy, Clone, Debug)] // Miri debug-prints these
+pub enum LocalValue<Prov: Provenance = AllocId> {
/// This local is not currently alive, and cannot be used at all.
Dead,
- /// This local is alive but not yet allocated. It cannot be read from or have its address taken,
- /// and will be allocated on the first write. This is to support unsized locals, where we cannot
- /// know their size in advance.
- Unallocated,
/// A normal, live local.
/// Mostly for convenience, we re-use the `Operand` type here.
/// This is an optimization over just always having a pointer here;
/// we can thus avoid doing an allocation when the local just stores
/// immediate values *and* never has its address taken.
- Live(Operand<Tag>),
+ Live(Operand<Prov>),
}
-impl<'tcx, Tag: Provenance + 'static> LocalState<'tcx, Tag> {
+impl<'tcx, Prov: Provenance + 'static> LocalState<'tcx, Prov> {
/// Read the local's value or error if the local is not yet live or not live anymore.
///
/// Note: This may only be invoked from the `Machine::access_local` hook and not from
/// anywhere else. You may be invalidating machine invariants if you do!
- pub fn access(&self) -> InterpResult<'tcx, Operand<Tag>> {
- match self.value {
- LocalValue::Dead => throw_ub!(DeadLocal),
- LocalValue::Unallocated => {
- bug!("The type checker should prevent reading from a never-written local")
- }
+ #[inline]
+ pub fn access(&self) -> InterpResult<'tcx, &Operand<Prov>> {
+ match &self.value {
+ LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
LocalValue::Live(val) => Ok(val),
}
}
///
/// Note: This may only be invoked from the `Machine::access_local_mut` hook and not from
/// anywhere else. You may be invalidating machine invariants if you do!
- pub fn access_mut(
- &mut self,
- ) -> InterpResult<'tcx, Result<&mut LocalValue<Tag>, MemPlace<Tag>>> {
- match self.value {
- LocalValue::Dead => throw_ub!(DeadLocal),
- LocalValue::Live(Operand::Indirect(mplace)) => Ok(Err(mplace)),
- ref mut local @ (LocalValue::Live(Operand::Immediate(_)) | LocalValue::Unallocated) => {
- Ok(Ok(local))
- }
+ #[inline]
+ pub fn access_mut(&mut self) -> InterpResult<'tcx, &mut Operand<Prov>> {
+ match &mut self.value {
+ LocalValue::Dead => throw_ub!(DeadLocal), // could even be "invalid program"?
+ LocalValue::Live(val) => Ok(val),
}
}
}
-impl<'mir, 'tcx, Tag: Provenance> Frame<'mir, 'tcx, Tag> {
- pub fn with_extra<Extra>(self, extra: Extra) -> Frame<'mir, 'tcx, Tag, Extra> {
+impl<'mir, 'tcx, Prov: Provenance> Frame<'mir, 'tcx, Prov> {
+ pub fn with_extra<Extra>(self, extra: Extra) -> Frame<'mir, 'tcx, Prov, Extra> {
Frame {
body: self.body,
instance: self.instance,
}
}
-impl<'mir, 'tcx, Tag: Provenance, Extra> Frame<'mir, 'tcx, Tag, Extra> {
+impl<'mir, 'tcx, Prov: Provenance, Extra> Frame<'mir, 'tcx, Prov, Extra> {
/// Get the current location within the Frame.
///
/// If this is `Err`, we are not currently executing any particular statement in
#[inline(always)]
pub fn cur_span(&self) -> Span {
- self.stack()
- .iter()
- .rev()
- .find(|frame| !frame.instance.def.requires_caller_location(*self.tcx))
- .map_or(self.tcx.span, |f| f.current_span())
+ // This deliberately does *not* honor `requires_caller_location` since it is used for much
+ // more than just panics.
+ self.stack().last().map_or(self.tcx.span, |f| f.current_span())
}
#[inline(always)]
- pub(crate) fn stack(&self) -> &[Frame<'mir, 'tcx, M::PointerTag, M::FrameExtra>] {
+ pub(crate) fn stack(&self) -> &[Frame<'mir, 'tcx, M::Provenance, M::FrameExtra>] {
M::stack(self)
}
#[inline(always)]
pub(crate) fn stack_mut(
&mut self,
- ) -> &mut Vec<Frame<'mir, 'tcx, M::PointerTag, M::FrameExtra>> {
+ ) -> &mut Vec<Frame<'mir, 'tcx, M::Provenance, M::FrameExtra>> {
M::stack_mut(self)
}
}
#[inline(always)]
- pub fn frame(&self) -> &Frame<'mir, 'tcx, M::PointerTag, M::FrameExtra> {
+ pub fn frame(&self) -> &Frame<'mir, 'tcx, M::Provenance, M::FrameExtra> {
self.stack().last().expect("no call frames exist")
}
#[inline(always)]
- pub fn frame_mut(&mut self) -> &mut Frame<'mir, 'tcx, M::PointerTag, M::FrameExtra> {
+ pub fn frame_mut(&mut self) -> &mut Frame<'mir, 'tcx, M::Provenance, M::FrameExtra> {
self.stack_mut().last_mut().expect("no call frames exist")
}
/// stack frame), to bring it into the proper environment for this interpreter.
pub(super) fn subst_from_frame_and_normalize_erasing_regions<T: TypeFoldable<'tcx>>(
&self,
- frame: &Frame<'mir, 'tcx, M::PointerTag, M::FrameExtra>,
+ frame: &Frame<'mir, 'tcx, M::Provenance, M::FrameExtra>,
value: T,
) -> Result<T, InterpError<'tcx>> {
frame
#[inline(always)]
pub fn layout_of_local(
&self,
- frame: &Frame<'mir, 'tcx, M::PointerTag, M::FrameExtra>,
+ frame: &Frame<'mir, 'tcx, M::Provenance, M::FrameExtra>,
local: mir::Local,
layout: Option<TyAndLayout<'tcx>>,
) -> InterpResult<'tcx, TyAndLayout<'tcx>> {
/// This can fail to provide an answer for extern types.
pub(super) fn size_and_align_of(
&self,
- metadata: &MemPlaceMeta<M::PointerTag>,
+ metadata: &MemPlaceMeta<M::Provenance>,
layout: &TyAndLayout<'tcx>,
) -> InterpResult<'tcx, Option<(Size, Align)>> {
if !layout.is_unsized() {
Ok(Some((size, align)))
}
ty::Dynamic(..) => {
- let vtable = self.scalar_to_ptr(metadata.unwrap_meta())?;
+ let vtable = metadata.unwrap_meta().to_pointer(self)?;
// Read size and align from vtable (already checks size).
- Ok(Some(self.read_size_and_align_from_vtable(vtable)?))
+ Ok(Some(self.get_vtable_size_and_align(vtable)?))
}
ty::Slice(_) | ty::Str => {
#[inline]
pub fn size_and_align_of_mplace(
&self,
- mplace: &MPlaceTy<'tcx, M::PointerTag>,
+ mplace: &MPlaceTy<'tcx, M::Provenance>,
) -> InterpResult<'tcx, Option<(Size, Align)>> {
self.size_and_align_of(&mplace.meta, &mplace.layout)
}
&mut self,
instance: ty::Instance<'tcx>,
body: &'mir mir::Body<'tcx>,
- return_place: &PlaceTy<'tcx, M::PointerTag>,
+ return_place: &PlaceTy<'tcx, M::Provenance>,
return_to_block: StackPopCleanup,
) -> InterpResult<'tcx> {
trace!("body: {:#?}", body);
body,
loc: Err(body.span), // Span used for errors caused during preamble.
return_to_block,
- return_place: *return_place,
+ return_place: return_place.clone(),
// empty local array, we fill it in below, after we are inside the stack frame and
// all methods actually know about the frame
locals: IndexVec::new(),
})?;
}
- // Locals are initially unallocated.
- let dummy = LocalState { value: LocalValue::Unallocated, layout: Cell::new(None) };
+ // Most locals are initially dead.
+ let dummy = LocalState { value: LocalValue::Dead, layout: Cell::new(None) };
let mut locals = IndexVec::from_elem(dummy, &body.local_decls);
- // Now mark those locals as dead that we do not want to initialize
- // Mark locals that use `Storage*` annotations as dead on function entry.
- let always_live = always_live_locals(self.body());
+ // Now mark those locals as live that have no `Storage*` annotations.
+ let always_live = always_storage_live_locals(self.body());
for local in locals.indices() {
- if !always_live.contains(local) {
- locals[local].value = LocalValue::Dead;
+ if always_live.contains(local) {
+ locals[local].value = LocalValue::Live(Operand::Immediate(Immediate::Uninit));
}
}
// done
if unwinding { "during unwinding" } else { "returning from function" }
);
- // Sanity check `unwinding`.
+ // Check `unwinding`.
assert_eq!(
unwinding,
match self.frame().loc {
Err(_) => true,
}
);
-
if unwinding && self.frame_idx() == 0 {
throw_ub_format!("unwinding past the topmost frame of the stack");
}
- let frame =
- self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
-
- if !unwinding {
- let op = self.access_local(&frame, mir::RETURN_PLACE, None)?;
- self.copy_op_transmute(&op, &frame.return_place)?;
- trace!("{:?}", self.dump_place(*frame.return_place));
- }
-
- let return_to_block = frame.return_to_block;
-
- // Now where do we jump next?
+ // Copy return value. Must of course happen *before* we deallocate the locals.
+ let copy_ret_result = if !unwinding {
+ let op = self
+ .local_to_op(self.frame(), mir::RETURN_PLACE, None)
+ .expect("return place should always be live");
+ let dest = self.frame().return_place.clone();
+ let err = self.copy_op(&op, &dest, /*allow_transmute*/ true);
+ trace!("return value: {:?}", self.dump_place(*dest));
+ // We delay actually short-circuiting on this error until *after* the stack frame is
+ // popped, since we want this error to be attributed to the caller, whose type defines
+ // this transmute.
+ err
+ } else {
+ Ok(())
+ };
+ // Cleanup: deallocate locals.
// Usually we want to clean up (deallocate locals), but in a few rare cases we don't.
- // In that case, we return early. We also avoid validation in that case,
- // because this is CTFE and the final value will be thoroughly validated anyway.
+ // We do this while the frame is still on the stack, so errors point to the callee.
+ let return_to_block = self.frame().return_to_block;
let cleanup = match return_to_block {
StackPopCleanup::Goto { .. } => true,
StackPopCleanup::Root { cleanup, .. } => cleanup,
};
+ if cleanup {
+ // We need to take the locals out, since we need to mutate while iterating.
+ let locals = mem::take(&mut self.frame_mut().locals);
+ for local in &locals {
+ self.deallocate_local(local.value)?;
+ }
+ }
+ // All right, now it is time to actually pop the frame.
+ // Note that its locals are gone already, but that's fine.
+ let frame =
+ self.stack_mut().pop().expect("tried to pop a stack frame, but there were none");
+ // Report error from return value copy, if any.
+ copy_ret_result?;
+
+ // If we are not doing cleanup, also skip everything else.
if !cleanup {
assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked");
assert!(!unwinding, "tried to skip cleanup during unwinding");
- // Leak the locals, skip validation, skip machine hook.
+ // Skip machine hook.
return Ok(());
}
-
- trace!("locals: {:#?}", frame.locals);
-
- // Cleanup: deallocate all locals that are backed by an allocation.
- for local in &frame.locals {
- self.deallocate_local(local.value)?;
- }
-
if M::after_stack_pop(self, frame, unwinding)? == StackPopJump::NoJump {
// The hook already did everything.
- // We want to skip the `info!` below, hence early return.
return Ok(());
}
+
// Normal return, figure out where to jump.
if unwinding {
// Follow the unwind edge.
assert!(local != mir::RETURN_PLACE, "Cannot make return place live");
trace!("{:?} is now live", local);
- let local_val = LocalValue::Unallocated;
+ let local_val = LocalValue::Live(Operand::Immediate(Immediate::Uninit));
// StorageLive expects the local to be dead, and marks it live.
let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val);
if !matches!(old, LocalValue::Dead) {
}
#[instrument(skip(self), level = "debug")]
- fn deallocate_local(&mut self, local: LocalValue<M::PointerTag>) -> InterpResult<'tcx> {
+ fn deallocate_local(&mut self, local: LocalValue<M::Provenance>) -> InterpResult<'tcx> {
if let LocalValue::Live(Operand::Indirect(MemPlace { ptr, .. })) = local {
// All locals have a backing allocation, even if the allocation is empty
// due to the local having ZST type. Hence we can `unwrap`.
pub fn eval_to_allocation(
&self,
gid: GlobalId<'tcx>,
- ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::PointerTag>> {
+ ) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> {
// For statics we pick `ParamEnv::reveal_all`, because statics don't have generics
// and thus don't care about the parameter environment. While we could just use
// `self.param_env`, that would mean we invoke the query to evaluate the static
}
#[must_use]
- pub fn dump_place(&self, place: Place<M::PointerTag>) -> PlacePrinter<'_, 'mir, 'tcx, M> {
+ pub fn dump_place(&self, place: Place<M::Provenance>) -> PlacePrinter<'_, 'mir, 'tcx, M> {
PlacePrinter { ecx: self, place }
}
#[must_use]
pub fn generate_stacktrace(&self) -> Vec<FrameInfo<'tcx>> {
let mut frames = Vec::new();
- for frame in self
- .stack()
- .iter()
- .rev()
- .skip_while(|frame| frame.instance.def.requires_caller_location(*self.tcx))
- {
+ // This deliberately does *not* honor `requires_caller_location` since it is used for much
+ // more than just panics.
+ for frame in self.stack().iter().rev() {
let lint_root = frame.current_source_info().and_then(|source_info| {
match &frame.body.source_scopes[source_info.scope].local_data {
mir::ClearCrossCrate::Set(data) => Some(data.lint_root),
/// Helper struct for the `dump_place` function.
pub struct PlacePrinter<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> {
ecx: &'a InterpCx<'mir, 'tcx, M>,
- place: Place<M::PointerTag>,
+ place: Place<M::Provenance>,
}
impl<'a, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> std::fmt::Debug
match self.ecx.stack()[frame].locals[local].value {
LocalValue::Dead => write!(fmt, " is dead")?,
- LocalValue::Unallocated => write!(fmt, " is unallocated")?,
+ LocalValue::Live(Operand::Immediate(Immediate::Uninit)) => {
+ write!(fmt, " is uninitialized")?
+ }
LocalValue::Live(Operand::Indirect(mplace)) => {
write!(
fmt,
- " by align({}){} ref {:?}:",
- mplace.align.bytes(),
+ " by {} ref {:?}:",
match mplace.meta {
MemPlaceMeta::Meta(meta) => format!(" meta({:?})", meta),
- MemPlaceMeta::Poison | MemPlaceMeta::None => String::new(),
+ MemPlaceMeta::None => String::new(),
},
mplace.ptr,
)?;
write!(fmt, ": {:?}", self.ecx.dump_allocs(allocs.into_iter().flatten().collect()))
}
Place::Ptr(mplace) => match mplace.ptr.provenance.and_then(Provenance::get_alloc_id) {
- Some(alloc_id) => write!(
- fmt,
- "by align({}) ref {:?}: {:?}",
- mplace.align.bytes(),
- mplace.ptr,
- self.ecx.dump_alloc(alloc_id)
- ),
+ Some(alloc_id) => {
+ write!(fmt, "by ref {:?}: {:?}", mplace.ptr, self.ecx.dump_alloc(alloc_id))
+ }
ptr => write!(fmt, " integral by ref: {:?}", ptr),
},
}
}
}
-
-impl<'ctx, 'mir, 'tcx, Tag: Provenance, Extra> HashStable<StableHashingContext<'ctx>>
- for Frame<'mir, 'tcx, Tag, Extra>
-where
- Extra: HashStable<StableHashingContext<'ctx>>,
- Tag: HashStable<StableHashingContext<'ctx>>,
-{
- fn hash_stable(&self, hcx: &mut StableHashingContext<'ctx>, hasher: &mut StableHasher) {
- // Exhaustive match on fields to make sure we forget no field.
- let Frame {
- body,
- instance,
- return_to_block,
- return_place,
- locals,
- loc,
- extra,
- tracing_span: _,
- } = self;
- body.hash_stable(hcx, hasher);
- instance.hash_stable(hcx, hasher);
- return_to_block.hash_stable(hcx, hasher);
- return_place.hash_stable(hcx, hasher);
- locals.hash_stable(hcx, hasher);
- loc.hash_stable(hcx, hasher);
- extra.hash_stable(hcx, hasher);
- }
-}