Skip to content

Commit 65d2dc0

Browse files
committed
Auto merge of #693 - Aaron1011:feature/panic_unwind_final, r=<try>
Support unwinding after a panic Fixes #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. I've a test to exercise several different aspects of unwind panicking. Ideally, we would run Miri against the libstd panic tests, but I haven't yet figured out how to do that. This depends on rust-lang/rust#60026
2 parents 67a63f8 + 660cd55 commit 65d2dc0

23 files changed

+419
-135
lines changed

src/eval.rs

+1-4
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);

src/helpers.rs

+37-31
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,45 @@ 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+
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())
4338
}
39+
40+
items = tcx.item_children(item.res.def_id());
41+
break;
4442
}
4543
}
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-
})
44+
}
45+
None
46+
})
47+
.ok_or_else(|| {
48+
let path = path.iter().map(|&s| s.to_owned()).collect();
49+
err_unsup!(PathNotFound(path)).into()
50+
})
51+
}
52+
53+
54+
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
55+
56+
fn resolve_path(&self, path: &[&str]) -> InterpResult<'tcx, ty::Instance<'tcx>> {
57+
Ok(ty::Instance::mono(self.eval_context_ref().tcx.tcx, resolve_did(self.eval_context_ref().tcx.tcx, path)?))
5258
}
5359

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

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub use crate::shims::time::{EvalContextExt as TimeEvalContextExt};
3737
pub use crate::shims::dlsym::{Dlsym, EvalContextExt as DlsymEvalContextExt};
3838
pub use crate::shims::env::{EnvVars, EvalContextExt as EnvEvalContextExt};
3939
pub use crate::shims::fs::{FileHandler, EvalContextExt as FileEvalContextExt};
40+
pub use crate::shims::panic::{CatchUnwindData, EvalContextExt as PanicEvalContextExt};
4041
pub use crate::operator::EvalContextExt as OperatorEvalContextExt;
4142
pub use crate::range_map::RangeMap;
4243
pub use crate::helpers::{EvalContextExt as HelpersEvalContextExt};

src/machine.rs

+33-26
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,19 @@ 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+
/// Extra data stored with each stack frame
24+
#[derive(Debug)]
25+
pub struct FrameData<'tcx> {
26+
/// Extra data for Stacked Borrows.
27+
pub call_id: stacked_borrows::CallId,
28+
/// If this is Some(), then this is a special 'catch unwind'
29+
/// frame. When this frame is popped during unwinding a panic,
30+
/// we stop unwinding, and use the `CatchUnwindData` to
31+
/// store the panic payload and continue execution in the parent frame.
32+
pub catch_panic: Option<CatchUnwindData<'tcx>>,
33+
}
34+
35+
2736
/// Extra memory kinds
2837
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
2938
pub enum MiriMemoryKind {
@@ -101,6 +110,10 @@ pub struct Evaluator<'tcx> {
101110
pub(crate) communicate: bool,
102111

103112
pub(crate) file_handler: FileHandler,
113+
114+
/// The temporary used for storing the argument of
115+
/// the call to `miri_start_panic` (the panic payload) when unwinding.
116+
pub(crate) panic_payload: Option<ImmTy<'tcx, Tag>>
104117
}
105118

106119
impl<'tcx> Evaluator<'tcx> {
@@ -116,6 +129,7 @@ impl<'tcx> Evaluator<'tcx> {
116129
tls: TlsData::default(),
117130
communicate,
118131
file_handler: Default::default(),
132+
panic_payload: None
119133
}
120134
}
121135
}
@@ -143,7 +157,7 @@ impl<'mir, 'tcx> MiriEvalContextExt<'mir, 'tcx> for MiriEvalContext<'mir, 'tcx>
143157
impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
144158
type MemoryKinds = MiriMemoryKind;
145159

146-
type FrameExtra = stacked_borrows::CallId;
160+
type FrameExtra = FrameData<'tcx>;
147161
type MemoryExtra = MemoryExtra;
148162
type AllocExtra = AllocExtra;
149163
type PointerTag = Tag;
@@ -173,9 +187,9 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
173187
args: &[OpTy<'tcx, Tag>],
174188
dest: Option<PlaceTy<'tcx, Tag>>,
175189
ret: Option<mir::BasicBlock>,
176-
_unwind: Option<mir::BasicBlock>,
190+
unwind: Option<mir::BasicBlock>,
177191
) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> {
178-
ecx.find_fn(instance, args, dest, ret)
192+
ecx.find_fn(instance, args, dest, ret, unwind)
179193
}
180194

181195
#[inline(always)]
@@ -196,14 +210,10 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
196210
instance: ty::Instance<'tcx>,
197211
args: &[OpTy<'tcx, Tag>],
198212
dest: Option<PlaceTy<'tcx, Tag>>,
199-
_ret: Option<mir::BasicBlock>,
200-
_unwind: Option<mir::BasicBlock>
213+
ret: Option<mir::BasicBlock>,
214+
unwind: Option<mir::BasicBlock>,
201215
) -> InterpResult<'tcx> {
202-
let dest = match dest {
203-
Some(dest) => dest,
204-
None => throw_ub!(Unreachable)
205-
};
206-
ecx.call_intrinsic(span, instance, args, dest)
216+
ecx.call_intrinsic(span, instance, args, dest, ret, unwind)
207217
}
208218

