diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index 7344846d6d9d2..59b24b0f63ebf 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -26,7 +26,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { args: &[OpTy<'tcx, Provenance>], dest: &MPlaceTy<'tcx, Provenance>, ret: Option, - _unwind: mir::UnwindAction, + unwind: mir::UnwindAction, ) -> InterpResult<'tcx, Option>> { let this = self.eval_context_mut(); @@ -67,6 +67,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.return_to_block(ret)?; Ok(None) } + EmulateItemResult::NeedsUnwind => { + // Jump to the unwind block to begin unwinding. + this.unwind_to_block(unwind)?; + Ok(None) + } EmulateItemResult::AlreadyJumped => Ok(None), } } diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index a65da823e24cd..b363531a2dd56 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -87,6 +87,10 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { trace!("{:?}", this.dump_place(&dest.clone().into())); this.return_to_block(ret)?; } + EmulateItemResult::NeedsUnwind => { + // Jump to the unwind block to begin unwinding. + this.unwind_to_block(unwind)?; + } EmulateItemResult::AlreadyJumped => (), EmulateItemResult::NotSupported => { if let Some(body) = this.lookup_exported_symbol(link_name)? { diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index d9c4a2282c1fc..0e80c142b49ab 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -23,6 +23,8 @@ pub use unix::{DirTable, FdTable}; pub enum EmulateItemResult { /// The caller is expected to jump to the return block. NeedsJumping, + /// The caller is expected to jump to the unwind block. + NeedsUnwind, /// Jumping has already been taken care of. AlreadyJumped, /// The item is not supported. diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs index 396011cc22afc..ed23771d2b58d 100644 --- a/src/tools/miri/src/shims/unix/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/foreign_items.rs @@ -639,6 +639,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.gen_random(ptr, len)?; this.write_scalar(Scalar::from_target_usize(len, this), dest)?; } + "_Unwind_RaiseException" => { + trace!("_Unwind_RaiseException: {:?}", this.frame().instance); + + // Get the raw pointer stored in arg[0] (the panic payload). + let [payload] = this.check_shim(abi, Abi::C { unwind: true }, link_name, args)?; + let payload = this.read_scalar(payload)?; + let thread = this.active_thread_mut(); + thread.panic_payloads.push(payload); + + return Ok(EmulateItemResult::NeedsUnwind); + } // Incomplete shims that we "stub out" just to get pre-main initialization code to work. // These shims are enabled only when the caller is in the standard library. diff --git a/src/tools/miri/tests/pass/panic/unwind_dwarf.rs b/src/tools/miri/tests/pass/panic/unwind_dwarf.rs new file mode 100644 index 0000000000000..aa5ec05fbc061 --- /dev/null +++ b/src/tools/miri/tests/pass/panic/unwind_dwarf.rs @@ -0,0 +1,95 @@ +//@only-target-linux +#![feature(core_intrinsics, panic_unwind, rustc_attrs)] +#![allow(internal_features)] + +//! Unwinding using `_Unwind_RaiseException` + +extern crate unwind as uw; + +use std::any::Any; +use std::ptr; + +#[repr(C)] +struct Exception { + _uwe: uw::_Unwind_Exception, + cause: Box, +} + +pub fn panic(data: Box) -> u32 { + let exception = Box::new(Exception { + _uwe: uw::_Unwind_Exception { + exception_class: rust_exception_class(), + exception_cleanup, + private: [core::ptr::null(); uw::unwinder_private_data_size], + }, + cause: data, + }); + let exception_param = Box::into_raw(exception) as *mut uw::_Unwind_Exception; + return unsafe { uw::_Unwind_RaiseException(exception_param) as u32 }; + + extern "C" fn exception_cleanup( + _unwind_code: uw::_Unwind_Reason_Code, + _exception: *mut uw::_Unwind_Exception, + ) { + std::process::abort(); + } +} + +pub unsafe fn rust_panic_cleanup(ptr: *mut u8) -> Box { + let exception = ptr as *mut uw::_Unwind_Exception; + if (*exception).exception_class != rust_exception_class() { + std::process::abort(); + } + + let exception = exception.cast::(); + + let exception = Box::from_raw(exception as *mut Exception); + exception.cause +} + +fn rust_exception_class() -> uw::_Unwind_Exception_Class { + // M O Z \0 R U S T -- vendor, language + 0x4d4f5a_00_52555354 +} + +pub fn catch_unwind R>(f: F) -> Result> { + struct Data { + f: Option, + r: Option, + p: Option>, + } + + let mut data = Data { f: Some(f), r: None, p: None }; + + let data_ptr = ptr::addr_of_mut!(data) as *mut u8; + unsafe { + return if std::intrinsics::r#try(do_call::, data_ptr, do_catch::) == 0 { + Ok(data.r.take().unwrap()) + } else { + Err(data.p.take().unwrap()) + }; + } + + fn do_call R, R>(data: *mut u8) { + unsafe { + let data = &mut *data.cast::>(); + let f = data.f.take().unwrap(); + data.r = Some(f()); + } + } + + #[rustc_nounwind] + fn do_catch R, R>(data: *mut u8, payload: *mut u8) { + unsafe { + let obj = rust_panic_cleanup(payload); + (*data.cast::>()).p = Some(obj); + } + } +} + +fn main() { + assert_eq!( + catch_unwind(|| panic(Box::new(42))).unwrap_err().downcast::().unwrap(), + Box::new(42) + ); +}