Skip to content

Commit

Permalink
Support unwinding after a panic
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Aaron1011 committed Oct 30, 2019
1 parent d4e4fe7 commit 425ff06
Show file tree
Hide file tree
Showing 15 changed files with 453 additions and 132 deletions.
7 changes: 2 additions & 5 deletions src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
tcx.at(syntax::source_map::DUMMY_SP),
ty::ParamEnv::reveal_all(),
Evaluator::new(config.communicate),
MemoryExtra::new(
StdRng::seed_from_u64(config.seed.unwrap_or(0)),
config.validate,
),
MemoryExtra::new(StdRng::seed_from_u64(config.seed.unwrap_or(0)), config.validate),
);
// Complete initialization.
EnvVars::init(&mut ecx, config.excluded_env_vars);
Expand Down Expand Up @@ -225,7 +222,7 @@ pub fn eval_main<'tcx>(tcx: TyCtxt<'tcx>, main_id: DefId, config: MiriConfig) {
};
e.print_backtrace();
if let Some(frame) = ecx.stack().last() {
let block = &frame.body.basic_blocks()[frame.block];
let block = &frame.body.basic_blocks()[frame.block.expect("Missing block!")];
let span = if frame.stmt < block.statements.len() {
block.statements[frame.stmt].source_info.span
} else {
Expand Down
70 changes: 39 additions & 31 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use rustc::mir;
use rustc::ty::{
self,
List,
TyCtxt,
layout::{self, LayoutOf, Size, TyLayout},
};

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

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

pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
/// Gets an instance for a path.
fn resolve_path(&self, path: &[&str]) -> InterpResult<'tcx, ty::Instance<'tcx>> {
let this = self.eval_context_ref();
this.tcx
.crates()
.iter()
.find(|&&krate| this.tcx.original_crate_name(krate).as_str() == path[0])
.and_then(|krate| {
let krate = DefId {
krate: *krate,
index: CRATE_DEF_INDEX,
};
let mut items = this.tcx.item_children(krate);
let mut path_it = path.iter().skip(1).peekable();

while let Some(segment) = path_it.next() {
for item in mem::replace(&mut items, Default::default()).iter() {
if item.ident.name.as_str() == *segment {
if path_it.peek().is_none() {
return Some(ty::Instance::mono(this.tcx.tcx, item.res.def_id()));
}

items = this.tcx.item_children(item.res.def_id());
break;
/// Gets an instance for a path.
pub fn resolve_did<'mir, 'tcx>(tcx: TyCtxt<'tcx>, path: &[&str]) -> InterpResult<'tcx, DefId> {
tcx
.crates()
.iter()
.find(|&&krate| tcx.original_crate_name(krate).as_str() == path[0])
.and_then(|krate| {
let krate = DefId {
krate: *krate,
index: CRATE_DEF_INDEX,
};
let mut items = tcx.item_children(krate);
let mut path_it = path.iter().skip(1).peekable();

while let Some(segment) = path_it.next() {
for item in mem::replace(&mut items, Default::default()).iter() {
if item.ident.name.as_str() == *segment {
if path_it.peek().is_none() {
return Some(item.res.def_id())
//eprintln!("Generics: {:?}", this.tcx.generics_of(item.res.def_id()));
//return Some(ty::Instance::mono(this.tcx.tcx, item.res.def_id()).def_id());
}

items = tcx.item_children(item.res.def_id());
break;
}
}
None
})
.ok_or_else(|| {
let path = path.iter().map(|&s| s.to_owned()).collect();
err_unsup!(PathNotFound(path)).into()
})
}
None
})
.ok_or_else(|| {
let path = path.iter().map(|&s| s.to_owned()).collect();
err_unsup!(PathNotFound(path)).into()
})
}


pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {

fn resolve_path(&self, path: &[&str]) -> InterpResult<'tcx, ty::Instance<'tcx>> {
Ok(ty::Instance::mono(self.eval_context_ref().tcx.tcx, resolve_did(self.eval_context_ref().tcx.tcx, path)?))
}

/// Write a 0 of the appropriate size to `dest`.
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ mod machine;
mod eval;

// Make all those symbols available in the same place as our own.

pub use rustc_mir::interpret::*;
// Resolve ambiguity.
pub use rustc_mir::interpret::{self, AllocMap, PlaceTy};
Expand All @@ -37,6 +38,7 @@ pub use crate::shims::time::{EvalContextExt as TimeEvalContextExt};
pub use crate::shims::dlsym::{Dlsym, EvalContextExt as DlsymEvalContextExt};
pub use crate::shims::env::{EnvVars, EvalContextExt as EnvEvalContextExt};
pub use crate::shims::fs::{FileHandler, EvalContextExt as FileEvalContextExt};
pub use crate::shims::panic::{UnwindData, EvalContextExt as PanicEvalContextExt};
pub use crate::operator::EvalContextExt as OperatorEvalContextExt;
pub use crate::range_map::RangeMap;
pub use crate::helpers::{EvalContextExt as HelpersEvalContextExt};
Expand Down
49 changes: 30 additions & 19 deletions src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,8 @@ use std::rc::Rc;
use rand::rngs::StdRng;

use rustc::hir::def_id::DefId;
use rustc::ty::{self, layout::{Size, LayoutOf}, Ty, TyCtxt};
use rustc::mir;
use rustc::ty::{
self,
layout::{LayoutOf, Size},
Ty, TyCtxt,
};
use syntax::{attr, source_map::Span, symbol::sym};

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

pub struct FrameData<'tcx> {
pub call_id: stacked_borrows::CallId,
pub catch_panic: Option<UnwindData<'tcx>>,
pub is_panic_start: bool
}


/// Extra memory kinds
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MiriMemoryKind {
Expand All @@ -37,6 +40,8 @@ pub enum MiriMemoryKind {
Env,
/// Statics.
Static,
/// Temporary storage for implementing unwinding
UnwindHelper,
}

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

pub(crate) file_handler: FileHandler,

/// The temporary used for storing the result of
/// the call to `miri_start_panic` when unwinding
pub(crate) panic_tmp_ptr: Option<ImmTy<'tcx, Tag>>
}

impl<'tcx> Evaluator<'tcx> {
Expand All @@ -116,6 +125,7 @@ impl<'tcx> Evaluator<'tcx> {
tls: TlsData::default(),
communicate,
file_handler: Default::default(),
panic_tmp_ptr: None
}
}
}
Expand Down Expand Up @@ -143,7 +153,7 @@ impl<'mir, 'tcx> MiriEvalContextExt<'mir, 'tcx> for MiriEvalContext<'mir, 'tcx>
impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
type MemoryKinds = MiriMemoryKind;

type FrameExtra = stacked_borrows::CallId;
type FrameExtra = FrameData<'tcx>;
type MemoryExtra = MemoryExtra;
type AllocExtra = AllocExtra;
type PointerTag = Tag;
Expand Down Expand Up @@ -173,8 +183,9 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
args: &[OpTy<'tcx, Tag>],
dest: Option<PlaceTy<'tcx, Tag>>,
ret: Option<mir::BasicBlock>,
unwind: Option<mir::BasicBlock>,
) -> InterpResult<'tcx, Option<&'mir mir::Body<'tcx>>> {
ecx.find_fn(instance, args, dest, ret)
ecx.find_fn(instance, args, dest, ret, unwind)
}

#[inline(always)]
Expand All @@ -194,7 +205,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
span: Span,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx, Tag>],
dest: PlaceTy<'tcx, Tag>,
dest: Option<PlaceTy<'tcx, Tag>>,
) -> InterpResult<'tcx> {
ecx.call_intrinsic(span, instance, args, dest)
}
Expand Down Expand Up @@ -345,21 +356,21 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
#[inline(always)]
fn stack_push(
ecx: &mut InterpCx<'mir, 'tcx, Self>,
) -> InterpResult<'tcx, stacked_borrows::CallId> {
Ok(ecx.memory.extra.stacked_borrows.borrow_mut().new_call())
) -> InterpResult<'tcx, FrameData<'tcx>> {
Ok(FrameData {
call_id: ecx.memory.extra.stacked_borrows.borrow_mut().new_call(),
catch_panic: None,
is_panic_start: false
})
}

#[inline(always)]
fn stack_pop(
ecx: &mut InterpCx<'mir, 'tcx, Self>,
extra: stacked_borrows::CallId,
) -> InterpResult<'tcx> {
Ok(ecx
.memory
.extra
.stacked_borrows
.borrow_mut()
.end_call(extra))
extra: FrameData<'tcx>,
unwinding: bool
) -> InterpResult<'tcx, StackPopInfo> {
ecx.handle_stack_pop(extra, unwinding)
}

