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.
This commit is contained in:
Aaron Hill 2019-04-14 21:02:55 -04:00
parent 67a63f89d8
commit 08d3fbc76b
No known key found for this signature in database
GPG key ID: B4087E510E98B164
19 changed files with 403 additions and 130 deletions

View file

@ -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);

View file

@ -6,6 +6,7 @@ use rustc::mir;
use rustc::ty::{
self,
List,
TyCtxt,
layout::{self, LayoutOf, Size, TyLayout},
};
@ -15,40 +16,45 @@ 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();
/// 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(ty::Instance::mono(this.tcx.tcx, item.res.def_id()));
}
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())
}
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`.

View file

@ -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};

View file

@ -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::*;
@ -24,6 +20,19 @@ 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. When this frame is popped during unwinding a panic,
/// we stop unwinding, and 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 {
@ -101,6 +110,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> {
@ -116,6 +129,7 @@ impl<'tcx> Evaluator<'tcx> {
tls: TlsData::default(),
communicate,
file_handler: Default::default(),
panic_payload: None
}
}
}
@ -143,7 +157,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;
@ -173,9 +187,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)]
@ -196,14 +210,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)]
@ -352,23 +362,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)]

View file

@ -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;
@ -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) {
@ -124,8 +130,26 @@ 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 `extern "Rust" { fn __rust_start_panic(...) }`
// We forward this to the underlying *implementation* in "libpanic_unwind"
"__rust_start_panic" => {
let start_panic_instance = this.resolve_path(&["panic_unwind", "__rust_start_panic"])?;
return Ok(Some(this.load_mir(start_panic_instance.def, None)?));
}
// During a normal (non-Miri) compilation,
// this gets resolved to the '#[panic_handler]` function at link time,
// which corresponds to the function with the `#[panic_handler]` attribute.
//
// Since we're interpreting mir, we forward it to the implementation of `panic_impl`
//
// 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);
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
@ -310,48 +334,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" => {
@ -943,7 +927,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)

View file

