about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbjorn3 <17426603+bjorn3@users.noreply.github.com>2024-02-24 14:27:27 +0000
committerRalf Jung <post@ralfj.de>2024-05-19 19:06:31 +0200
commit42cb1ffa3604748073670003fcfeba0277e38c52 (patch)
tree6b3eb8d3c114236b2941045e60329d4df40d34cd
parentf7520e40f72c002a6dcf70db7a7d7d0e74549af6 (diff)
downloadrust-42cb1ffa3604748073670003fcfeba0277e38c52.tar.gz
rust-42cb1ffa3604748073670003fcfeba0277e38c52.zip
Directly implement native exception raise methods in miri
Windows still needs the old custom ABI as SEH unwinding isn't supported
by miri. Unlike DWARF unwinding it preserves all stack frames until
right after the do_catch function has executed. Because of this
panic_unwind stack allocates the exception object. Miri can't currently
model unwinding without destroying stack frames and as such will report
a use-after-free of the exception object.
-rw-r--r--src/tools/miri/src/intrinsics/mod.rs7
-rw-r--r--src/tools/miri/src/shims/foreign_items.rs4
-rw-r--r--src/tools/miri/src/shims/mod.rs2
-rw-r--r--src/tools/miri/src/shims/unix/foreign_items.rs11
-rw-r--r--src/tools/miri/tests/pass/panic/unwind_dwarf.rs95
5 files changed, 118 insertions, 1 deletions
diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs
index 7344846d6d9..59b24b0f63e 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<mir::BasicBlock>,
-        _unwind: mir::UnwindAction,
+        unwind: mir::UnwindAction,
     ) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
         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 a65da823e24..b363531a2dd 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 d9c4a2282c1..0e80c142b49 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 396011cc22a..ed23771d2b5 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 00000000000..aa5ec05fbc0
--- /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<dyn Any + Send>,
+}
+
+pub fn panic(data: Box<dyn Any + Send>) -> 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<dyn Any + Send> {
+    let exception = ptr as *mut uw::_Unwind_Exception;
+    if (*exception).exception_class != rust_exception_class() {
+        std::process::abort();
+    }
+
+    let exception = exception.cast::<Exception>();
+
+    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: FnOnce() -> R>(f: F) -> Result<R, Box<dyn Any + Send>> {
+    struct Data<F, R> {
+        f: Option<F>,
+        r: Option<R>,
+        p: Option<Box<dyn Any + Send>>,
+    }
+
+    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::<F, R>, data_ptr, do_catch::<F, R>) == 0 {
+            Ok(data.r.take().unwrap())
+        } else {
+            Err(data.p.take().unwrap())
+        };
+    }
+
+    fn do_call<F: FnOnce() -> R, R>(data: *mut u8) {
+        unsafe {
+            let data = &mut *data.cast::<Data<F, R>>();
+            let f = data.f.take().unwrap();
+            data.r = Some(f());
+        }
+    }
+
+    #[rustc_nounwind]
+    fn do_catch<F: FnOnce() -> R, R>(data: *mut u8, payload: *mut u8) {
+        unsafe {
+            let obj = rust_panic_cleanup(payload);
+            (*data.cast::<Data<F, R>>()).p = Some(obj);
+        }
+    }
+}
+
+fn main() {
+    assert_eq!(
+        catch_unwind(|| panic(Box::new(42))).unwrap_err().downcast::<i32>().unwrap(),
+        Box::new(42)
+    );
+}