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 Jul 7, 2019
1 parent 67fd5a4 commit 395f77f
Show file tree
Hide file tree
Showing 13 changed files with 455 additions and 68 deletions.
8 changes: 5 additions & 3 deletions src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use crate::{
MemoryExtra, MiriMemoryKind, Evaluator, TlsEvalContextExt,
};

use crate::machine::CachedTypes;

/// Configuration needed to spawn a Miri instance.
#[derive(Clone)]
pub struct MiriConfig {
Expand All @@ -31,7 +33,6 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
main_id: DefId,
config: MiriConfig,
) -> InterpResult<'tcx, InterpCx<'mir, 'tcx, Evaluator<'tcx>>> {

// FIXME(https://github.com/rust-lang/miri/pull/803): no validation on Windows.
let target_os = tcx.sess.target.target.target_os.to_lowercase();
let validate = if target_os == "windows" {
Expand All @@ -40,11 +41,12 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
config.validate
};


let mut ecx = InterpCx::new(
tcx.at(syntax::source_map::DUMMY_SP),
ty::ParamEnv::reveal_all(),
Evaluator::new(),
MemoryExtra::new(config.seed.map(StdRng::seed_from_u64), validate),
Evaluator::new(CachedTypes::new(tcx)?),
MemoryExtra::new(config.seed.map(StdRng::seed_from_u64), validate)
);

let main_instance = ty::Instance::mono(ecx.tcx.tcx, main_id);
Expand Down
69 changes: 38 additions & 31 deletions src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::mem;

use rustc::ty::{self, layout::{self, Size, Align}};
use rustc::ty::{self, TyCtxt, layout::{self, Size, Align}};
use rustc::hir::def_id::{DefId, CRATE_DEF_INDEX};

use rand::RngCore;
Expand All @@ -9,40 +9,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()));
}
/// 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();

items = this.tcx.item_children(item.res.def_id());
break;
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();
InterpError::PathNotFound(path).into()
})
}
None
})
.ok_or_else(|| {
let path = path.iter().map(|&s| s.to_owned()).collect();
InterpError::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
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,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 Down
136 changes: 126 additions & 10 deletions src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use rand::rngs::StdRng;
use syntax::attr;
use syntax::symbol::sym;
use rustc::hir::def_id::DefId;
use rustc::ty::{self, layout::{Size, LayoutOf}, TyCtxt};
use rustc::ty::{self, layout::{Size, LayoutOf, TyLayout}, query::TyCtxtAt};
use rustc::ty::{ExistentialPredicate, ExistentialTraitRef, RegionKind, List, ParamEnv, TyCtxt};
use rustc::mir;

use crate::*;
Expand All @@ -22,6 +23,25 @@ pub const STACK_ADDR: u64 = 32*PAGE_SIZE; // not really about the "stack", but w
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_box_me_frame: bool
}

/// Hold all of the relevant data for a call to
/// __rust_maybe_catch_panic
///
/// If a panic occurs, we update this data with
/// the information from the panic site
pub struct UnwindData<'tcx> {
pub data: Pointer<Tag>,
pub data_ptr: MPlaceTy<'tcx, Tag>,
pub vtable_ptr: MPlaceTy<'tcx, Tag>,
pub dest: PlaceTy<'tcx, Tag>,
pub ret: mir::BasicBlock,
}

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

impl Into<MemoryKind<MiriMemoryKind>> for MiriMemoryKind {
Expand Down Expand Up @@ -76,6 +98,36 @@ impl MemoryExtra {
}
}

pub struct CachedTypes<'tcx> {
/// The layout of the type '*mut &mut dyn core::panic::BoxMeUp'
pub box_me_up_layout: TyLayout<'tcx>,
}