#[inline(always)]
Expand Down Expand Up @@ -425,7 +436,7 @@ impl MayLeak for MiriMemoryKind {
fn may_leak(self) -> bool {
use self::MiriMemoryKind::*;
match self {
Rust | C | WinHeap => false,
Rust | C | WinHeap | UnwindHelper => false,
Env | Static => true,
}
}
Expand Down
92 changes: 36 additions & 56 deletions src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use std::{iter, convert::TryInto};

use rustc::hir::def_id::DefId;
use rustc::mir;
use rustc::ty::layout::{Align, LayoutOf, Size};
use rustc_apfloat::Float;
use syntax::attr;
use syntax::symbol::sym;
use rustc::ty;
use syntax::source_map::DUMMY_SP;

use crate::*;

Expand Down Expand Up @@ -107,25 +106,47 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
/// This function will handle `goto_block` if needed.
fn emulate_foreign_item(
&mut self,
def_id: DefId,
link_name: &str,
args: &[OpTy<'tcx, Tag>],
dest: Option<PlaceTy<'tcx, Tag>>,
ret: Option<mir::BasicBlock>,
_unwind: Option<mir::BasicBlock>
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let attrs = this.tcx.get_attrs(def_id);
let link_name = match attr::first_attr_value_str_by_name(&attrs, sym::link_name) {
Some(name) => name.as_str(),
None => this.tcx.item_name(def_id).as_str(),
};
// Strip linker suffixes (seen on 32-bit macOS).
let link_name = link_name.trim_end_matches("$UNIX2003");
let tcx = &{ this.tcx.tcx };
let tcx = &{this.tcx.tcx};

// First: functions that diverge.
match link_name {
"__rust_start_panic" | "panic_impl" => {
throw_unsup_format!("the evaluated program panicked");
"__rust_start_panic" => {
return this.handle_rust_start_panic(args)
}

// Normally, this gets resolved to the '#[panic_handler]` function
// during compilation. We manually forward to the panic_impl lang item,
// which corresponds to the function with the `#[panic_handler]` attribute
//
// This is used by libcore to forward panics to the actual
// panic impl
"panic_impl" => {
let panic_impl_id = this.tcx.lang_items().panic_impl().unwrap();
let panic_impl_instance = ty::Instance::mono(*this.tcx, panic_impl_id);
let panic_impl_mir = this.load_mir(panic_impl_instance.def, None)?;

this.push_stack_frame(
panic_impl_instance,
DUMMY_SP,
panic_impl_mir,
None,
StackPopCleanup::Goto { ret: None, unwind: None }
)?;

// Copy first argument into new call frame
let mut new_args = this.frame().body.args_iter();
let orig_arg = this.read_scalar(args[0])?;
let new_arg = this.local_place(new_args.next().unwrap())?;
this.write_scalar(orig_arg, new_arg)?;

return Ok(())
}
"exit" | "ExitProcess" => {
// it's really u32 for ExitProcess, but we have to put it into the `Exit` error variant anyway
Expand Down Expand Up @@ -310,48 +331,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
}

"__rust_maybe_catch_panic" => {
// fn __rust_maybe_catch_panic(
// f: fn(*mut u8),
// data: *mut u8,
// data_ptr: *mut usize,
// vtable_ptr: *mut usize,
// ) -> u32
// We abort on panic, so not much is going on here, but we still have to call the closure.
let f = this.read_scalar(args[0])?.not_undef()?;
let data = this.read_scalar(args[1])?.not_undef()?;
let f_instance = this.memory.get_fn(f)?.as_instance()?;
this.write_null(dest)?;
trace!("__rust_maybe_catch_panic: {:?}", f_instance);

// Now we make a function call.
// TODO: consider making this reusable? `InterpCx::step` does something similar
// for the TLS destructors, and of course `eval_main`.
let mir = this.load_mir(f_instance.def, None)?;
let ret_place =
MPlaceTy::dangling(this.layout_of(tcx.mk_unit())?, this).into();
this.push_stack_frame(
f_instance,
mir.span,
mir,
Some(ret_place),
// Directly return to caller.
StackPopCleanup::Goto(Some(ret)),
)?;
let mut args = this.frame().body.args_iter();

let arg_local = args
.next()
.expect("Argument to __rust_maybe_catch_panic does not take enough arguments.");
let arg_dest = this.local_place(arg_local)?;
this.write_scalar(data, arg_dest)?;

args.next().expect_none("__rust_maybe_catch_panic argument has more arguments than expected");

// We ourselves will return `0`, eventually (because we will not return if we paniced).
this.write_null(dest)?;

// Don't fall through, we do *not* want to `goto_block`!
return Ok(());
return this.handle_catch_panic(args, dest, ret);
}

"memcmp" => {
Expand Down
Loading

0 comments on commit 425ff06

Please sign in to comment.