From b3a1e127955ec77efb61d831811f96044278c0db Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sun, 14 Apr 2019 21:02:55 -0400 Subject: [PATCH] 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. --- src/fn_call.rs | 323 ++++++++++++++++++++++++++++++++-- src/helpers.rs | 8 +- src/lib.rs | 78 ++++++-- src/stacked_borrows.rs | 1 + tests/run-pass/catch_panic.rs | 50 ++++++ 5 files changed, 433 insertions(+), 27 deletions(-) create mode 100644 tests/run-pass/catch_panic.rs diff --git a/src/fn_call.rs b/src/fn_call.rs index 1fd10ff385..c5402fe0be 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1,6 +1,7 @@ use rustc::ty; use rustc::ty::layout::{Align, LayoutOf, Size}; -use rustc::hir::def_id::DefId; +use rustc::ty::InstanceDef; +use rustc_target::spec::PanicStrategy; use rustc::mir; use syntax::attr; @@ -18,10 +19,15 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' ret: Option, ) -> EvalResult<'tcx, Option<&'mir mir::Mir<'tcx>>> { let this = self.eval_context_mut(); - trace!("eval_fn_call: {:#?}, {:?}", instance, dest.map(|place| *place)); + trace!("eval_fn_call: {:#?}, {:?} {:?}", instance, instance.def_id(), dest.map(|place| *place)); // First, run the common hooks also supported by CTFE. - if this.hook_fn(instance, args, dest)? { + // We *don't* forward panic-related items to the common hooks, + // as we want to handle those specially + if Some(instance.def_id()) != this.tcx.lang_items().panic_fn() && + Some(instance.def_id()) != this.tcx.lang_items().begin_panic_fn() && + this.hook_fn(instance, args, dest)? { + this.goto_block(ret)?; return Ok(None); } @@ -39,11 +45,9 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' // 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 + // An external function that we (possibly) 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); + return Ok(this.emulate_foreign_item(instance, args, dest, ret)?); } // Otherwise, load the MIR. @@ -134,11 +138,12 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' /// This function will handle `goto_block` if needed. fn emulate_foreign_item( &mut self, - def_id: DefId, - args: &[OpTy<'tcx, Tag>], + instance: ty::Instance<'tcx>, + args: &[OpTy<'tcx, Borrow>], dest: Option>, ret: Option, - ) -> EvalResult<'tcx> { + ) -> EvalResult<'tcx, Option<&'mir mir::Mir<'tcx>>> { + let def_id = instance.def_id(); 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, "link_name") { @@ -151,8 +156,195 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' // First: functions that diverge. match link_name { - "__rust_start_panic" | "panic_impl" => { - return err!(MachineError("the evaluated program panicked".to_string())); + "panic_impl" => { + // Manually forward to 'panic_impl' lang item + let panic_impl_real = this.tcx.lang_items().panic_impl().unwrap(); + + return Ok(Some(this.load_mir(InstanceDef::Item(panic_impl_real))?)); + }, + "__rust_start_panic" => { + // This function has the signature: + // 'fn __rust_start_panic(payload: usize) -> u32;' + // + // The caller constructs 'payload' as follows + // 1. We start with a type implementing core::panic::BoxMeUp + // 2. We make this type into a trait object, obtaining a '&mut dyn BoxMeUp' + // 3. We obtain a raw pointer to the above mutable reference: that is, we make: + // '*mut &mut dyn BoxMeUp' + // 4. We convert the raw pointer to a 'usize' + // + + // When a panic occurs, we (carefully!) reverse the above steps + // to get back to the actual panic payload + // + // Even though our argument is a 'usize', Miri will have kept track + // of the fact that it was created via a cast from a pointer. + // This allows us to construct an ImmTy with the proper layout, + // and dereference it + // + // Reference: + // https://github.com/rust-lang/rust/blob/9ebf47851a357faa4cd97f4b1dc7835f6376e639/src/libpanic_unwind/lib.rs#L101 + // https://github.com/rust-lang/rust/blob/9ebf47851a357faa4cd97f4b1dc7835f6376e639/src/libpanic_unwind/lib.rs#L81 + // + // payload_raw now represents our '&mut dyn BoxMeUp' - a fat pointer + // + // Note that we intentially call deref_operand before checking + // This ensures that we always check the validity of the argument, + // even if we don't end up using it + + trace!("__rustc_start_panic: {:?}", this.frame().span); + + + // Read our 'usize' payload argument (which was made by casting + // a '*mut &mut dyn BoxMeUp' + let payload_raw = this.read_scalar(args[0])?.not_undef()?; + + // Construct an ImmTy, using the precomputed layout of '*mut &mut dyn BoxMeUp' + let imm_ty = ImmTy::from_scalar( + payload_raw, + this.machine.cached_data.as_ref().unwrap().box_me_up_layout + ); + + // Convert our ImmTy to an MPlace, and read it + let mplace = this.ref_to_mplace(imm_ty)?; + + // This is an '&mut dyn BoxMeUp' + let payload_dyn = this.read_immediate(mplace.into())?; + + // We deliberately do this after we do some validation of the + // 'payload'. This should help catch some basic errors in + // the caller of this function, even in abort mode + if this.tcx.tcx.sess.panic_strategy() == PanicStrategy::Abort { + return err!(MachineError("the evaluated program abort-panicked".to_string())); + } + + // This part is tricky - we need to call BoxMeUp::box_me_up + // on the vtable. + // + // core::panic::BoxMeUp is declared as follows: + // + // pub unsafe trait BoxMeUp { + // fn box_me_up(&mut self) -> *mut (dyn Any + Send); + // fn get(&mut self) -> &(dyn Any + Send); + // } + // + // box_me_up is the first method in the vtable. + // First, we extract the vtable pointer from our fat pointer, + // and check its alignment + + let vtable_ptr = payload_dyn.to_meta()?.expect("Expected fat pointer!").to_ptr()?; + let data_ptr = payload_dyn.to_scalar_ptr()?; + this.memory().check_align(vtable_ptr.into(), this.tcx.data_layout.pointer_align.abi)?; + + // Now, we derefernce the vtable pointer. + let alloc = this.memory().get(vtable_ptr.alloc_id)?; + + // Finally, we extract the pointer to 'box_me_up'. + // The vtable is layed out in memory like this: + // + //``` + // (usize) + // (usize) + // (usize) + // (usize) + // (usize) + // ... + // (usize) + //``` + // + // Since box_me_up is the first method pointer + // in the vtable, we use an offset of 3 pointer sizes + // (skipping over , , and ) + + let box_me_up_ptr = alloc.read_ptr_sized( + this, + vtable_ptr.offset(this.pointer_size() * 3, this)? + )?.to_ptr()?; + + // Get the actual function instance + let box_me_up_fn = this.memory().get_fn(box_me_up_ptr)?; + let box_me_up_mir = this.load_mir(box_me_up_fn.def)?; + + // Extract the signature + // We know that there are no HRBTs here, so it's fine to use + // skip_binder + let fn_sig_temp = box_me_up_fn.ty(*this.tcx).fn_sig(*this.tcx); + let fn_sig = fn_sig_temp.skip_binder(); + + // This is the layout of '*mut (dyn Any + Send)', which + // is the return type of 'box_me_up' + let dyn_ptr_layout = this.layout_of(fn_sig.output())?; + + // We allocate space to store the return value of box_me_up: + // '*mut (dyn Any + Send)', which is a fat + + let temp_ptr = this.allocate(dyn_ptr_layout, MiriMemoryKind::UnwindHelper.into()); + + // Keep track of our current frame + // This allows us to step throgh the exection of 'box_me_up', + // exiting when we get back to this frame + let cur_frame = this.cur_frame(); + + this.push_stack_frame( + box_me_up_fn, + box_me_up_mir.span, + box_me_up_mir, + Some(temp_ptr.into()), + StackPopCleanup::None { cleanup: true } + )?; + + let mut args = this.frame().mir.args_iter(); + let arg_0 = this.eval_place(&mir::Place::Base(mir::PlaceBase::Local(args.next().unwrap())))?; + this.write_scalar(data_ptr, arg_0)?; + + // Step through execution of 'box_me_up' + // We know that we're finished when our stack depth + // returns to where it was before. + // + // Note that everything will get completely screwed up + // if 'box_me_up' panics. This is fine, since this + // function should never panic, as it's part of the core + // panic handling infrastructure + // + // Normally, we would just let Miri drive + // the execution of this stack frame. + // However, we need to access its return value + // in order to properly unwind. + // + // When we 'return' from '__rustc_start_panic', + // we need to be executing the panic catch handler. + // Therefore, we take care all all of the unwinding logic + // here, instead of letting the Miri main loop do it + while this.cur_frame() != cur_frame { + this.step()?; + } + + // '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 real_ret = this.read_immediate(temp_ptr.into())?; + let real_ret_data = real_ret.to_scalar_ptr()?; + let real_ret_vtable = real_ret.to_meta()?.expect("Expected fat pointer"); + + // We're in panic unwind mode. We pop off stack + // frames until one of two things happens: we reach + // a frame with 'catch_panic' set, or we pop of all frames + // + // If we pop off all frames without encountering 'catch_panic', + // we exut. + // + // If we encounter 'catch_panic', we continue execution at that + // frame, filling in data from the panic + // + unwind_stack(this, real_ret_data, real_ret_vtable)?; + + this.memory_mut().deallocate(temp_ptr.to_ptr()?, None, MiriMemoryKind::UnwindHelper.into())?; + this.dump_place(*dest.expect("dest is None!")); + + return Ok(None) + } "exit" | "ExitProcess" => { // it's really u32 for ExitProcess, but we have to put it into the `Exit` error variant anyway @@ -340,13 +532,27 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' // 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])?.to_ptr()?; let data = this.read_scalar(args[1])?.not_undef()?; + let data_ptr = this.deref_operand(args[2])?; + let vtable_ptr = this.deref_operand(args[3])?; let f_instance = this.memory().get_fn(f)?; this.write_null(dest)?; trace!("__rust_maybe_catch_panic: {:?}", f_instance); + // In unwind mode, we tag this frame with some extra data. + // This lets '__rust_start_panic' know that it should jump back + // to this frame is a panic occurs. + if this.tcx.tcx.sess.panic_strategy() == PanicStrategy::Unwind { + this.frame_mut().extra.catch_panic = Some(UnwindData { + data: data.to_ptr()?, + data_ptr, + vtable_ptr, + dest: dest.clone(), + ret + }) + } + // Now we make a function call. // TODO: consider making this reusable? `InterpretCx::step` does something similar // for the TLS destructors, and of course `eval_main`. @@ -360,6 +566,7 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' // Directly return to caller. StackPopCleanup::Goto(Some(ret)), )?; + let mut args = this.frame().mir.args_iter(); let arg_local = args.next().ok_or_else(|| @@ -377,7 +584,7 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' this.write_null(dest)?; // Don't fall through, we do *not* want to `goto_block`! - return Ok(()); + return Ok(None); } "memcmp" => { @@ -890,7 +1097,7 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' this.goto_block(Some(ret))?; this.dump_place(*dest); - Ok(()) + Ok(None) } fn write_null(&mut self, dest: PlaceTy<'tcx, Tag>) -> EvalResult<'tcx> { @@ -944,3 +1151,89 @@ fn gen_random<'a, 'mir, 'tcx>( this.memory_mut().get_mut(ptr.alloc_id)? .write_bytes(tcx, ptr, &data) } + +/// A helper method to unwind the stack. +/// +/// We execute the 'unwind' blocks associated with frame +/// terminators as we go along (these blocks are responsible +/// for dropping frame locals in the event of a panic) +/// +/// When we find our target frame, we write the panic payload +/// directly into its locals, and jump to it. +/// After that, panic handling is done - from the perspective +/// of the caller of '__rust_maybe_catch_panic', the function +/// has 'returned' normally, after which point Miri excecution +/// can proceeed normally. +fn unwind_stack<'a, 'mir, 'tcx>( + this: &mut MiriEvalContext<'a, 'mir, 'tcx>, + payload_data_ptr: Scalar, + payload_vtable_ptr: Scalar +) -> EvalResult<'tcx> { + + let mut found = false; + + while !this.stack().is_empty() { + // When '__rust_maybe_catch_panic' is called, it marks is frame + // with 'catch_panic'. When we find this marker, we've found + // our target frame to jump to. + if let Some(unwind_data) = this.frame_mut().extra.catch_panic.take() { + trace!("unwinding: found target frame: {:?}", this.frame().span); + + let data_ptr = unwind_data.data_ptr.clone(); + let vtable_ptr = unwind_data.vtable_ptr.clone(); + let dest = unwind_data.dest.clone(); + let ret = unwind_data.ret.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') + + this.write_scalar(payload_data_ptr, data_ptr.into())?; + this.write_scalar(payload_vtable_ptr, vtable_ptr.into())?; + + // We 'return' the value 1 from __rust_maybe_catch_panic, + // since there was a panic + this.write_scalar(Scalar::from_int(1, dest.layout.size), dest)?; + + // We're done - continue execution in the frame of the function + // that called '__rust_maybe_catch_panic,' + this.goto_block(Some(ret))?; + found = true; + + break; + } else { + // This frame is above our target frame on the call stack. + // We pop it off the stack, running its 'unwind' block if applicable + trace!("unwinding: popping frame: {:?}", this.frame().span); + let block = &this.frame().mir.basic_blocks()[this.frame().block]; + + // All frames in the call stack should be executing their terminators., + // as that's the only way for a basic block to perform a function call + if let Some(stmt) = block.statements.get(this.frame().stmt) { + panic!("Unexpcted statement '{:?}' for frame {:?}", stmt, this.frame().span); + } + + // We're only interested in terminator types which allow for a cleanuup + // block (e.g. Call), and that also actually provide one + if let Some(Some(unwind)) = block.terminator().unwind() { + this.goto_block(Some(*unwind))?; + + // Run the 'unwind' block until we encounter + // a 'Resume', which indicates that the block + // is done. + assert_eq!(this.run()?, StepOutcome::Resume); + } + + // Pop this frame, and continue on to the next one + this.pop_stack_frame_unwind()?; + } + } + + if !found { + // The 'start_fn' lang item should always install a panic handler + return err!(Unreachable); + } + return Ok(()) +} diff --git a/src/helpers.rs b/src/helpers.rs index f468d25603..e6cc30bbba 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -9,7 +9,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'a, 'mir, 'tcx> for crate::MiriEvalContext<' pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<'a, 'mir, 'tcx> { /// Gets an instance for a path. - fn resolve_path(&self, path: &[&str]) -> EvalResult<'tcx, ty::Instance<'tcx>> { + fn resolve_did(&self, path: &[&str]) -> EvalResult<'tcx, DefId> { let this = self.eval_context_ref(); this.tcx .crates() @@ -27,7 +27,7 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' for item in mem::replace(&mut items, Default::default()).iter() { if item.ident.name == *segment { if path_it.peek().is_none() { - return Some(ty::Instance::mono(this.tcx.tcx, item.def.def_id())); + return Some(item.def.def_id()); } items = this.tcx.item_children(item.def.def_id()); @@ -43,6 +43,10 @@ pub trait EvalContextExt<'a, 'mir, 'tcx: 'a + 'mir>: crate::MiriEvalContextExt<' }) } + fn resolve_path(&self, path: &[&str]) -> EvalResult<'tcx, ty::Instance<'tcx>> { + Ok(ty::Instance::mono(self.eval_context_ref().tcx.tcx, self.resolve_did(path)?)) + } + /// Visits the memory covered by `place`, sensitive to freezing: the 3rd parameter /// will be true if this is frozen, false if this is in an `UnsafeCell`. fn visit_freeze_sensitive( diff --git a/src/lib.rs b/src/lib.rs index 683eee0cb7..356706af50 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,8 +28,10 @@ use std::rc::Rc; use rand::rngs::StdRng; use rand::SeedableRng; +use rustc_target::spec::PanicStrategy; +use rustc::ty::{ExistentialPredicate, ExistentialTraitRef, RegionKind, List}; use rustc::ty::{self, TyCtxt, query::TyCtxtAt}; -use rustc::ty::layout::{LayoutOf, Size, Align}; +use rustc::ty::layout::{LayoutOf, Size, Align, TyLayout}; use rustc::hir::{self, def_id::DefId}; use rustc::mir; pub use rustc_mir::interpret::*; @@ -66,7 +68,7 @@ pub struct MiriConfig { pub args: Vec, // The seed to use when non-determinism is required (e.g. getrandom()) - pub seed: Option + pub seed: Option, } // Used by priroda. @@ -205,6 +207,30 @@ pub fn create_ecx<'a, 'mir: 'a, 'tcx: 'mir>( } } + // Cache some data used by unwinding + if ecx.tcx.tcx.sess.panic_strategy() == PanicStrategy::Unwind { + + // We build up the layout of '*mut &mut dyn core::panic::BoxMeUp' + let box_me_up_did = ecx.resolve_did(&["core", "panic", "BoxMeUp"])?; + let traits = &[ExistentialPredicate::Trait(ExistentialTraitRef { + def_id: box_me_up_did, + substs: List::empty() + })]; + + let me_mut_dyn = ecx.tcx.tcx.mk_dynamic( + ty::Binder::dummy(ecx.tcx.tcx.intern_existential_predicates(traits)), + &RegionKind::ReErased + ); + + let me_mut_ref = ecx.tcx.tcx.mk_mut_ref(&RegionKind::ReErased, me_mut_dyn); + let me_mut_raw = ecx.tcx.tcx.mk_mut_ptr(me_mut_ref); + let box_me_up_layout = ecx.layout_of(me_mut_raw)?; + + ecx.machine.cached_data = Some(CachedTypes { + box_me_up_layout + }); + } + assert!(args.next().is_none(), "start lang item has more arguments than expected"); Ok(ecx) @@ -298,6 +324,7 @@ pub enum MiriMemoryKind { C, /// Part of env var emulation. Env, + UnwindHelper, /// Mutable statics. MutStatic, } @@ -314,7 +341,7 @@ impl MayLeak for MiriMemoryKind { fn may_leak(self) -> bool { use self::MiriMemoryKind::*; match self { - Rust | C => false, + Rust | C | UnwindHelper => false, Env | MutStatic => true, } } @@ -343,7 +370,16 @@ pub struct Evaluator<'tcx> { /// The random number generator to use if Miri /// is running in non-deterministic mode - pub(crate) rng: Option + pub(crate) rng: Option, + + /// Extra information needed for unwinding + /// This is 'None' when we're running in abort mode + pub(crate) cached_data: Option> +} + +pub struct CachedTypes<'tcx> { + /// The layout of the type '*mut &mut dyn core::panic::BoxMeUp' + box_me_up_layout: TyLayout<'tcx>, } impl<'tcx> Evaluator<'tcx> { @@ -356,7 +392,8 @@ impl<'tcx> Evaluator<'tcx> { last_error: 0, tls: TlsData::default(), validate, - rng: seed.map(|s| StdRng::seed_from_u64(s)) + rng: seed.map(|s| StdRng::seed_from_u64(s)), + cached_data: None } } } @@ -381,10 +418,28 @@ impl<'a, 'mir, 'tcx> MiriEvalContextExt<'a, 'mir, 'tcx> for MiriEvalContext<'a, } } +pub struct FrameData<'tcx> { + pub call_id: stacked_borrows::CallId, + pub catch_panic: Option> +} + +/// 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, + pub data_ptr: MPlaceTy<'tcx, stacked_borrows::Borrow>, + pub vtable_ptr: MPlaceTy<'tcx, stacked_borrows::Borrow>, + pub dest: PlaceTy<'tcx, Borrow>, + pub ret: mir::BasicBlock +} + impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> { type MemoryKinds = MiriMemoryKind; - type FrameExtra = stacked_borrows::CallId; + type FrameExtra = FrameData<'tcx>; type MemoryExtra = stacked_borrows::MemoryState; type AllocExtra = stacked_borrows::Stacks; type PointerTag = Tag; @@ -569,15 +624,18 @@ impl<'a, 'mir, 'tcx> Machine<'a, 'mir, 'tcx> for Evaluator<'tcx> { #[inline(always)] fn stack_push( ecx: &mut InterpretCx<'a, 'mir, 'tcx, Self>, - ) -> EvalResult<'tcx, stacked_borrows::CallId> { - Ok(ecx.memory().extra.borrow_mut().new_call()) + ) -> EvalResult<'tcx, FrameData<'tcx>> { + Ok(FrameData { + call_id: ecx.memory().extra.borrow_mut().new_call(), + catch_panic: None + }) } #[inline(always)] fn stack_pop( ecx: &mut InterpretCx<'a, 'mir, 'tcx, Self>, - extra: stacked_borrows::CallId, + extra: FrameData, ) -> EvalResult<'tcx> { - Ok(ecx.memory().extra.borrow_mut().end_call(extra)) + Ok(ecx.memory().extra.borrow_mut().end_call(extra.call_id)) } } diff --git a/src/stacked_borrows.rs b/src/stacked_borrows.rs index bac35796ed..7f887c54b9 100644 --- a/src/stacked_borrows.rs +++ b/src/stacked_borrows.rs @@ -566,6 +566,7 @@ trait EvalContextPrivExt<'a, 'mir, 'tcx: 'a+'mir>: crate::MiriEvalContextExt<'a, trace!("reborrow: {:?} reference {} derived from {} (pointee {}): {:?}, size {}", kind, new_tag, ptr.tag, place.layout.ty, ptr, size.bytes()); + // Get the allocation. It might not be mutable, so we cannot use `get_mut`. let alloc = this.memory().get(ptr.alloc_id)?; alloc.check_bounds(this, ptr, size)?; diff --git a/tests/run-pass/catch_panic.rs b/tests/run-pass/catch_panic.rs new file mode 100644 index 0000000000..0a2bcce365 --- /dev/null +++ b/tests/run-pass/catch_panic.rs @@ -0,0 +1,50 @@ +use std::panic::catch_unwind; +use std::cell::Cell; + +thread_local! { + static MY_COUNTER: Cell = Cell::new(0); + static DROPPED: Cell = 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() { + let res = catch_unwind(do_panic_counter); + let expected: Box = Box::new("Hello from panic: 0".to_string()); + let actual = res.expect_err("do_panic() did not panic!") + .downcast::().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()); + }); +} +