Skip to content

Commit 187d05f

Browse files
committed
Add hooks for Miri panic unwinding
This commits adds in some additional hooks to allow Miri to properly handle panic unwinding. None of this should have any impact on CTFE mode
1 parent 56237d7 commit 187d05f

File tree

8 files changed

+211
-59
lines changed

8 files changed

+211
-59
lines changed

src/librustc_mir/const_eval.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::interpret::{self,
2424
PlaceTy, MPlaceTy, OpTy, ImmTy, Immediate, Scalar, Pointer,
2525
RawConst, ConstValue, Machine,
2626
InterpResult, InterpErrorInfo, GlobalId, InterpCx, StackPopCleanup,
27-
Allocation, AllocId, MemoryKind, Memory,
27+
Allocation, AllocId, MemoryKind, Memory, StackPopInfo,
2828
snapshot, RefTracking, intern_const_alloc_recursive,
2929
};
3030

@@ -325,6 +325,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
325325
args: &[OpTy<'tcx>],
326326
dest: Option<PlaceTy<'tcx>>,
327327
ret: Option<mir::BasicBlock>,
328+
_unwind: Option<mir::BasicBlock> // unwinding is not supported in consts
328329
) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> {
329330
debug!("eval_fn_call: {:?}", instance);
330331
// Only check non-glue functions
@@ -374,7 +375,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
374375
span: Span,
375376
instance: ty::Instance<'tcx>,
376377
args: &[OpTy<'tcx>],
377-
dest: PlaceTy<'tcx>,
378+
dest: Option<PlaceTy<'tcx>>,
378379
) -> InterpResult<'tcx> {
379380
if ecx.emulate_intrinsic(span, instance, args, dest)? {
380381
return Ok(());
@@ -472,8 +473,10 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
472473

473474
/// Called immediately before a stack frame gets popped.
474475
#[inline(always)]
475-
fn stack_pop(_ecx: &mut InterpCx<'mir, 'tcx, Self>, _extra: ()) -> InterpResult<'tcx> {
476-
Ok(())
476+
fn stack_pop(
477+
_ecx: &mut InterpCx<'mir, 'tcx, Self>, _extra: ()) -> InterpResult<'tcx, StackPopInfo> {
478+
// Const-eval mode does not support unwinding from panics
479+
Ok(StackPopInfo::Normal)
477480
}
478481
}
479482

src/librustc_mir/interpret/eval_context.rs

Lines changed: 141 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use rustc_data_structures::fx::FxHashMap;
2121

2222
use super::{
2323
Immediate, Operand, MemPlace, MPlaceTy, Place, PlaceTy, ScalarMaybeUndef,
24-
Memory, Machine
24+
Memory, Machine, PointerArithmetic, FnVal, StackPopInfo
2525
};
2626

2727
pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
@@ -96,7 +96,9 @@ pub enum StackPopCleanup {
9696
/// Jump to the next block in the caller, or cause UB if None (that's a function
9797
/// that may never return). Also store layout of return place so
9898
/// we can validate it at that layout.
99-
Goto(Option<mir::BasicBlock>),
99+
/// 'ret' stores the block we jump to on a normal return, while 'unwind'
100+
/// stores the block used for cleanup during unwinding
101+
Goto { ret: Option<mir::BasicBlock>, unwind: Option<mir::BasicBlock> },
100102
/// Just do nohing: Used by Main and for the box_alloc hook in miri.
101103
/// `cleanup` says whether locals are deallocated. Static computation
102104
/// wants them leaked to intern what they need (and just throw away
@@ -547,56 +549,142 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
547549
}
548550
}
549551

