Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support unwinding after a panic #693

Merged
merged 10 commits into from
Nov 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rust-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
8831d766ace89bc74714918a7d9fbd3ca5ec946a
3e525e3f6d9e85d54fa4c49b52df85aa0c990100
5 changes: 1 addition & 4 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
69 changes: 38 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,46 @@ 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.
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())
}

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> {

/// Gets an instance for a path.
fn resolve_path(&self, path: &[&str]) -> InterpResult<'tcx, ty::Instance<'tcx>> {
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -37,6 +37,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::{CatchUnwindData, 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
60 changes: 34 additions & 26 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,20 @@ 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;

/// Extra data stored with each stack frame
#[derive(Debug)]
pub struct FrameData<'tcx> {
/// Extra data for Stacked Borrows.
pub call_id: stacked_borrows::CallId,

/// If this is Some(), then this is a special "catch unwind" frame (the frame of the closure
/// called by `__rustc_maybe_catch_panic`). When this frame is popped during unwinding a panic,
/// we stop unwinding, use the `CatchUnwindData` to
/// store the panic payload, and continue execution in the parent frame.
pub catch_panic: Option<CatchUnwindData<'tcx>>,
}


/// Extra memory kinds
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MiriMemoryKind {
Expand Down Expand Up @@ -101,6 +111,10 @@ pub struct Evaluator<'tcx> {
pub(crate) communicate: bool,

pub(crate) file_handler: FileHandler,

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

impl<'tcx> Evaluator<'tcx> {
Expand All @@ -116,6 +130,7 @@ impl<'tcx> Evaluator<'tcx> {
tls: TlsData::default(),
communicate,
file_handler: Default::default(),
panic_payload: None
}
}
}
Expand Down Expand Up @@ -143,7 +158,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,9 +188,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>,
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 @@ -196,14 +211,10 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx, Tag>],
dest: Option<PlaceTy<'tcx, Tag>>,
_ret: Option<mir::BasicBlock>,
_unwind: Option<mir::BasicBlock>
ret: Option<mir::BasicBlock>,
unwind: Option<mir::BasicBlock>,
) -> InterpResult<'tcx> {
let dest = match dest {
Some(dest) => dest,
None => throw_ub!(Unreachable)
};
ecx.call_intrinsic(span, instance, args, dest)
ecx.call_intrinsic(span, instance, args, dest, ret, unwind)
}

#[inline(always)]
Expand Down Expand Up @@ -352,23 +363,20 @@ 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,
})
}

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

#[inline(always)]
Expand Down
75 changes: 28 additions & 47 deletions src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
@@ -1,9 +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::hir::def_id::DefId;
use rustc_apfloat::Float;
use rustc::ty;
use syntax::attr;
use syntax::symbol::sym;

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

/// Emulates calling a foreign item, failing if the item is not supported.
/// This function will handle `goto_block` if needed.
/// Returns Ok(None) if the foreign item was completely handled
/// by this function.
/// Returns Ok(Some(body)) if processing the foreign item
/// is delegated to another function.
fn emulate_foreign_item(
&mut self,
def_id: DefId,
args: &[OpTy<'tcx, Tag>],
dest: Option<PlaceTy<'tcx, Tag>>,
ret: Option<mir::BasicBlock>,
) -> InterpResult<'tcx> {
_unwind: Option<mir::BasicBlock>
) -> InterpResult<'tcx, Option<&'mir mir::Body<'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) {
Expand All @@ -124,9 +130,24 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx

// First: functions that diverge.
match link_name {
"__rust_start_panic" | "panic_impl" => {
throw_unsup_format!("the evaluated program panicked");
// Note that this matches calls to the *foreign* item `__rust_start_panic* -
// that is, calls to `extern "Rust" { fn __rust_start_panic(...) }`.
// We forward this to the underlying *implementation* in the panic runtime crate.
// Normally, this will be either `libpanic_unwind` or `libpanic_abort`, but it could
// also be a custom user-provided implementation via `#![feature(panic_runtime)]`
"__rust_start_panic" => {
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
let panic_runtime = tcx.crate_name(tcx.injected_panic_runtime().expect("No panic runtime found!"));
let start_panic_instance = this.resolve_path(&[&*panic_runtime.as_str(), "__rust_start_panic"])?;
return Ok(Some(this.load_mir(start_panic_instance.def, None)?));
}
// Similarly, we forward calls to the `panic_impl` foreign item to its implementation.
// The implementation is provided by the function with the `#[panic_handler]` attribute.
"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);
return Ok(Some(this.load_mir(panic_impl_instance.def, None)?));
}

"exit" | "ExitProcess" => {
// it's really u32 for ExitProcess, but we have to put it into the `Exit` error variant anyway
let code = this.read_scalar(args[0])?.to_i32()?;
Expand Down Expand Up @@ -310,48 +331,8 @@ 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 { ret: Some(ret), unwind: None },
)?;
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(());
this.handle_catch_panic(args, dest, ret)?;
return Ok(None)
}

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

this.goto_block(Some(ret))?;
this.dump_place(*dest);
Ok(())
Ok(None)
}

/// Evaluates the scalar at the specified path. Returns Some(val)
Expand Down
Loading