209219
#[inline(always)]
@@ -352,23 +362,20 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
352362
#[inline(always)]
353363
fn stack_push(
354364
ecx: &mut InterpCx<'mir, 'tcx, Self>,
355-
) -> InterpResult<'tcx, stacked_borrows::CallId> {
356-
Ok(ecx.memory.extra.stacked_borrows.borrow_mut().new_call())
365+
) -> InterpResult<'tcx, FrameData<'tcx>> {
366+
Ok(FrameData {
367+
call_id: ecx.memory.extra.stacked_borrows.borrow_mut().new_call(),
368+
catch_panic: None,
369+
})
357370
}
358371

359372
#[inline(always)]
360373
fn stack_pop(
361374
ecx: &mut InterpCx<'mir, 'tcx, Self>,
362-
extra: stacked_borrows::CallId,
363-
_unwinding: bool
375+
extra: FrameData<'tcx>,
376+
unwinding: bool
364377
) -> InterpResult<'tcx, StackPopInfo> {
365-
ecx
366-
.memory
367-
.extra
368-
.stacked_borrows
369-
.borrow_mut()
370-
.end_call(extra);
371-
Ok(StackPopInfo::Normal)
378+
ecx.handle_stack_pop(extra, unwinding)
372379
}
373380

374381
#[inline(always)]

src/shims/foreign_items.rs

+31-47
Original file line numberDiff line numberDiff line change
@@ -1,9 +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};
5+
use rustc::hir::def_id::DefId;
66
use rustc_apfloat::Float;
7+
use rustc::ty;
78
use syntax::attr;
89
use syntax::symbol::sym;
910

@@ -105,13 +106,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
105106

106107
/// Emulates calling a foreign item, failing if the item is not supported.
107108
/// This function will handle `goto_block` if needed.
109+
/// Returns Ok(None) if the foreign item was completely handled
110+
/// by this function.
111+
/// Returns Ok(Some(body)) if processing the foreign item
112+
/// is delegated to another function.
108113
fn emulate_foreign_item(
109114
&mut self,
110115
def_id: DefId,
111116
args: &[OpTy<'tcx, Tag>],
112117
dest: Option<PlaceTy<'tcx, Tag>>,
113118
ret: Option<mir::BasicBlock>,
114-
) -> InterpResult<'tcx> {
119+
_unwind: Option<mir::BasicBlock>
120+
) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> {
115121
let this = self.eval_context_mut();
116122
let attrs = this.tcx.get_attrs(def_id);
117123
let link_name = match attr::first_attr_value_str_by_name(&attrs, sym::link_name) {
@@ -124,8 +130,26 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
124130

125131
// First: functions that diverge.
126132
match link_name {
127-
"__rust_start_panic" | "panic_impl" => {
128-
throw_unsup_format!("the evaluated program panicked");
133+
// Note that this matches calls to the *foreign* item "__rust_start_panic" -
134+
// that is, calls `extern "Rust" { fn __rust_start_panic(...) }`
135+
// We forward this to the underlying *implementation* in "libpanic_unwind"
136+
"__rust_start_panic" => {
137+
let start_panic_instance = this.resolve_path(&["panic_unwind", "__rust_start_panic"])?;
138+
return Ok(Some(this.load_mir(start_panic_instance.def, None)?));
139+
}
140+
141+
// During a normal (non-Miri) compilation,
142+
// this gets resolved to the '#[panic_handler]` function at link time,
143+
// which corresponds to the function with the `#[panic_handler]` attribute.
144+
//
145+
// Since we're interpreting mir, we forward it to the implementation of `panic_impl`
146+
//
147+
// This is used by libcore to forward panics to the actual
148+
// panic impl
149+
"panic_impl" => {
150+
let panic_impl_id = this.tcx.lang_items().panic_impl().unwrap();
151+
let panic_impl_instance = ty::Instance::mono(*this.tcx, panic_impl_id);
152+
return Ok(Some(this.load_mir(panic_impl_instance.def, None)?));
129153
}
130154
"exit" | "ExitProcess" => {
131155
// it's really u32 for ExitProcess, but we have to put it into the `Exit` error variant anyway
@@ -310,48 +334,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
310334
}
311335

312336
"__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 { ret: Some(ret), unwind: None },
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(());
337+
this.handle_catch_panic(args, dest, ret)?;
338+
return Ok(None)
355339
}
356340

357341
"memcmp" => {
@@ -943,7 +927,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
943927

944928
this.goto_block(Some(ret))?;
945929
this.dump_place(*dest);
946-
Ok(())
930+
Ok(None)
947931
}
948932

949933
/// Evaluates the scalar at the specified path. Returns Some(val)

0 commit comments

Comments
 (0)