Skip to content

Commit 425ff06

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 d4e4fe7 commit 425ff06

File tree

15 files changed

+453
-132
lines changed

15 files changed

+453
-132
lines changed

src/eval.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
3939
tcx.at(syntax::source_map::DUMMY_SP),
4040
ty::ParamEnv::reveal_all(),
4141
Evaluator::new(config.communicate),
42-
MemoryExtra::new(
43-
StdRng::seed_from_u64(config.seed.unwrap_or(0)),
44-
config.validate,
45-
),
42+
MemoryExtra::new(StdRng::seed_from_u64(config.seed.unwrap_or(0)), config.validate),
4643
);
4744
// Complete initialization.
4845
EnvVars::init(&mut ecx, config.excluded_env_vars);
@@ -225,7 +222,7 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) {
225222
};
226223
e.print_backtrace();
227224
if let Some(frame) = ecx.stack().last() {
228-
let block = &frame.body.basic_blocks()[frame.block];
225+
let block = &frame.body.basic_blocks()[frame.block.expect("Missing block!")];
229226
let span = if frame.stmt < block.statements.len() {
230227
block.statements[frame.stmt].source_info.span
231228
} else {

src/helpers.rs

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use rustc::mir;
66
use rustc::ty::{
77
self,
88
List,
9+
TyCtxt,
910
layout::{self, LayoutOf, Size, TyLayout},
1011
};
1112

@@ -15,40 +16,47 @@ use crate::*;
1516

1617
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
1718

18-
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
19-
/// Gets an instance for a path.
20-
fn resolve_path(&self, path: &[&str]) -> InterpResult<'tcx, ty::Instance<'tcx>> {
21-
let this = self.eval_context_ref();
22-
this.tcx
23-
.crates()
24-
.iter()
25-
.find(|&&krate| this.tcx.original_crate_name(krate).as_str() == path[0])
26-
.and_then(|krate| {
27-
let krate = DefId {
28-
krate: *krate,
29-
index: CRATE_DEF_INDEX,
30-
};
31-
let mut items = this.tcx.item_children(krate);
32-
let mut path_it = path.iter().skip(1).peekable();
33-
34-
while let Some(segment) = path_it.next() {
35-
for item in mem::replace(&mut items, Default::default()).iter() {
36-
if item.ident.name.as_str() == *segment {
37-
if path_it.peek().is_none() {
38-
return Some(ty::Instance::mono(this.tcx.tcx, item.res.def_id()));
39-
}
40-
41-
items = this.tcx.item_children(item.res.def_id());
42-
break;
19+
/// Gets an instance for a path.
20+
pub fn resolve_did<'mir, 'tcx>(tcx: TyCtxt<'tcx>, path: &[&str]) -> InterpResult<'tcx, DefId> {
21+
tcx
22+
.crates()
23+
.iter()
24+
.find(|&&krate| tcx.original_crate_name(krate).as_str() == path[0])
25+
.and_then(|krate| {
26+
let krate = DefId {
27+
krate: *krate,
28+
index: CRATE_DEF_INDEX,
29+
};
30+
let mut items = tcx.item_children(krate);
31+
let mut path_it = path.iter().skip(1).peekable();
32+
33+
while let Some(segment) = path_it.next() {
34+
for item in mem::replace(&mut items, Default::default()).iter() {
35+
if item.ident.name.as_str() == *segment {
36+
if path_it.peek().is_none() {
37+
return Some(item.res.def_id())
38+
//eprintln!("Generics: {:?}", this.tcx.generics_of(item.res.def_id()));
39+
//return Some(ty::Instance::mono(this.tcx.tcx, item.res.def_id()).def_id());
4340
}
41+
42+
items = tcx.item_children(item.res.def_id());
43+
break;
4444
}
4545
}
46-
None
47-
})
48-
.ok_or_else(|| {
49-
let path = path.iter().map(|&s| s.to_owned()).collect();
50-
err_unsup!(PathNotFound(path)).into()
51-
})
46+
}
47+
None
48+
})
49+
.ok_or_else(|| {
50+
let path = path.iter().map(|&s| s.to_owned()).collect();
51+
err_unsup!(PathNotFound(path)).into()
52+
})
53+
}
54+
55+
56+
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
57+
58+
fn resolve_path(&self, path: &[&str]) -> InterpResult<'tcx, ty::Instance<'tcx>> {
59+
Ok(ty::Instance::mono(self.eval_context_ref().tcx.tcx, resolve_did(self.eval_context_ref().tcx.tcx, path)?))
5260
}
5361

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

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod machine;
2525
mod eval;
2626

2727
// Make all those symbols available in the same place as our own.
28+
2829
pub use rustc_mir::interpret::*;
2930
// Resolve ambiguity.
3031
pub use rustc_mir::interpret::{self, AllocMap, PlaceTy};
@@ -37,6 +38,7 @@ pub use crate::shims::time::{EvalContextExt as TimeEvalContextExt};
3738
pub use crate::shims::dlsym::{Dlsym, EvalContextExt as DlsymEvalContextExt};
3839
pub use crate::shims::env::{EnvVars, EvalContextExt as EnvEvalContextExt};
3940
pub use crate::shims::fs::{FileHandler, EvalContextExt as FileEvalContextExt};
41+
pub use crate::shims::panic::{UnwindData, EvalContextExt as PanicEvalContextExt};
4042
pub use crate::operator::EvalContextExt as OperatorEvalContextExt;
4143
pub use crate::range_map::RangeMap;
4244
pub use crate::helpers::{EvalContextExt as HelpersEvalContextExt};

src/machine.rs

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

1010
use rustc::hir::def_id::DefId;
11+
use rustc::ty::{self, layout::{Size, LayoutOf}, Ty, TyCtxt};
1112
use rustc::mir;
12-
use rustc::ty::{
13-
self,
14-
layout::{LayoutOf, Size},
15-
Ty, TyCtxt,
16-
};
1713
use syntax::{attr, source_map::Span, symbol::sym};
1814

1915
use crate::*;
@@ -24,6 +20,13 @@ pub const STACK_ADDR: u64 = 32 * PAGE_SIZE; // not really about the "stack", but
2420
pub const STACK_SIZE: u64 = 16 * PAGE_SIZE; // whatever
2521
pub const NUM_CPUS: u64 = 1;
2622

23+
pub struct FrameData<'tcx> {
24+
pub call_id: stacked_borrows::CallId,
25+
pub catch_panic: Option<UnwindData<'tcx>>,
26+
pub is_panic_start: bool
27+
}
28+
29+
2730
/// Extra memory kinds
2831
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
2932
pub enum MiriMemoryKind {
@@ -37,6 +40,8 @@ pub enum MiriMemoryKind {
3740
Env,
3841
/// Statics.
3942
Static,
43+
/// Temporary storage for implementing unwinding
44+
UnwindHelper,
4045
}
4146

4247
impl Into<MemoryKind<MiriMemoryKind>> for MiriMemoryKind {
@@ -101,6 +106,10 @@ pub struct Evaluator<'tcx> {
101106
pub(crate) communicate: bool,
102107

103108
pub(crate) file_handler: FileHandler,
109+
110+
/// The temporary used for storing the result of
111+
/// the call to `miri_start_panic` when unwinding
112+
pub(crate) panic_tmp_ptr: Option<ImmTy<'tcx, Tag>>
104113
}
105114

106115
impl<'tcx> Evaluator<'tcx> {
@@ -116,6 +125,7 @@ impl<'tcx> Evaluator<'tcx> {
116125
tls: TlsData::default(),
117126
communicate,
118127
file_handler: Default::default(),
128+
panic_tmp_ptr: None
119129
}
120130
}
121131
}
@@ -143,7 +153,7 @@ impl<'mir, 'tcx> MiriEvalContextExt<'mir, 'tcx> for MiriEvalContext<'mir, 'tcx>
143153
impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
144154
type MemoryKinds = MiriMemoryKind;
145155

146-
type FrameExtra = stacked_borrows::CallId;
156+
type FrameExtra = FrameData<'tcx>;
147157
type MemoryExtra = MemoryExtra;
148158
type AllocExtra = AllocExtra;
149159
type PointerTag = Tag;
@@ -173,8 +183,9 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
173183
args: &[OpTy<'tcx, Tag>],
174184
dest: Option<PlaceTy<'tcx, Tag>>,
175185
ret: Option<mir::BasicBlock>,
186+
unwind: Option<mir::BasicBlock>,
176187
) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> {
177-
ecx.find_fn(instance, args, dest, ret)
188+
ecx.find_fn(instance, args, dest, ret, unwind)
178189
}
179190

180191
#[inline(always)]
@@ -194,7 +205,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
194205
span: Span,
195206
instance: ty::Instance<'tcx>,
196207
args: &[OpTy<'tcx, Tag>],
197-
dest: PlaceTy<'tcx, Tag>,
208+
dest: Option<PlaceTy<'tcx, Tag>>,
198209
) -> InterpResult<'tcx> {
199210
ecx.call_intrinsic(span, instance, args, dest)
200211
}
@@ -345,21 +356,21 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
345356
#[inline(always)]
346357
fn stack_push(
347358
ecx: &mut InterpCx<'mir, 'tcx, Self>,
348-
) -> InterpResult<'tcx, stacked_borrows::CallId> {
349-
Ok(ecx.memory.extra.stacked_borrows.borrow_mut().new_call())
359+
) -> InterpResult<'tcx, FrameData<'tcx>> {
360+
Ok(FrameData {
361+
call_id: ecx.memory.extra.stacked_borrows.borrow_mut().new_call(),
362+
catch_panic: None,
363+
is_panic_start: false
364+
})
350365
}
351366

352367
#[inline(always)]
353368
fn stack_pop(
354369
ecx: &mut InterpCx<'mir, 'tcx, Self>,
355-
extra: stacked_borrows::CallId,
356-
) -> InterpResult<'tcx> {
357-
Ok(ecx
358-
.memory
359-
.extra
360-
.stacked_borrows
361-
.borrow_mut()
362-
.end_call(extra))
370+
extra: FrameData<'tcx>,
371+
unwinding: bool
372+
) -> InterpResult<'tcx, StackPopInfo> {
373+
ecx.handle_stack_pop(extra, unwinding)
363374
}
364375

365376
#[inline(always)]
@@ -425,7 +436,7 @@ impl MayLeak for MiriMemoryKind {
425436
fn may_leak(self) -> bool {
426437
use self::MiriMemoryKind::*;
427438
match self {
428-
Rust | C | WinHeap => false,
439+
Rust | C | WinHeap | UnwindHelper => false,
429440
Env | Static => true,
430441
}
431442
}

src/shims/foreign_items.rs

Lines changed: 36 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use std::{iter, convert::TryInto};
22

3-
use rustc::hir::def_id::DefId;
43
use rustc::mir;
54
use rustc::ty::layout::{Align, LayoutOf, Size};
65
use rustc_apfloat::Float;
7-
use syntax::attr;
8-
use syntax::symbol::sym;
6+
use rustc::ty;
7+
use syntax::source_map::DUMMY_SP;
98

109
use crate::*;
1110

@@ -107,25 +106,47 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
107106
/// This function will handle `goto_block` if needed.
108107
fn emulate_foreign_item(
109108
&mut self,
110-
def_id: DefId,
109+
link_name: &str,
111110
args: &[OpTy<'tcx, Tag>],
112111
dest: Option<PlaceTy<'tcx, Tag>>,
113112
ret: Option<mir::BasicBlock>,
113+
_unwind: Option<mir::BasicBlock>
114114
) -> InterpResult<'tcx> {
115115
let this = self.eval_context_mut();
116-
let attrs = this.tcx.get_attrs(def_id);
117-
let link_name = match attr::first_attr_value_str_by_name(&attrs, sym::link_name) {
118-
Some(name) => name.as_str(),
119-
None => this.tcx.item_name(def_id).as_str(),
120-
};
121-
// Strip linker suffixes (seen on 32-bit macOS).
122-
let link_name = link_name.trim_end_matches("$UNIX2003");
123-
let tcx = &{ this.tcx.tcx };
116+
let tcx = &{this.tcx.tcx};
124117

125118
// First: functions that diverge.
126119
match link_name {
127-
"__rust_start_panic" | "panic_impl" => {
128-
throw_unsup_format!("the evaluated program panicked");
120+
"__rust_start_panic" => {
121+
return this.handle_rust_start_panic(args)
122+
}
123+
124+
// Normally, this gets resolved to the '#[panic_handler]` function
125+
// during compilation. We manually forward to the panic_impl lang item,
126+
// which corresponds to the function with the `#[panic_handler]` attribute
127+
//
128+
// This is used by libcore to forward panics to the actual
129+
// panic impl
130+
"panic_impl" => {
131+
let panic_impl_id = this.tcx.lang_items().panic_impl().unwrap();
132+
let panic_impl_instance = ty::Instance::mono(*this.tcx, panic_impl_id);
133+
let panic_impl_mir = this.load_mir(panic_impl_instance.def, None)?;
134+
135+
this.push_stack_frame(
136+
panic_impl_instance,
137+
DUMMY_SP,
138+
panic_impl_mir,
139+
None,
140+
StackPopCleanup::Goto { ret: None, unwind: None }
141+
)?;
142+
143+
// Copy first argument into new call frame
144+
let mut new_args = this.frame().body.args_iter();
145+
let orig_arg = this.read_scalar(args[0])?;
146+
let new_arg = this.local_place(new_args.next().unwrap())?;
147+
this.write_scalar(orig_arg, new_arg)?;
148+
149+
return Ok(())
129150
}
130151
"exit" | "ExitProcess" => {
131152
// it's really u32 for ExitProcess, but we have to put it into the `Exit` error variant anyway
@@ -310,48 +331,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
310331
}
311332

312333
"__rust_maybe_catch_panic" => {
313-
// fn __rust_maybe_catch_panic(
314-
// f: fn(*mut u8),
315-
// data: *mut u8,
316-
// data_ptr: *mut usize,
317-
// vtable_ptr: *mut usize,
318-
// ) -> u32
319-
// We abort on panic, so not much is going on here, but we still have to call the closure.
320-
let f = this.read_scalar(args[0])?.not_undef()?;
321-
let data = this.read_scalar(args[1])?.not_undef()?;
322-
let f_instance = this.memory.get_fn(f)?.as_instance()?;
323-
this.write_null(dest)?;
324-
trace!("__rust_maybe_catch_panic: {:?}", f_instance);
325-
326-
// Now we make a function call.
327-
// TODO: consider making this reusable? `InterpCx::step` does something similar
328-
// for the TLS destructors, and of course `eval_main`.
329-
let mir = this.load_mir(f_instance.def, None)?;
330-
let ret_place =
331-
MPlaceTy::dangling(this.layout_of(tcx.mk_unit())?, this).into();
332-
this.push_stack_frame(
333-
f_instance,
334-
mir.span,
335-
mir,
336-
Some(ret_place),
337-
// Directly return to caller.
338-
StackPopCleanup::Goto(Some(ret)),
339-
)?;
340-
let mut args = this.frame().body.args_iter();
341-
342-
let arg_local = args
343-
.next()
344-
.expect("Argument to __rust_maybe_catch_panic does not take enough arguments.");
345-
let arg_dest = this.local_place(arg_local)?;
346-
this.write_scalar(data, arg_dest)?;
347-
348-
args.next().expect_none("__rust_maybe_catch_panic argument has more arguments than expected");
349-
350-
// We ourselves will return `0`, eventually (because we will not return if we paniced).
351-
this.write_null(dest)?;
352-
353-
// Don't fall through, we do *not* want to `goto_block`!
354-
return Ok(());
334+
return this.handle_catch_panic(args, dest, ret);
355335
}
356336

357337
"memcmp" => {

0 commit comments

Comments
 (0)