550-
pub(super) fn pop_stack_frame(&mut self) -> InterpResult<'tcx> {
551-
info!("LEAVING({}) {}", self.cur_frame(), self.frame().instance);
552+
pub(super) fn pop_stack_frame_internal(
553+
&mut self,
554+
unwinding: bool
555+
) -> InterpResult<'tcx, (StackPopCleanup, StackPopInfo)> {
556+
info!("LEAVING({}) {} (unwinding = {})",
557+
self.cur_frame(), self.frame().instance, unwinding);
558+
552559
::log_settings::settings().indentation -= 1;
553560
let frame = self.stack.pop().expect(
554561
"tried to pop a stack frame, but there were none",
555562
);
556-
M::stack_pop(self, frame.extra)?;
563+
let stack_pop_info = M::stack_pop(self, frame.extra)?;
564+
557565
// Abort early if we do not want to clean up: We also avoid validation in that case,
558566
// because this is CTFE and the final value will be thoroughly validated anyway.
559567
match frame.return_to_block {
560-
StackPopCleanup::Goto(_) => {},
561-
StackPopCleanup::None { cleanup } => {
568+
StackPopCleanup::Goto{ .. } => {},
569+
StackPopCleanup::None { cleanup, .. } => {
570+
assert!(!unwinding, "Encountered StackPopCleanup::None while unwinding");
571+
562572
if !cleanup {
563573
assert!(self.stack.is_empty(), "only the topmost frame should ever be leaked");
564574
// Leak the locals, skip validation.
565-
return Ok(());
575+
return Ok((frame.return_to_block, stack_pop_info));
566576
}
567577
}
568578
}
569579
// Deallocate all locals that are backed by an allocation.
570580
for local in frame.locals {
571581
self.deallocate_local(local.value)?;
572582
}
573-
// Validate the return value. Do this after deallocating so that we catch dangling
574-
// references.
575-
if let Some(return_place) = frame.return_place {
576-
if M::enforce_validity(self) {
577-
// Data got changed, better make sure it matches the type!
578-
// It is still possible that the return place held invalid data while
579-
// the function is running, but that's okay because nobody could have
580-
// accessed that same data from the "outside" to observe any broken
581-
// invariant -- that is, unless a function somehow has a ptr to
582-
// its return place... but the way MIR is currently generated, the
583-
// return place is always a local and then this cannot happen.
584-
self.validate_operand(
585-
self.place_to_op(return_place)?,
586-
vec![],
587-
None,
588-
)?;
583+
584+
// If we're popping frames due to unwinding, and we didn't just exit
585+
// unwinding, we skip a bunch of validation and cleanup logic (including
586+
// jumping to the regular return block specified in the StackPopCleanu)
587+
let cur_unwinding = unwinding && stack_pop_info != StackPopInfo::StopUnwinding;
588+
589+
info!("StackPopCleanup: {:?} StackPopInfo: {:?} cur_unwinding = {:?}",
590+
frame.return_to_block, stack_pop_info, cur_unwinding);
591+
592+
593+
// When we're popping a stack frame for unwinding purposes,
594+
// we don't care at all about returning-related stuff (i.e. return_place
595+
// and return_to_block), because we're not performing a return from this frame.
596+
if !cur_unwinding {
597+
// Validate the return value. Do this after deallocating so that we catch dangling
598+
// references.
599+
if let Some(return_place) = frame.return_place {
600+
if M::enforce_validity(self) {
601+
// Data got changed, better make sure it matches the type!
602+
// It is still possible that the return place held invalid data while
603+
// the function is running, but that's okay because nobody could have
604+
// accessed that same data from the "outside" to observe any broken
605+
// invariant -- that is, unless a function somehow has a ptr to
606+
// its return place... but the way MIR is currently generated, the
607+
// return place is always a local and then this cannot happen.
608+
self.validate_operand(
609+
self.place_to_op(return_place)?,
610+
vec![],
611+
None,
612+
)?;
613+
}
614+
} else {
615+
// Uh, that shouldn't happen... the function did not intend to return
616+
throw_ub!(Unreachable);
617+
}
618+
619+
// Jump to new block -- *after* validation so that the spans make more sense.
620+
match frame.return_to_block {
621+
StackPopCleanup::Goto { ret, .. } => {
622+
self.goto_block(ret)?;
623+
}
624+
StackPopCleanup::None { .. } => {}
589625
}
590-
} else {
591-
// Uh, that shouldn't happen... the function did not intend to return
592-
throw_ub!(Unreachable)
593626
}
594-
// Jump to new block -- *after* validation so that the spans make more sense.
595-
match frame.return_to_block {
596-
StackPopCleanup::Goto(block) => {
597-
self.goto_block(block)?;
627+
628+
629+
Ok((frame.return_to_block, stack_pop_info))
630+
}
631+
632+
pub(super) fn pop_stack_frame(&mut self, unwinding: bool) -> InterpResult<'tcx> {
633+
let (mut cleanup, mut stack_pop_info) = self.pop_stack_frame_internal(unwinding)?;
634+
635+
// There are two cases where we want to unwind the stack:
636+
// * The caller explicitly told us (i.e. we hit a Resume terminator)
637+
// * The machine indicated that we've just started unwinding (i.e.
638+
// a panic has just occured)
639+
if unwinding || stack_pop_info == StackPopInfo::StartUnwinding {
640+
trace!("unwinding: starting stack unwind...");
641+
// Overwrite our current stack_pop_info, so that the check
642+
// below doesn't fail.
643+
stack_pop_info = StackPopInfo::Normal;
644+
// There are three posible ways that we can exit the loop:
645+
// 1) We find an unwind block - we jump to it to allow cleanup
646+
// to occur for that frame
647+
// 2) pop_stack_frame_internal reports that we're no longer unwinding
648+
// - this means that the panic has been caught, and that execution
649+
// should continue as normal
650+
// 3) We pop all of our frames off the stack - this should never happen.
651+
while !self.stack.is_empty() {
652+
match stack_pop_info {
653+
// We tried to start unwinding while we were already
654+
// unwinding. Note that this **is not** the same thing
655+
// as a double panic, which will be intercepted by
656+
// libcore/libstd before we actually attempt to unwind.
657+
StackPopInfo::StartUnwinding => {
658+
throw_ub_format!("Attempted to start unwinding while already unwinding!");
659+
},
660+
StackPopInfo::StopUnwinding => {
661+
trace!("unwinding: no longer unwinding!");
662+
break;
663+
}
664+
StackPopInfo::Normal => {}
665+
}
666+
667+
match cleanup {
668+
StackPopCleanup::Goto { unwind, .. } if unwind.is_some() => {
669+
670+
info!("unwind: found cleanup block {:?}", unwind);
671+
self.goto_block(unwind)?;
672+
break;
673+
},
674+
_ => {}
675+
}
676+
677+
info!("unwinding: popping frame!");
678+
let res = self.pop_stack_frame_internal(true)?;
679+
cleanup = res.0;
680+
stack_pop_info = res.1;
681+
}
682+
if self.stack.is_empty() {
683+
// We should never get here:
684+
// The 'start_fn' lang item should always install a panic handler
685+
throw_ub!(Unreachable);
598686
}
599-
StackPopCleanup::None { .. } => {}
687+
600688
}
601689

602690
if self.stack.len() > 0 {
@@ -760,4 +848,25 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
760848
trace!("generate stacktrace: {:#?}, {:?}", frames, explicit_span);
761849
frames
762850
}
851+
852+
/// Resolve the function at the specified slot in the provided
853+
/// vtable. An index of '0' corresponds to the first method
854+
/// declared in the trait of the provided vtable
855+
pub fn get_vtable_slot(
856+
&self,
857+
vtable: Scalar<M::PointerTag>,
858+
idx: usize
859+
) -> InterpResult<'tcx, FnVal<'tcx, M::ExtraFnVal>> {
860+
let ptr_size = self.pointer_size();
861+
// Skip over the 'drop_ptr', 'size', and 'align' fields
862+
let vtable_slot = vtable.ptr_offset(ptr_size * (idx as u64 + 3), self)?;
863+
let vtable_slot = self.memory.check_ptr_access(
864+
vtable_slot,
865+
ptr_size,
866+
self.tcx.data_layout.pointer_align.abi,
867+
)?.expect("cannot be a ZST");
868+
let fn_ptr = self.memory.get(vtable_slot.alloc_id)?
869+
.read_ptr_sized(self, vtable_slot)?.not_undef()?;
870+
Ok(self.memory.get_fn(fn_ptr)?)
871+
}
763872
}

src/librustc_mir/interpret/intrinsics.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,18 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
9191
span: Span,
9292
instance: ty::Instance<'tcx>,
9393
args: &[OpTy<'tcx, M::PointerTag>],
94-
dest: PlaceTy<'tcx, M::PointerTag>,
94+
dest: Option<PlaceTy<'tcx, M::PointerTag>>,
9595
) -> InterpResult<'tcx, bool> {
9696
let substs = instance.substs;
9797

98-
let intrinsic_name = &*self.tcx.item_name(instance.def_id()).as_str();
98+
// The intrinsic itself cannot diverge, so if we got here without a return
99+
// place... (can happen e.g., for transmute returning `!`)
100+
let dest = match dest {
101+
Some(dest) => dest,
102+
None => throw_ub!(Unreachable)
103+
};
104+
let intrinsic_name = &self.tcx.item_name(instance.def_id()).as_str();
105+
99106
match intrinsic_name {
100107
"caller_location" => {
101108
let topmost = span.ctxt().outer_expn().expansion_cause().unwrap_or(span);

src/librustc_mir/interpret/machine.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ use super::{
1616
Frame, Operand,
1717
};
1818

19+
/// Data returned by Machine::stack_pop,
20+
/// to provide further control over the popping of the stack frame
21+
#[derive(Eq, PartialEq, Debug)]
22+
pub enum StackPopInfo {
23+
/// Indicates that we have just started unwinding
24+
/// as the result of panic
25+
StartUnwinding,
26+
27+
/// Indicates that we performed a normal return
28+
Normal,
29+
30+
/// Indicates that we should stop unwinding,
31+
/// as we've reached a catch frame
32+
StopUnwinding
33+
}
34+
1935
/// Whether this kind of memory is allowed to leak
2036
pub trait MayLeak: Copy {
2137
fn may_leak(self) -> bool;
@@ -137,6 +153,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
137153
args: &[OpTy<'tcx, Self::PointerTag>],
138154
dest: Option<PlaceTy<'tcx, Self::PointerTag>>,
139155
ret: Option<mir::BasicBlock>,
156+
unwind: Option<mir::BasicBlock>
140157
) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>>;
141158

142159
/// Execute `fn_val`. it is the hook's responsibility to advance the instruction
@@ -156,7 +173,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
156173
span: Span,
157174
instance: ty::Instance<'tcx>,
158175
args: &[OpTy<'tcx, Self::PointerTag>],
159-
dest: PlaceTy<'tcx, Self::PointerTag>,
176+
dest: Option<PlaceTy<'tcx, Self::PointerTag>>,
160177
) -> InterpResult<'tcx>;
161178

162179
/// Called for read access to a foreign static item.
@@ -253,7 +270,7 @@ pub trait Machine<'mir, 'tcx>: Sized {
253270
fn stack_pop(
254271
ecx: &mut InterpCx<'mir, 'tcx, Self>,
255272
extra: Self::FrameExtra,
256-
) -> InterpResult<'tcx>;
273+
) -> InterpResult<'tcx, StackPopInfo>;
257274

258275
fn int_to_ptr(
259276
_mem: &Memory<'mir, 'tcx, Self>,

src/librustc_mir/interpret/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub use self::place::{Place, PlaceTy, MemPlace, MPlaceTy};
2626

2727
pub use self::memory::{Memory, MemoryKind, AllocCheck, FnVal};
2828

29-
pub use self::machine::{Machine, AllocMap, MayLeak};
29+
pub use self::machine::{Machine, AllocMap, MayLeak, StackPopInfo};
3030

3131
pub use self::operand::{ScalarMaybeUndef, Immediate, ImmTy, Operand, OpTy};
3232

src/librustc_mir/interpret/snapshot.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ impl<'a, Ctx> Snapshot<'a, Ctx> for &'a Allocation
315315
}
316316

317317
impl_stable_hash_for!(enum crate::interpret::eval_context::StackPopCleanup {
318-
Goto(block),
318+
Goto { ret, unwind },
319319
None { cleanup },
320320
});
321321

src/librustc_mir/interpret/step.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
290290

291291
let old_stack = self.cur_frame();
292292
let old_bb = self.frame().block;
293+
293294
self.eval_terminator(terminator)?;
294295
if !self.stack.is_empty() {
295296
// This should change *something*

0 commit comments

Comments
 (0)