impl<'tcx> CachedTypes<'tcx> {
pub fn new(tcx: TyCtxt<'tcx>) -> InterpResult<'tcx, CachedTypes<'tcx>> {
// We build up the layout of '*mut &mut dyn core::panic::BoxMeUp'
let box_me_up_did = helpers::resolve_did(tcx, &["core", "panic", "BoxMeUp"])?;
let traits = &[ExistentialPredicate::Trait(ExistentialTraitRef {
def_id: box_me_up_did,
substs: List::empty()
})];

let me_mut_dyn = tcx.mk_dynamic(
ty::Binder::dummy(tcx.intern_existential_predicates(traits)),
&RegionKind::ReErased
);

let me_mut_ref = tcx.mk_mut_ref(&RegionKind::ReErased, me_mut_dyn);
let me_mut_raw = tcx.mk_mut_ptr(me_mut_ref);
let box_me_up_layout = tcx.layout_of(ParamEnv::empty().and(me_mut_raw))
.map_err(|layout| InterpError::Layout(layout))?;

Ok(CachedTypes {
box_me_up_layout
})
}
}

/// The machine itself.
pub struct Evaluator<'tcx> {
/// Environment variables set by `setenv`.
Expand All @@ -94,17 +146,31 @@ pub struct Evaluator<'tcx> {

/// TLS state.
pub(crate) tls: TlsData<'tcx>,

/// Extra information needed for unwinding
/// We create this even in abort mode, so
/// that we can perform some basic validation
/// during panics
pub(crate) cached_data: CachedTypes<'tcx>,

/// Whether or not we are currently unwinding from
/// a panic
pub(crate) unwinding: bool,
pub(crate) box_me_up_tmp_ptr: Option<MPlaceTy<'tcx, Tag>>
}

impl<'tcx> Evaluator<'tcx> {
pub(crate) fn new() -> Self {
pub(crate) fn new(cached_data: CachedTypes<'tcx>) -> Self {
Evaluator {
env_vars: HashMap::default(),
argc: None,
argv: None,
cmd_line: None,
last_error: 0,
tls: TlsData::default(),
cached_data,
unwinding: false,
box_me_up_tmp_ptr: None
}
}
}
Expand Down Expand Up @@ -132,7 +198,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 All @@ -154,8 +220,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 Down Expand Up @@ -341,16 +408,65 @@ 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_box_me_frame: 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>,
) -> InterpResult<'tcx, StackPopInfo> {
if extra.is_box_me_frame {
trace!("unwinding: found box_me_frame");
ecx.machine.unwinding = true;
}
if ecx.machine.unwinding {
trace!("Popping during unwind!");
if let Some(unwind_data) = ecx.frame_mut().extra.catch_panic.take() {
// We've just popped the frame that was immediately above
// our target frame on the stack.
//
trace!("unwinding: found target frame: {:?}", ecx.frame().span);

// 'box_me_up' has finished. 'temp_ptr' now holds
// a '*mut (dyn Any + Send)'
// We want to split this into its consituient parts -
// the data and vtable pointers - and store them back
// into the panic handler frame
let tmp_ptr = ecx.machine.box_me_up_tmp_ptr.take().unwrap();
let real_ret = ecx.read_immediate(tmp_ptr.into())?;
let payload_data_ptr = real_ret.to_scalar_ptr()?;
let payload_vtable_ptr = real_ret.to_meta()?.expect("Expected fat pointer");


let data_ptr = unwind_data.data_ptr.clone();
let vtable_ptr = unwind_data.vtable_ptr.clone();
let dest = unwind_data.dest.clone();
drop(unwind_data);


// Here, we write directly into the frame of the function
// that called '__rust_maybe_catch_panic'.
// (NOT the function that called '__rust_start_panic')

ecx.write_scalar(payload_data_ptr, data_ptr.into())?;
ecx.write_scalar(payload_vtable_ptr, vtable_ptr.into())?;

// We 'return' the value 1 from __rust_maybe_catch_panic,
// since there was a panic
ecx.write_scalar(Scalar::from_int(1, dest.layout.size), dest)?;
ecx.machine.unwinding = false;

ecx.memory_mut().deallocate(tmp_ptr.to_ptr()?, None, MiriMemoryKind::UnwindHelper.into())?;
}
}
ecx.memory().extra.stacked_borrows.borrow_mut().end_call(extra.call_id);
Ok(StackPopInfo { unwinding: ecx.machine.unwinding })
}

fn int_to_ptr(
Expand Down Expand Up @@ -424,7 +540,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
Loading

0 comments on commit 395f77f

Please sign in to comment.