Skip to content

Commit e64caeb

Browse files
committed
Auto merge of #693 - Aaron1011:feature/panic_unwind_final, r=oli-obk
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 9e13cba + 2750f60 commit e64caeb

24 files changed

+416
-136
lines changed

rust-version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8831d766ace89bc74714918a7d9fbd3ca5ec946a
1+
3e525e3f6d9e85d54fa4c49b52df85aa0c990100

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

+38-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,46 @@ 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+
/// Gets an instance for a path.
57+
fn resolve_path(&self, path: &[&str]) -> InterpResult<'tcx, ty::Instance<'tcx>> {
58+
Ok(ty::Instance::mono(self.eval_context_ref().tcx.tcx, resolve_did(self.eval_context_ref().tcx.tcx, path)?))
5259
}
5360

5461
/// 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

+34-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,20 @@ 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+
29+
/// If this is Some(), then this is a special "catch unwind" frame (the frame of the closure
30+
/// called by `__rustc_maybe_catch_panic`). When this frame is popped during unwinding a panic,
31+
/// we stop unwinding, use the `CatchUnwindData` to
32+
/// store the panic payload, and continue execution in the parent frame.
33+
pub catch_panic: Option<CatchUnwindData<'tcx>>,
34+
}
35+
36+
2737
/// Extra memory kinds
2838
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
2939
pub enum MiriMemoryKind {
@@ -101,6 +111,10 @@ pub struct Evaluator<'tcx> {
101111
pub(crate) communicate: bool,
102112

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

106120
impl<'tcx> Evaluator<'tcx> {
@@ -116,6 +130,7 @@ impl<'tcx> Evaluator<'tcx> {
116130
tls: TlsData::default(),
117131
communicate,
118132
file_handler: Default::default(),
133+
panic_payload: None
119134
}
120135
}
121136
}
@@ -143,7 +158,7 @@ impl<'mir, 'tcx> MiriEvalContextExt<'mir, 'tcx> for MiriEvalContext<'mir, 'tcx>
143158
impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
144159
type MemoryKinds = MiriMemoryKind;
145160

146-
type FrameExtra = stacked_borrows::CallId;
161+
type FrameExtra = FrameData<'tcx>;
147162
type MemoryExtra = MemoryExtra;
148163
type AllocExtra = AllocExtra;
149164
type PointerTag = Tag;
@@ -173,9 +188,9 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
173188
args: &[OpTy<'tcx, Tag>],
174189
dest: Option<PlaceTy<'tcx, Tag>>,
175190
ret: Option<mir::BasicBlock>,
176-
_unwind: Option<mir::BasicBlock>,
191+
unwind: Option<mir::BasicBlock>,
177192
) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> {
178-
ecx.find_fn(instance, args, dest, ret)
193+
ecx.find_fn(instance, args, dest, ret, unwind)
179194
}
180195

181196
#[inline(always)]
@@ -196,14 +211,10 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
196211
instance: ty::Instance<'tcx>,
197212
args: &[OpTy<'tcx, Tag>],
198213
dest: Option<PlaceTy<'tcx, Tag>>,
199-
_ret: Option<mir::BasicBlock>,
200-
_unwind: Option<mir::BasicBlock>
214+
ret: Option<mir::BasicBlock>,
215+
unwind: Option<mir::BasicBlock>,
201216
) -> 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)
217+
ecx.call_intrinsic(span, instance, args, dest, ret, unwind)
207218
}
208219

209220
#[inline(always)]
@@ -352,23 +363,20 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
352363
#[inline(always)]
353364
fn stack_push(
354365
ecx: &mut InterpCx<'mir, 'tcx, Self>,
355-
) -> InterpResult<'tcx, stacked_borrows::CallId> {
356-
Ok(ecx.memory.extra.stacked_borrows.borrow_mut().new_call())
366+
) -> InterpResult<'tcx, FrameData<'tcx>> {
367+
Ok(FrameData {
368+
call_id: ecx.memory.extra.stacked_borrows.borrow_mut().new_call(),
369+
catch_panic: None,
370+
})
357371
}
358372

359373
#[inline(always)]
360374
fn stack_pop(
361375
ecx: &mut InterpCx<'mir, 'tcx, Self>,
362-
extra: stacked_borrows::CallId,
363-
_unwinding: bool
376+
extra: FrameData<'tcx>,
377+
unwinding: bool
364378
) -> InterpResult<'tcx, StackPopInfo> {
365-
ecx
366-
.memory
367-
.extra
368-
.stacked_borrows
369-
.borrow_mut()
370-
.end_call(extra);
371-
Ok(StackPopInfo::Normal)
379+
ecx.handle_stack_pop(extra, unwinding)
372380
}
373381

374382
#[inline(always)]

src/shims/foreign_items.rs

+28-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,9 +130,24 @@ 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 to `extern "Rust" { fn __rust_start_panic(...) }`.
135+
// We forward this to the underlying *implementation* in the panic runtime crate.
136+
// Normally, this will be either `libpanic_unwind` or `libpanic_abort`, but it could
137+
// also be a custom user-provided implementation via `#![feature(panic_runtime)]`
138+
"__rust_start_panic" => {
139+
let panic_runtime = tcx.crate_name(tcx.injected_panic_runtime().expect("No panic runtime found!"));
140+
let start_panic_instance = this.resolve_path(&[&*panic_runtime.as_str(), "__rust_start_panic"])?;
141+
return Ok(Some(this.load_mir(start_panic_instance.def, None)?));
142+
}
143+
// Similarly, we forward calls to the `panic_impl` foreign item to its implementation.
144+
// The implementation is provided by the function with the `#[panic_handler]` attribute.
145+
"panic_impl" => {
146+
let panic_impl_id = this.tcx.lang_items().panic_impl().unwrap();
147+
let panic_impl_instance = ty::Instance::mono(*this.tcx, panic_impl_id);
148+
return Ok(Some(this.load_mir(panic_impl_instance.def, None)?));
129149
}
150+
130151
"exit" | "ExitProcess" => {
131152
// it's really u32 for ExitProcess, but we have to put it into the `Exit` error variant anyway
132153
let code = this.read_scalar(args[0])?.to_i32()?;
@@ -310,48 +331,8 @@ 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 { 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(());
334+
this.handle_catch_panic(args, dest, ret)?;
335+
return Ok(None)
355336
}
356337

357338
"memcmp" => {
@@ -943,7 +924,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
943924

944925
this.goto_block(Some(ret))?;
945926
this.dump_place(*dest);
946-
Ok(())
927+
Ok(None)
947928
}
948929

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

0 commit comments

Comments
 (0)