@ -7,10 +7,7 @@ use rustc::ty::layout::{self, LayoutOf, Size, Align};
use rustc::ty;
use syntax::source_map::Span;
use crate::{
PlaceTy, OpTy, Immediate, Scalar, Tag,
OperatorEvalContextExt
};
use crate::*;
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
@ -19,10 +16,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
span: Span,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx, Tag>],
dest: PlaceTy<'tcx, Tag>,
dest: Option<PlaceTy<'tcx, Tag>>,
_ret: Option<mir::BasicBlock>,
unwind: Option<mir::BasicBlock>
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
if this.emulate_intrinsic(span, instance, args, Some(dest))? {
if this.emulate_intrinsic(span, instance, args, dest)? {
return Ok(());
}
let tcx = &{this.tcx.tcx};
@ -31,8 +30,27 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
// All these intrinsics take raw pointers, so if we access memory directly
// (as opposed to through a place), we have to remember to erase any tag
// that might still hang around!
let intrinsic_name = &*tcx.item_name(instance.def_id()).as_str();
// Handle diverging intrinsics
match intrinsic_name {
"abort" => {
// FIXME: Add a better way of indicating 'abnormal' termination,
// since this is not really an 'unsupported' behavior
throw_unsup_format!("the evaluated program aborted!");
}
"miri_start_panic" => return this.handle_miri_start_panic(args, unwind),
_ => {}
}
// Handle non-diverging intrinsics
// The intrinsic itself cannot diverge (otherwise, we would have handled it above),
// so if we got here without a return place... (can happen e.g., for transmute returning `!`)
let dest = match dest {
Some(dest) => dest,
None => throw_ub!(Unreachable)
};
match intrinsic_name {
"arith_offset" => {
let offset = this.read_scalar(args[1])?.to_machine_isize(this)?;
@ -526,7 +544,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
// However, this only affects direct calls of the intrinsic; calls to the stable
// functions wrapping them do get their validation.
// FIXME: should we check alignment for ZSTs?
use crate::ScalarMaybeUndef;
if !dest.layout.is_zst() {
match dest.layout.abi {
layout::Abi::Scalar(..) => {

View file

@ -5,9 +5,9 @@ pub mod intrinsics;
pub mod tls;
pub mod fs;
pub mod time;
pub mod panic;
use rustc::{mir, ty};
use crate::*;
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
@ -18,6 +18,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, '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>>> {
let this = self.eval_context_mut();
trace!(
@ -26,11 +27,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
dest.map(|place| *place)
);
// First, run the common hooks also supported by CTFE.
if this.hook_panic_fn(instance, args, dest)? {
this.goto_block(ret)?;
return Ok(None);
}
// There are some more lang items we want to hook that CTFE does not hook (yet).
if this.tcx.lang_items().align_offset_fn() == Some(instance.def.def_id()) {
let dest = dest.unwrap();
@ -44,11 +40,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
// Try to see if we can do something about foreign items.
if this.tcx.is_foreign_item(instance.def_id()) {
// An external function that we cannot find MIR for, but we can still run enough
// of them to make miri viable.
this.emulate_foreign_item(instance.def_id(), args, dest, ret)?;
// `goto_block` already handled.
return Ok(None);
// An external function call that does not have a MIR body. We either find MIR elsewhere
// or emulate its effect.
// This will be Ok(None) if we're emulating the intrinsic entirely within Miri (no need
// to run extra MIR), and Ok(Some(body)) if we found MIR to run for the
// foreign function
// Any needed call to `goto_block` will be performed by `emulate_foreign_item`.
return this.emulate_foreign_item(instance.def_id(), args, dest, ret, unwind);
}
// Otherwise, load the MIR.

179
src/shims/panic.rs Normal file
View file

@ -0,0 +1,179 @@
use rustc::mir;
use crate::*;
use super::machine::FrameData;
use rustc_target::spec::PanicStrategy;
use crate::rustc_target::abi::LayoutOf;
/// Holds 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
#[derive(Debug)]
pub struct CatchUnwindData<'tcx> {
/// The 'data' argument passed to `__rust_maybe_catch_panic`
pub data: Pointer<Tag>,
/// The `data_ptr` argument passed to `__rust_maybe_catch_panic`
pub data_place: MPlaceTy<'tcx, Tag>,
/// The `vtable_ptr` argument passed to `__rust_maybe_catch_panic`
pub vtable_place: MPlaceTy<'tcx, Tag>,
/// The `dest` from the original call to `__rust_maybe_catch_panic`
pub dest: PlaceTy<'tcx, Tag>,
/// The `ret` from the original call to `__rust_maybe_catch_panic`
pub ret: mir::BasicBlock,
}
impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx> {
/// Handles the special "miri_start_panic" intrinsic, which is called
/// by libpanic_unwind to delegate the actual unwinding process to Miri
#[inline(always)]
fn handle_miri_start_panic(
&mut self,
args: &[OpTy<'tcx, Tag>],
unwind: Option<mir::BasicBlock>
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
trace!("miri_start_panic: {:?}", this.frame().span);
if this.tcx.tcx.sess.panic_strategy() == PanicStrategy::Abort {
// FIXME: Add a better way of indicating 'abnormal' termination,
// since this is not really an 'unsupported' behavior
throw_unsup_format!("the evaluated program panicked");
}
// Get the raw pointer stored in arg[0]
let scalar = this.read_immediate(args[0])?;
this.machine.panic_payload = Some(scalar);
// Jump to the unwind block to begin unwinding
// We don't use 'goto_block' - if `unwind` is None,
// we'll end up immediately returning out of the
// function during the next step() call
let next_frame = this.frame_mut();
next_frame.block = unwind;
next_frame.stmt = 0;
return Ok(())
}
#[inline(always)]
fn handle_catch_panic(
&mut self,
args: &[OpTy<'tcx, Tag>],
dest: PlaceTy<'tcx, Tag>,
ret: mir::BasicBlock,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let tcx = &{this.tcx.tcx};
// fn __rust_maybe_catch_panic(
// f: fn(*mut u8),
// data: *mut u8,
// data_ptr: *mut usize,
// vtable_ptr: *mut usize,
// ) -> u32
let f = this.read_scalar(args[0])?.not_undef()?;
let data = this.read_scalar(args[1])?.not_undef()?;
let data_place = this.deref_operand(args[2])?;
let vtable_place = this.deref_operand(args[3])?;
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 },
)?;
// In unwind mode, we tag this frame with some extra data.
// This lets `handle_stack_pop` (below) know that we should stop unwinding
// when we pop this frame.
if this.tcx.tcx.sess.panic_strategy() == PanicStrategy::Unwind {
this.frame_mut().extra.catch_panic = Some(CatchUnwindData {
data: data.to_ptr()?,
data_place,
vtable_place,
dest,
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)?;
return Ok(());
}
#[inline(always)]
fn handle_stack_pop(
&mut self,
mut extra: FrameData<'tcx>,
unwinding: bool
) -> InterpResult<'tcx, StackPopInfo> {
let this = self.eval_context_mut();
trace!("handle_stack_pop(extra = {:?}, unwinding = {})", extra, unwinding);
// We only care about `catch_panic` if we're unwinding - if we're doing a normal
// return, then we don't need to do anything special.
let res = if let (true, Some(unwind_data)) = (unwinding, extra.catch_panic.take()) {
// We've just popped the frame that was immediately above
// the frame which originally called `__rust_maybe_catch_panic`
trace!("unwinding: found catch_panic frame: {:?}", this.frame().span);
// `panic_payload` now holds a '*mut (dyn Any + Send)',
// provided by the `miri_start_panic` intrinsic.
// We want to split this into its consituient parts -
// the data and vtable pointers - and store them back
// into the panic handler frame
let real_ret = this.machine.panic_payload.take().unwrap();
let payload = this.ref_to_mplace(real_ret)?;
let payload_data_place = payload.ptr;
let payload_vtable_place = payload.meta.expect("Expected fat pointer");
let data_place = unwind_data.data_place;
let vtable_place = unwind_data.vtable_place;
let dest = unwind_data.dest;
// Here, we write to the pointers provided to the call
// to '__rust_maybe_catch_panic`.
this.write_scalar(payload_data_place, data_place.into())?;
this.write_scalar(payload_vtable_place, vtable_place.into())?;
// We set the return value of `__rust_maybe_catch_panic` to 1,
// since there was a panic.
this.write_scalar(Scalar::from_int(1, dest.layout.size), dest)?;
StackPopInfo::StopUnwinding
} else {
StackPopInfo::Normal
};
this.memory.extra.stacked_borrows.borrow_mut().end_call(extra.call_id);
Ok(res)
}
}

View file

@ -532,7 +532,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
protect: bool,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut();
let protector = if protect { Some(this.frame().extra) } else { None };
let protector = if protect { Some(this.frame().extra.call_id) } else { None };
let ptr = place.ptr.to_ptr().expect("we should have a proper pointer");
trace!("reborrow: {} reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
kind, new_tag, ptr.tag, place.layout.ty, ptr.erase_tag(), size.bytes());

View file

@ -0,0 +1,11 @@
//error-pattern: the evaluated program aborted
struct Foo;
impl Drop for Foo {
fn drop(&mut self) {
panic!("second");
}
}
fn main() {
let _foo = Foo;
panic!("first");
}

View file

@ -1,5 +1,5 @@
//error-pattern: the evaluated program panicked
// error-pattern: the evaluated program panicked
// compile-flags: -C panic=abort
fn main() {
std::panic!("panicking from libstd");
}

View file

@ -1,4 +1,5 @@
//error-pattern: the evaluated program panicked
// error-pattern: the evaluated program panicked
// compile-flags: -C panic=abort
fn main() {
std::panic!("{}-panicking from libstd", 42);

View file

@ -1,4 +1,5 @@
//error-pattern: the evaluated program panicked
// compile-flags: -C panic=abort
fn main() {
core::panic!("panicking from libcore");

View file

@ -1,4 +1,5 @@
//error-pattern: the evaluated program panicked
// compile-flags: -C panic=abort
fn main() {
core::panic!("{}-panicking from libcore", 42);

View file

@ -0,0 +1,61 @@
use std::panic::catch_unwind;
use std::cell::Cell;
thread_local! {
static MY_COUNTER: Cell<usize> = Cell::new(0);
static DROPPED: Cell<bool> = Cell::new(false);
static HOOK_CALLED: Cell<bool> = Cell::new(false);
}
struct DropTester;
impl Drop for DropTester {
fn drop(&mut self) {
DROPPED.with(|c| {
c.set(true);
});
}
}
fn do_panic_counter() {
// If this gets leaked, it will be easy to spot
// in Miri's leak report
let _string = "LEAKED FROM do_panic_counter".to_string();
// When we panic, this should get dropped during unwinding
let _drop_tester = DropTester;
// Check for bugs in Miri's panic implementation.
// If do_panic_counter() somehow gets called more than once,
// we'll generate a different panic message
let old_val = MY_COUNTER.with(|c| {
let val = c.get();
c.set(val + 1);
val
});
panic!(format!("Hello from panic: {:?}", old_val));
}
fn main() {
std::panic::set_hook(Box::new(|_panic_info| {
HOOK_CALLED.with(|h| h.set(true));
}));
let res = catch_unwind(|| {
let _string = "LEAKED FROM CLOSURE".to_string();
do_panic_counter()
});
let expected: Box<String> = Box::new("Hello from panic: 0".to_string());
let actual = res.expect_err("do_panic() did not panic!")
.downcast::<String>().expect("Failed to cast to string!");
assert_eq!(expected, actual);
DROPPED.with(|c| {
// This should have been set to 'true' by DropTester
assert!(c.get());
});
HOOK_CALLED.with(|h| {
assert!(h.get());
});
}

3
tests/run-pass/panic1.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
panic!("Miri panic!");
}

View file

@ -0,0 +1 @@
thread 'main' panicked at 'Miri panic!', $DIR/panic1.rs:2:5

4
tests/run-pass/panic2.rs Normal file
View file

@ -0,0 +1,4 @@
fn main() {
let val = "Value".to_string();
panic!("Miri panic with value: {}", val);
}

View file

@ -0,0 +1 @@
thread 'main' panicked at 'Miri panic with value: Value', $DIR/panic2.rs:3:5