Skip to content

Commit 0e7a1d6

Browse files
committed
Support unwinding after a panic
Fixes rust-lang#658 This commit adds support for unwinding after a panic. It requires a companion rustc PR to be merged, in order for the necessary hooks to work properly. Currently implemented: * Selecting between unwind/abort mode based on the rustc Session * Properly popping off stack frames, unwinding back the caller * Running 'unwind' blocks in Mir terminators Not yet implemented: * 'Abort' terminators This PR was getting fairly large, so I decided to open it for review without implementing 'Abort' terminator support. This could either be added on to this PR, or merged separately.
1 parent 3899dce commit 0e7a1d6

File tree

14 files changed

+451
-60
lines changed

14 files changed

+451
-60
lines changed

src/eval.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use crate::{
1414
TlsEvalContextExt,
1515
};
1616

17+
use crate::machine::CachedTypes;
18+
1719
/// Configuration needed to spawn a Miri instance.
1820
#[derive(Clone)]
1921
pub struct MiriConfig {
@@ -38,11 +40,8 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
3840
let mut ecx = InterpCx::new(
3941
tcx.at(syntax::source_map::DUMMY_SP),
4042
ty::ParamEnv::reveal_all(),
41-
Evaluator::new(config.communicate),
42-
MemoryExtra::new(
43-
StdRng::seed_from_u64(config.seed.unwrap_or(0)),
44-
config.validate,
45-
),
43+
Evaluator::new(config.communicate, CachedTypes::new(tcx)?),
44+
MemoryExtra::new(StdRng::seed_from_u64(config.seed.unwrap_or(0)), config.validate),
4645
);
4746
// Complete initialization.
4847
EnvVars::init(&mut ecx, config.excluded_env_vars);

src/helpers.rs

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::mem;
22

3-
use rustc::ty::{self, layout::{self, Size, Align}};
3+
use rustc::ty::{self, TyCtxt, layout::{self, Size, Align}};
44
use rustc::hir::def_id::{DefId, CRATE_DEF_INDEX};
55
use rustc::mir;
66

@@ -10,32 +10,31 @@ use crate::*;
1010

1111
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
1212

13-
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
14-
/// Gets an instance for a path.
15-
fn resolve_path(&self, path: &[&str]) -> InterpResult<'tcx, ty::Instance<'tcx>> {
16-
let this = self.eval_context_ref();
17-
this.tcx
18-
.crates()
19-
.iter()
20-
.find(|&&krate| this.tcx.original_crate_name(krate).as_str() == path[0])
21-
.and_then(|krate| {
22-
let krate = DefId {
23-
krate: *krate,
24-
index: CRATE_DEF_INDEX,
25-
};
26-
let mut items = this.tcx.item_children(krate);
27-
let mut path_it = path.iter().skip(1).peekable();
28-
29-
while let Some(segment) = path_it.next() {
30-
for item in mem::replace(&mut items, Default::default()).iter() {
31-
if item.ident.name.as_str() == *segment {
32-
if path_it.peek().is_none() {
33-
return Some(ty::Instance::mono(this.tcx.tcx, item.res.def_id()));
34-
}
13+
/// Gets an instance for a path.
14+
pub fn resolve_did<'mir, 'tcx>(tcx: TyCtxt<'tcx>, path: &[&str]) -> InterpResult<'tcx, DefId> {
15+
tcx
16+
.crates()
17+
.iter()
18+
.find(|&&krate| tcx.original_crate_name(krate).as_str() == path[0])
19+
.and_then(|krate| {
20+
let krate = DefId {
21+
krate: *krate,
22+
index: CRATE_DEF_INDEX,
23+
};
24+
let mut items = tcx.item_children(krate);
25+
let mut path_it = path.iter().skip(1).peekable();
3526

36-
items = this.tcx.item_children(item.res.def_id());
37-
break;
27+
while let Some(segment) = path_it.next() {
28+
for item in mem::replace(&mut items, Default::default()).iter() {
29+
if item.ident.name.as_str() == *segment {
30+
if path_it.peek().is_none() {
31+
return Some(item.res.def_id())
32+
//eprintln!("Generics: {:?}", this.tcx.generics_of(item.res.def_id()));
33+
//return Some(ty::Instance::mono(this.tcx.tcx, item.res.def_id()).def_id());
3834
}
35+
36+
items = tcx.item_children(item.res.def_id());
37+
break;
3938
}
4039
}
4140
None
@@ -44,6 +43,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
4443
let path = path.iter().map(|&s| s.to_owned()).collect();
4544
err_unsup!(PathNotFound(path)).into()
4645
})
46+
}
47+
48+
49+
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
50+
51+
fn resolve_path(&self, path: &[&str]) -> InterpResult<'tcx, ty::Instance<'tcx>> {
52+
Ok(ty::Instance::mono(self.eval_context_ref().tcx.tcx, resolve_did(self.eval_context_ref().tcx.tcx, path)?))
4753
}
4854

4955
/// Write a 0 of the appropriate size to `dest`.

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ mod machine;
2424
mod eval;
2525

2626
// Make all those symbols available in the same place as our own.
27+
2728
pub use rustc_mir::interpret::*;
2829
// Resolve ambiguity.
2930
pub use rustc_mir::interpret::{self, AllocMap, PlaceTy};

src/machine.rs

Lines changed: 127 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use std::rc::Rc;
88
use rand::rngs::StdRng;
99

1010
use rustc::hir::def_id::DefId;
11+
use rustc::ty::{self, Ty, TyCtxt, layout::{Size, LayoutOf, TyLayout}};
12+
use rustc::ty::{ExistentialPredicate, ExistentialTraitRef, RegionKind, List, ParamEnv, TyCtxt};
1113
use rustc::mir;
1214
use rustc::ty::{
1315
self,
@@ -25,6 +27,25 @@ pub const STACK_ADDR: u64 = 32 * PAGE_SIZE; // not really about the "stack", but
2527
pub const STACK_SIZE: u64 = 16 * PAGE_SIZE; // whatever
2628
pub const NUM_CPUS: u64 = 1;
2729

30+
pub struct FrameData<'tcx> {
31+
pub call_id: stacked_borrows::CallId,
32+
pub catch_panic: Option<UnwindData<'tcx>>,
33+
pub is_box_me_frame: bool
34+
}
35+
36+
/// Hold all of the relevant data for a call to
37+
/// __rust_maybe_catch_panic
38+
///
39+
/// If a panic occurs, we update this data with
40+
/// the information from the panic site
41+
pub struct UnwindData<'tcx> {
42+
pub data: Pointer<Tag>,
43+
pub data_ptr: MPlaceTy<'tcx, Tag>,
44+
pub vtable_ptr: MPlaceTy<'tcx, Tag>,
45+
pub dest: PlaceTy<'tcx, Tag>,
46+
pub ret: mir::BasicBlock,
47+
}
48+
2849
/// Extra memory kinds
2950
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
3051
pub enum MiriMemoryKind {
@@ -38,6 +59,8 @@ pub enum MiriMemoryKind {
3859
Env,
3960
/// Statics.
4061
Static,
62+
/// Temporary storage for implementing unwinding
63+
UnwindHelper,
4164
}
4265

4366
impl Into<MemoryKind<MiriMemoryKind>> for MiriMemoryKind {
@@ -78,6 +101,36 @@ impl MemoryExtra {
78101
}
79102
}
80103

104+
pub struct CachedTypes<'tcx> {
105+
/// The layout of the type '*mut &mut dyn core::panic::BoxMeUp'
106+
pub box_me_up_layout: TyLayout<'tcx>,
107+
}
108+
109+
impl<'tcx> CachedTypes<'tcx> {
110+
pub fn new(tcx: TyCtxt<'tcx>) -> InterpResult<'tcx, CachedTypes<'tcx>> {
111+
// We build up the layout of '*mut &mut dyn core::panic::BoxMeUp'
112+
let box_me_up_did = helpers::resolve_did(tcx, &["core", "panic", "BoxMeUp"])?;
113+
let traits = &[ExistentialPredicate::Trait(ExistentialTraitRef {
114+
def_id: box_me_up_did,
115+
substs: List::empty()
116+
})];
117+
118+
let me_mut_dyn = tcx.mk_dynamic(
119+
ty::Binder::dummy(tcx.intern_existential_predicates(traits)),
120+
&RegionKind::ReErased
121+
);
122+
123+
let me_mut_ref = tcx.mk_mut_ref(&RegionKind::ReErased, me_mut_dyn);
124+
let me_mut_raw = tcx.mk_mut_ptr(me_mut_ref);
125+
let box_me_up_layout = tcx.layout_of(ParamEnv::empty().and(me_mut_raw))
126+
.map_err(|layout| InterpError::Layout(layout))?;
127+
128+
Ok(CachedTypes {
129+
box_me_up_layout
130+
})
131+
}
132+
}
133+
81134
/// The machine itself.
82135
pub struct Evaluator<'tcx> {
83136
/// Environment variables set by `setenv`.
@@ -102,10 +155,21 @@ pub struct Evaluator<'tcx> {
102155
pub(crate) communicate: bool,
103156

104157
pub(crate) file_handler: FileHandler,
158+
159+
/// Extra information needed for unwinding
160+
/// We create this even in abort mode, so
161+
/// that we can perform some basic validation
162+
/// during panics
163+
pub(crate) cached_data: CachedTypes<'tcx>,
164+
165+
/// Whether or not we are currently unwinding from
166+
/// a panic
167+
pub(crate) unwinding: bool,
168+
pub(crate) box_me_up_tmp_ptr: Option<MPlaceTy<'tcx, Tag>>
105169
}
106170

107171
impl<'tcx> Evaluator<'tcx> {
108-
pub(crate) fn new(communicate: bool) -> Self {
172+
pub(crate) fn new(communicate: bool, cached_data: CachedTypes<'tcx>) -> Self {
109173
Evaluator {
110174
// `env_vars` could be initialized properly here if `Memory` were available before
111175
// calling this method.
@@ -117,6 +181,9 @@ impl<'tcx> Evaluator<'tcx> {
117181
tls: TlsData::default(),
118182
communicate,
119183
file_handler: Default::default(),
184+
cached_data,
185+
unwinding: false,
186+
box_me_up_tmp_ptr: None
120187
}
121188
}
122189
}
@@ -144,7 +211,7 @@ impl<'mir, 'tcx> MiriEvalContextExt<'mir, 'tcx> for MiriEvalContext<'mir, 'tcx>
144211
impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
145212
type MemoryKinds = MiriMemoryKind;
146213

147-
type FrameExtra = stacked_borrows::CallId;
214+
type FrameExtra = FrameData<'tcx>;
148215
type MemoryExtra = MemoryExtra;
149216
type AllocExtra = AllocExtra;
150217
type PointerTag = Tag;
@@ -174,8 +241,9 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
174241
args: &[OpTy<'tcx, Tag>],
175242
dest: Option<PlaceTy<'tcx, Tag>>,
176243
ret: Option<mir::BasicBlock>,
244+
unwind: Option<mir::BasicBlock>,
177245
) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> {
178-
ecx.find_fn(instance, args, dest, ret)
246+
ecx.find_fn(instance, args, dest, ret, unwind)
179247
}
180248

181249
#[inline(always)]
@@ -348,21 +416,66 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
348416
#[inline(always)]
349417
fn stack_push(
350418
ecx: &mut InterpCx<'mir, 'tcx, Self>,
351-
) -> InterpResult<'tcx, stacked_borrows::CallId> {
352-
Ok(ecx.memory().extra.stacked_borrows.borrow_mut().new_call())
419+
) -> InterpResult<'tcx, FrameData<'tcx>> {
420+
Ok(FrameData {
421+
call_id: ecx.memory().extra.stacked_borrows.borrow_mut().new_call(),
422+
catch_panic: None,
423+
is_box_me_frame: false
424+
})
353425
}
354426

355427
#[inline(always)]
356428
fn stack_pop(
357429
ecx: &mut InterpCx<'mir, 'tcx, Self>,
358-
extra: stacked_borrows::CallId,
359-
) -> InterpResult<'tcx> {
360-
Ok(ecx
361-
.memory()
362-
.extra
363-
.stacked_borrows
364-
.borrow_mut()
365-
.end_call(extra))
430+
extra: FrameData<'tcx>,
431+
) -> InterpResult<'tcx, StackPopInfo> {
432+
if extra.is_box_me_frame {
433+
trace!("unwinding: found box_me_frame");
434+
ecx.machine.unwinding = true;
435+
}
436+
if ecx.machine.unwinding {
437+
trace!("Popping during unwind!");
438+
if let Some(unwind_data) = ecx.frame_mut().extra.catch_panic.take() {
439+
// We've just popped the frame that was immediately above
440+
// our target frame on the stack.
441+
//
442+
trace!("unwinding: found target frame: {:?}", ecx.frame().span);
443+
444+
// 'box_me_up' has finished. 'temp_ptr' now holds
445+
// a '*mut (dyn Any + Send)'
446+
// We want to split this into its consituient parts -
447+
// the data and vtable pointers - and store them back
448+
// into the panic handler frame
449+
let tmp_ptr = ecx.machine.box_me_up_tmp_ptr.take().unwrap();
450+
let real_ret = ecx.read_immediate(tmp_ptr.into())?;
451+
let payload_data_ptr = real_ret.to_scalar_ptr()?;
452+
let payload_vtable_ptr = real_ret.to_meta()?.expect("Expected fat pointer");
453+
454+
455+
let data_ptr = unwind_data.data_ptr.clone();
456+
let vtable_ptr = unwind_data.vtable_ptr.clone();
457+
let dest = unwind_data.dest.clone();
458+
drop(unwind_data);
459+
460+
461+
// Here, we write directly into the frame of the function
462+
// that called '__rust_maybe_catch_panic'.
463+
// (NOT the function that called '__rust_start_panic')
464+
465+
ecx.write_scalar(payload_data_ptr, data_ptr.into())?;
466+
ecx.write_scalar(payload_vtable_ptr, vtable_ptr.into())?;
467+
468+
// We 'return' the value 1 from __rust_maybe_catch_panic,
469+
// since there was a panic
470+
ecx.write_scalar(Scalar::from_int(1, dest.layout.size), dest)?;
471+
ecx.machine.unwinding = false;
472+
473+
ecx.memory_mut().deallocate(tmp_ptr.to_ptr()?, None, MiriMemoryKind::UnwindHelper.into())?;
474+
}
475+
}
476+
ecx.memory().extra.stacked_borrows.borrow_mut().end_call(extra.call_id);
477+
Ok(StackPopInfo { unwinding: ecx.machine.unwinding })
478+
>>>>>>> Support unwinding after a panic
366479
}
367480

368481
#[inline(always)]
@@ -428,7 +541,7 @@ impl MayLeak for MiriMemoryKind {
428541
fn may_leak(self) -> bool {
429542
use self::MiriMemoryKind::*;
430543
match self {
431-
Rust | C | WinHeap => false,
544+
Rust | C | WinHeap | UnwindHelper => false,
432545
Env | Static => true,
433546
}
434547
}

0 commit comments

Comments
 (0)