From 5f1a0afd22c7f1e26782467503d32d026c55fc0c Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 21 Oct 2019 02:46:04 +0100 Subject: [PATCH] Allow foreign exceptions to unwind through Rust code --- .../src/language-features/lang-items.md | 3 +- src/libpanic_unwind/dwarf/eh.rs | 15 +- src/libpanic_unwind/gcc.rs | 287 ++++++++++-------- src/libpanic_unwind/lib.rs | 8 +- src/libpanic_unwind/seh64_gnu.rs | 127 -------- src/libunwind/libunwind.rs | 27 ++ .../foreign-exceptions/Makefile | 10 + .../foreign-exceptions/foo.cpp | 59 ++++ .../foreign-exceptions/foo.rs | 63 ++++ 9 files changed, 338 insertions(+), 261 deletions(-) delete mode 100644 src/libpanic_unwind/seh64_gnu.rs create mode 100644 src/test/run-make-fulldeps/foreign-exceptions/Makefile create mode 100644 src/test/run-make-fulldeps/foreign-exceptions/foo.cpp create mode 100644 src/test/run-make-fulldeps/foreign-exceptions/foo.rs diff --git a/src/doc/unstable-book/src/language-features/lang-items.md b/src/doc/unstable-book/src/language-features/lang-items.md index 3ee024c6b58..60fdc6c7b69 100644 --- a/src/doc/unstable-book/src/language-features/lang-items.md +++ b/src/doc/unstable-book/src/language-features/lang-items.md @@ -249,9 +249,8 @@ the source code. - Runtime - `start`: `libstd/rt.rs` - `eh_personality`: `libpanic_unwind/emcc.rs` (EMCC) - - `eh_personality`: `libpanic_unwind/seh64_gnu.rs` (SEH64 GNU) + - `eh_personality`: `libpanic_unwind/gcc.rs` (GNU) - `eh_personality`: `libpanic_unwind/seh.rs` (SEH) - - `eh_unwind_resume`: `libpanic_unwind/seh64_gnu.rs` (SEH64 GNU) - `eh_unwind_resume`: `libpanic_unwind/gcc.rs` (GCC) - `msvc_try_filter`: `libpanic_unwind/seh.rs` (SEH) - `panic`: `libcore/panicking.rs` diff --git a/src/libpanic_unwind/dwarf/eh.rs b/src/libpanic_unwind/dwarf/eh.rs index 07fa2971847..1e9e7e4b835 100644 --- a/src/libpanic_unwind/dwarf/eh.rs +++ b/src/libpanic_unwind/dwarf/eh.rs @@ -51,7 +51,7 @@ pub enum EHAction { pub const USING_SJLJ_EXCEPTIONS: bool = cfg!(all(target_os = "ios", target_arch = "arm")); -pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>) +pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>, foreign_exception: bool) -> Result { if lsda.is_null() { @@ -96,7 +96,7 @@ pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>) return Ok(EHAction::None) } else { let lpad = lpad_base + cs_lpad; - return Ok(interpret_cs_action(cs_action, lpad)) + return Ok(interpret_cs_action(cs_action, lpad, foreign_exception)) } } } @@ -121,16 +121,23 @@ pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>) // Can never have null landing pad for sjlj -- that would have // been indicated by a -1 call site index. let lpad = (cs_lpad + 1) as usize; - return Ok(interpret_cs_action(cs_action, lpad)) + return Ok(interpret_cs_action(cs_action, lpad, foreign_exception)) } } } } -fn interpret_cs_action(cs_action: u64, lpad: usize) -> EHAction { +fn interpret_cs_action(cs_action: u64, lpad: usize, foreign_exception: bool) -> EHAction { if cs_action == 0 { + // If cs_action is 0 then this is a cleanup (Drop::drop). We run these + // for both Rust panics and foriegn exceptions. EHAction::Cleanup(lpad) + } else if foreign_exception { + // catch_unwind should not catch foreign exceptions, only Rust panics. + // Instead just continue unwinding. + EHAction::None } else { + // Stop unwinding Rust panics at catch_unwind. EHAction::Catch(lpad) } } diff --git a/src/libpanic_unwind/gcc.rs b/src/libpanic_unwind/gcc.rs index a35847c85fc..4f572fe21b3 100644 --- a/src/libpanic_unwind/gcc.rs +++ b/src/libpanic_unwind/gcc.rs @@ -133,133 +133,176 @@ const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 // https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/eh_personality.cc // https://github.com/gcc-mirror/gcc/blob/trunk/libgcc/unwind-c.c -// The personality routine for most of our targets, except ARM, which has a slightly different ABI -// (however, iOS goes here as it uses SjLj unwinding). Also, the 64-bit Windows implementation -// lives in seh64_gnu.rs -#[cfg(all(any(target_os = "ios", target_os = "netbsd", not(target_arch = "arm"))))] -#[lang = "eh_personality"] -#[no_mangle] -#[allow(unused)] -unsafe extern "C" fn rust_eh_personality(version: c_int, - actions: uw::_Unwind_Action, - exception_class: uw::_Unwind_Exception_Class, - exception_object: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code { - if version != 1 { - return uw::_URC_FATAL_PHASE1_ERROR; - } - let eh_action = match find_eh_action(context) { - Ok(action) => action, - Err(_) => return uw::_URC_FATAL_PHASE1_ERROR, - }; - if actions as i32 & uw::_UA_SEARCH_PHASE as i32 != 0 { - match eh_action { - EHAction::None | - EHAction::Cleanup(_) => uw::_URC_CONTINUE_UNWIND, - EHAction::Catch(_) => uw::_URC_HANDLER_FOUND, - EHAction::Terminate => uw::_URC_FATAL_PHASE1_ERROR, +cfg_if::cfg_if! { + if #[cfg(all(target_arch = "arm", not(target_os = "ios"), not(target_os = "netbsd")))] { + // ARM EHABI personality routine. + // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf + // + // iOS uses the default routine instead since it uses SjLj unwinding. + #[lang = "eh_personality"] + #[no_mangle] + unsafe extern "C" fn rust_eh_personality(state: uw::_Unwind_State, + exception_object: *mut uw::_Unwind_Exception, + context: *mut uw::_Unwind_Context) + -> uw::_Unwind_Reason_Code { + let state = state as c_int; + let action = state & uw::_US_ACTION_MASK as c_int; + let search_phase = if action == uw::_US_VIRTUAL_UNWIND_FRAME as c_int { + // Backtraces on ARM will call the personality routine with + // state == _US_VIRTUAL_UNWIND_FRAME | _US_FORCE_UNWIND. In those cases + // we want to continue unwinding the stack, otherwise all our backtraces + // would end at __rust_try + if state & uw::_US_FORCE_UNWIND as c_int != 0 { + return continue_unwind(exception_object, context); + } + true + } else if action == uw::_US_UNWIND_FRAME_STARTING as c_int { + false + } else if action == uw::_US_UNWIND_FRAME_RESUME as c_int { + return continue_unwind(exception_object, context); + } else { + return uw::_URC_FAILURE; + }; + + // The DWARF unwinder assumes that _Unwind_Context holds things like the function + // and LSDA pointers, however ARM EHABI places them into the exception object. + // To preserve signatures of functions like _Unwind_GetLanguageSpecificData(), which + // take only the context pointer, GCC personality routines stash a pointer to + // exception_object in the context, using location reserved for ARM's + // "scratch register" (r12). + uw::_Unwind_SetGR(context, + uw::UNWIND_POINTER_REG, + exception_object as uw::_Unwind_Ptr); + // ...A more principled approach would be to provide the full definition of ARM's + // _Unwind_Context in our libunwind bindings and fetch the required data from there + // directly, bypassing DWARF compatibility functions. + + let exception_class = (*exception_object).exception_class; + let foreign_exception = exception_class != rust_exception_class(); + let eh_action = match find_eh_action(context, foreign_exception) { + Ok(action) => action, + Err(_) => return uw::_URC_FAILURE, + }; + if search_phase { + match eh_action { + EHAction::None | + EHAction::Cleanup(_) => return continue_unwind(exception_object, context), + EHAction::Catch(_) => return uw::_URC_HANDLER_FOUND, + EHAction::Terminate => return uw::_URC_FAILURE, + } + } else { + match eh_action { + EHAction::None => return continue_unwind(exception_object, context), + EHAction::Cleanup(lpad) | + EHAction::Catch(lpad) => { + uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, + exception_object as uintptr_t); + uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 0); + uw::_Unwind_SetIP(context, lpad); + return uw::_URC_INSTALL_CONTEXT; + } + EHAction::Terminate => return uw::_URC_FAILURE, + } + } + + // On ARM EHABI the personality routine is responsible for actually + // unwinding a single stack frame before returning (ARM EHABI Sec. 6.1). + unsafe fn continue_unwind(exception_object: *mut uw::_Unwind_Exception, + context: *mut uw::_Unwind_Context) + -> uw::_Unwind_Reason_Code { + if __gnu_unwind_frame(exception_object, context) == uw::_URC_NO_REASON { + uw::_URC_CONTINUE_UNWIND + } else { + uw::_URC_FAILURE + } + } + // defined in libgcc + extern "C" { + fn __gnu_unwind_frame(exception_object: *mut uw::_Unwind_Exception, + context: *mut uw::_Unwind_Context) + -> uw::_Unwind_Reason_Code; + } } } else { - match eh_action { - EHAction::None => uw::_URC_CONTINUE_UNWIND, - EHAction::Cleanup(lpad) | - EHAction::Catch(lpad) => { - uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, exception_object as uintptr_t); - uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 0); - uw::_Unwind_SetIP(context, lpad); - uw::_URC_INSTALL_CONTEXT + // Default personality routine, which is used directly on most targets + // and indirectly on Windows x86_64 via SEH. + unsafe extern "C" fn rust_eh_personality_impl(version: c_int, + actions: uw::_Unwind_Action, + exception_class: uw::_Unwind_Exception_Class, + exception_object: *mut uw::_Unwind_Exception, + context: *mut uw::_Unwind_Context) + -> uw::_Unwind_Reason_Code { + if version != 1 { + return uw::_URC_FATAL_PHASE1_ERROR; + } + let foreign_exception = exception_class != rust_exception_class(); + let eh_action = match find_eh_action(context, foreign_exception) { + Ok(action) => action, + Err(_) => return uw::_URC_FATAL_PHASE1_ERROR, + }; + if actions as i32 & uw::_UA_SEARCH_PHASE as i32 != 0 { + match eh_action { + EHAction::None | + EHAction::Cleanup(_) => uw::_URC_CONTINUE_UNWIND, + EHAction::Catch(_) => uw::_URC_HANDLER_FOUND, + EHAction::Terminate => uw::_URC_FATAL_PHASE1_ERROR, + } + } else { + match eh_action { + EHAction::None => uw::_URC_CONTINUE_UNWIND, + EHAction::Cleanup(lpad) | + EHAction::Catch(lpad) => { + uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, + exception_object as uintptr_t); + uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 0); + uw::_Unwind_SetIP(context, lpad); + uw::_URC_INSTALL_CONTEXT + } + EHAction::Terminate => uw::_URC_FATAL_PHASE2_ERROR, + } + } + } + + cfg_if::cfg_if! { + if #[cfg(all(windows, target_arch = "x86_64", target_env = "gnu"))] { + // On x86_64 MinGW targets, the unwinding mechanism is SEH however the unwind + // handler data (aka LSDA) uses GCC-compatible encoding. + #[lang = "eh_personality"] + #[no_mangle] + #[allow(nonstandard_style)] + unsafe extern "C" fn rust_eh_personality(exceptionRecord: *mut uw::EXCEPTION_RECORD, + establisherFrame: uw::LPVOID, + contextRecord: *mut uw::CONTEXT, + dispatcherContext: *mut uw::DISPATCHER_CONTEXT) + -> uw::EXCEPTION_DISPOSITION { + uw::_GCC_specific_handler(exceptionRecord, + establisherFrame, + contextRecord, + dispatcherContext, + rust_eh_personality_impl) + } + } else { + // The personality routine for most of our targets. + #[lang = "eh_personality"] + #[no_mangle] + unsafe extern "C" fn rust_eh_personality(version: c_int, + actions: uw::_Unwind_Action, + exception_class: uw::_Unwind_Exception_Class, + exception_object: *mut uw::_Unwind_Exception, + context: *mut uw::_Unwind_Context) + -> uw::_Unwind_Reason_Code { + rust_eh_personality_impl(version, + actions, + exception_class, + exception_object, + context) + } } - EHAction::Terminate => uw::_URC_FATAL_PHASE2_ERROR, } } } -// ARM EHABI personality routine. -// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf -#[cfg(all(target_arch = "arm", not(target_os = "ios"), not(target_os = "netbsd")))] -#[lang = "eh_personality"] -#[no_mangle] -unsafe extern "C" fn rust_eh_personality(state: uw::_Unwind_State, - exception_object: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code { - let state = state as c_int; - let action = state & uw::_US_ACTION_MASK as c_int; - let search_phase = if action == uw::_US_VIRTUAL_UNWIND_FRAME as c_int { - // Backtraces on ARM will call the personality routine with - // state == _US_VIRTUAL_UNWIND_FRAME | _US_FORCE_UNWIND. In those cases - // we want to continue unwinding the stack, otherwise all our backtraces - // would end at __rust_try - if state & uw::_US_FORCE_UNWIND as c_int != 0 { - return continue_unwind(exception_object, context); - } - true - } else if action == uw::_US_UNWIND_FRAME_STARTING as c_int { - false - } else if action == uw::_US_UNWIND_FRAME_RESUME as c_int { - return continue_unwind(exception_object, context); - } else { - return uw::_URC_FAILURE; - }; - - // The DWARF unwinder assumes that _Unwind_Context holds things like the function - // and LSDA pointers, however ARM EHABI places them into the exception object. - // To preserve signatures of functions like _Unwind_GetLanguageSpecificData(), which - // take only the context pointer, GCC personality routines stash a pointer to exception_object - // in the context, using location reserved for ARM's "scratch register" (r12). - uw::_Unwind_SetGR(context, - uw::UNWIND_POINTER_REG, - exception_object as uw::_Unwind_Ptr); - // ...A more principled approach would be to provide the full definition of ARM's - // _Unwind_Context in our libunwind bindings and fetch the required data from there directly, - // bypassing DWARF compatibility functions. - - let eh_action = match find_eh_action(context) { - Ok(action) => action, - Err(_) => return uw::_URC_FAILURE, - }; - if search_phase { - match eh_action { - EHAction::None | - EHAction::Cleanup(_) => return continue_unwind(exception_object, context), - EHAction::Catch(_) => return uw::_URC_HANDLER_FOUND, - EHAction::Terminate => return uw::_URC_FAILURE, - } - } else { - match eh_action { - EHAction::None => return continue_unwind(exception_object, context), - EHAction::Cleanup(lpad) | - EHAction::Catch(lpad) => { - uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, exception_object as uintptr_t); - uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 0); - uw::_Unwind_SetIP(context, lpad); - return uw::_URC_INSTALL_CONTEXT; - } - EHAction::Terminate => return uw::_URC_FAILURE, - } - } - - // On ARM EHABI the personality routine is responsible for actually - // unwinding a single stack frame before returning (ARM EHABI Sec. 6.1). - unsafe fn continue_unwind(exception_object: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code { - if __gnu_unwind_frame(exception_object, context) == uw::_URC_NO_REASON { - uw::_URC_CONTINUE_UNWIND - } else { - uw::_URC_FAILURE - } - } - // defined in libgcc - extern "C" { - fn __gnu_unwind_frame(exception_object: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code; - } -} - -unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) +unsafe fn find_eh_action(context: *mut uw::_Unwind_Context, foreign_exception: bool) -> Result { let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8; @@ -273,11 +316,11 @@ unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) get_text_start: &|| uw::_Unwind_GetTextRelBase(context), get_data_start: &|| uw::_Unwind_GetDataRelBase(context), }; - eh::find_eh_action(lsda, &eh_context) + eh::find_eh_action(lsda, &eh_context, foreign_exception) } // See docs in the `unwind` module. -#[cfg(all(target_os="windows", target_arch = "x86", target_env="gnu"))] +#[cfg(all(target_os="windows", any(target_arch = "x86", target_arch = "x86_64"), target_env="gnu"))] #[lang = "eh_unwind_resume"] #[unwind(allowed)] unsafe extern "C" fn rust_eh_unwind_resume(panic_ctx: *mut u8) -> ! { diff --git a/src/libpanic_unwind/lib.rs b/src/libpanic_unwind/lib.rs index 2089a02083c..50d067b56e4 100644 --- a/src/libpanic_unwind/lib.rs +++ b/src/libpanic_unwind/lib.rs @@ -5,9 +5,8 @@ //! essentially gets categorized into three buckets currently: //! //! 1. MSVC targets use SEH in the `seh.rs` file. -//! 2. The 64-bit MinGW target half-uses SEH and half-use gcc-like information -//! in the `seh64_gnu.rs` module. -//! 3. All other targets use libunwind/libgcc in the `gcc/mod.rs` module. +//! 2. Emscripten uses C++ exceptions in the `emcc.rs` file. +//! 3. All other targets use libunwind/libgcc in the `gcc.rs` file. //! //! More documentation about each implementation can be found in the respective //! module. @@ -52,9 +51,6 @@ cfg_if::cfg_if! { } else if #[cfg(target_env = "msvc")] { #[path = "seh.rs"] mod imp; - } else if #[cfg(all(windows, target_arch = "x86_64", target_env = "gnu"))] { - #[path = "seh64_gnu.rs"] - mod imp; } else { // Rust runtime's startup objects depend on these symbols, so make them public. #[cfg(all(target_os="windows", target_arch = "x86", target_env="gnu"))] diff --git a/src/libpanic_unwind/seh64_gnu.rs b/src/libpanic_unwind/seh64_gnu.rs deleted file mode 100644 index 16b699a4437..00000000000 --- a/src/libpanic_unwind/seh64_gnu.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! Unwinding implementation of top of native Win64 SEH, -//! however the unwind handler data (aka LSDA) uses GCC-compatible encoding. - -#![allow(nonstandard_style)] -#![allow(private_no_mangle_fns)] - -use alloc::boxed::Box; - -use core::any::Any; -use core::intrinsics; -use core::ptr; -use crate::dwarf::eh::{EHContext, EHAction, find_eh_action}; -use crate::windows as c; - -// Define our exception codes: -// according to http://msdn.microsoft.com/en-us/library/het71c37(v=VS.80).aspx, -// [31:30] = 3 (error), 2 (warning), 1 (info), 0 (success) -// [29] = 1 (user-defined) -// [28] = 0 (reserved) -// we define bits: -// [24:27] = type -// [0:23] = magic -const ETYPE: c::DWORD = 0b1110_u32 << 28; -const MAGIC: c::DWORD = 0x525354; // "RST" - -const RUST_PANIC: c::DWORD = ETYPE | (1 << 24) | MAGIC; - -#[repr(C)] -struct PanicData { - data: Box, -} - -pub unsafe fn panic(data: Box) -> u32 { - let panic_ctx = Box::new(PanicData { data }); - let params = [Box::into_raw(panic_ctx) as c::ULONG_PTR]; - c::RaiseException(RUST_PANIC, - c::EXCEPTION_NONCONTINUABLE, - params.len() as c::DWORD, - ¶ms as *const c::ULONG_PTR); - u32::max_value() -} - -pub fn payload() -> *mut u8 { - ptr::null_mut() -} - -pub unsafe fn cleanup(ptr: *mut u8) -> Box { - let panic_ctx = Box::from_raw(ptr as *mut PanicData); - panic_ctx.data -} - -// SEH doesn't support resuming unwinds after calling a landing pad like -// libunwind does. For this reason, MSVC compiler outlines landing pads into -// separate functions that can be called directly from the personality function -// but are nevertheless able to find and modify stack frame of the "parent" -// function. -// -// Since this cannot be done with libdwarf-style landing pads, -// rust_eh_personality instead catches RUST_PANICs, runs the landing pad, then -// reraises the exception. -// -// Note that it makes certain assumptions about the exception: -// -// 1. That RUST_PANIC is non-continuable, so no lower stack frame may choose to -// resume execution. -// 2. That the first parameter of the exception is a pointer to an extra data -// area (PanicData). -// Since these assumptions do not generally hold true for foreign exceptions -// (system faults, C++ exceptions, etc), we make no attempt to invoke our -// landing pads (and, thus, destructors!) for anything other than RUST_PANICs. -// This is considered acceptable, because the behavior of throwing exceptions -// through a C ABI boundary is undefined. - -#[lang = "eh_personality"] -#[cfg(not(test))] -unsafe extern "C" fn rust_eh_personality(exceptionRecord: *mut c::EXCEPTION_RECORD, - establisherFrame: c::LPVOID, - contextRecord: *mut c::CONTEXT, - dispatcherContext: *mut c::DISPATCHER_CONTEXT) - -> c::EXCEPTION_DISPOSITION { - let er = &*exceptionRecord; - let dc = &*dispatcherContext; - - if er.ExceptionFlags & c::EXCEPTION_UNWIND == 0 { - // we are in the dispatch phase - if er.ExceptionCode == RUST_PANIC { - if let Some(lpad) = find_landing_pad(dc) { - c::RtlUnwindEx(establisherFrame, - lpad as c::LPVOID, - exceptionRecord, - er.ExceptionInformation[0] as c::LPVOID, // pointer to PanicData - contextRecord, - dc.HistoryTable); - } - } - } - c::ExceptionContinueSearch -} - -#[lang = "eh_unwind_resume"] -#[unwind(allowed)] -unsafe extern "C" fn rust_eh_unwind_resume(panic_ctx: c::LPVOID) -> ! { - let params = [panic_ctx as c::ULONG_PTR]; - c::RaiseException(RUST_PANIC, - c::EXCEPTION_NONCONTINUABLE, - params.len() as c::DWORD, - ¶ms as *const c::ULONG_PTR); - intrinsics::abort(); -} - -unsafe fn find_landing_pad(dc: &c::DISPATCHER_CONTEXT) -> Option { - let eh_ctx = EHContext { - // The return address points 1 byte past the call instruction, - // which could be in the next IP range in LSDA range table. - ip: dc.ControlPc as usize - 1, - func_start: dc.ImageBase as usize + (*dc.FunctionEntry).BeginAddress as usize, - get_text_start: &|| dc.ImageBase as usize, - get_data_start: &|| unimplemented!(), - }; - match find_eh_action(dc.HandlerData, &eh_ctx) { - Err(_) | - Ok(EHAction::None) => None, - Ok(EHAction::Cleanup(lpad)) | - Ok(EHAction::Catch(lpad)) => Some(lpad), - Ok(EHAction::Terminate) => intrinsics::abort(), - } -} diff --git a/src/libunwind/libunwind.rs b/src/libunwind/libunwind.rs index 7c9eaa51fd9..0b39503c0d0 100644 --- a/src/libunwind/libunwind.rs +++ b/src/libunwind/libunwind.rs @@ -244,3 +244,30 @@ if #[cfg(not(all(target_os = "ios", target_arch = "arm")))] { } } } // cfg_if! + +cfg_if::cfg_if! { +if #[cfg(all(windows, target_arch = "x86_64", target_env = "gnu"))] { + // We declare these as opaque types. This is fine since you just need to + // pass them to _GCC_specific_handler and forget about them. + pub enum EXCEPTION_RECORD {} + pub type LPVOID = *mut c_void; + pub enum CONTEXT {} + pub enum DISPATCHER_CONTEXT {} + pub type EXCEPTION_DISPOSITION = c_int; + type PersonalityFn = unsafe extern "C" fn(version: c_int, + actions: _Unwind_Action, + exception_class: _Unwind_Exception_Class, + exception_object: *mut _Unwind_Exception, + context: *mut _Unwind_Context) + -> _Unwind_Reason_Code; + + extern "C" { + pub fn _GCC_specific_handler(exceptionRecord: *mut EXCEPTION_RECORD, + establisherFrame: LPVOID, + contextRecord: *mut CONTEXT, + dispatcherContext: *mut DISPATCHER_CONTEXT, + personality: PersonalityFn) + -> EXCEPTION_DISPOSITION; + } +} +} // cfg_if! diff --git a/src/test/run-make-fulldeps/foreign-exceptions/Makefile b/src/test/run-make-fulldeps/foreign-exceptions/Makefile new file mode 100644 index 00000000000..fd15db9f151 --- /dev/null +++ b/src/test/run-make-fulldeps/foreign-exceptions/Makefile @@ -0,0 +1,10 @@ +-include ../tools.mk + +all: foo + $(call RUN,foo) + +foo: foo.rs $(call NATIVE_STATICLIB,foo) + $(RUSTC) $< -lfoo $(EXTRACXXFLAGS) + +$(TMPDIR)/libfoo.o: foo.cpp + $(call COMPILE_OBJ_CXX,$@,$<) diff --git a/src/test/run-make-fulldeps/foreign-exceptions/foo.cpp b/src/test/run-make-fulldeps/foreign-exceptions/foo.cpp new file mode 100644 index 00000000000..9a6fab49f49 --- /dev/null +++ b/src/test/run-make-fulldeps/foreign-exceptions/foo.cpp @@ -0,0 +1,59 @@ +#include +#include +#include + +void println(const char* s) { + puts(s); + fflush(stdout); +} + +struct exception {}; + +struct drop_check { + bool* ok; + ~drop_check() { + println("~drop_check"); + + if (ok) + *ok = true; + } +}; + +extern "C" { + void rust_catch_callback(void (*cb)(), bool* rust_ok); + + static void callback() { + println("throwing C++ exception"); + throw exception(); + } + + void throw_cxx_exception() { + bool rust_ok = false; + try { + rust_catch_callback(callback, &rust_ok); + assert(false && "unreachable"); + } catch (exception e) { + println("caught C++ exception"); + assert(rust_ok); + return; + } + assert(false && "did not catch thrown C++ exception"); + } + + void cxx_catch_callback(void (*cb)(), bool* cxx_ok) { + drop_check x; + x.ok = NULL; + try { + cb(); + } catch (exception e) { + assert(false && "shouldn't be able to catch a rust panic"); + } catch (...) { + println("caught foreign exception in catch (...)"); + // Foreign exceptions are caught by catch (...). We only set the ok + // flag if we successfully caught the panic. The destructor of + // drop_check will then set the flag to true if it is executed. + x.ok = cxx_ok; + throw; + } + } +} diff --git a/src/test/run-make-fulldeps/foreign-exceptions/foo.rs b/src/test/run-make-fulldeps/foreign-exceptions/foo.rs new file mode 100644 index 00000000000..83adf000d9e --- /dev/null +++ b/src/test/run-make-fulldeps/foreign-exceptions/foo.rs @@ -0,0 +1,63 @@ +// Tests that C++ exceptions can unwind through Rust code, run destructors and +// are ignored by catch_unwind. Also tests that Rust panics can unwind through +// C++ code. + +#![feature(unwind_attributes)] + +use std::panic::{catch_unwind, AssertUnwindSafe}; + +struct DropCheck<'a>(&'a mut bool); +impl<'a> Drop for DropCheck<'a> { + fn drop(&mut self) { + println!("DropCheck::drop"); + *self.0 = true; + } +} + +extern "C" { + fn throw_cxx_exception(); + + #[unwind(allowed)] + fn cxx_catch_callback(cb: extern "C" fn(), ok: *mut bool); +} + +#[no_mangle] +#[unwind(allowed)] +extern "C" fn rust_catch_callback(cb: extern "C" fn(), rust_ok: &mut bool) { + let _caught_unwind = catch_unwind(AssertUnwindSafe(|| { + let _drop = DropCheck(rust_ok); + cb(); + unreachable!("should have unwound instead of returned"); + })); + unreachable!("catch_unwind should not have caught foreign exception"); +} + +fn throw_rust_panic() { + #[unwind(allowed)] + extern "C" fn callback() { + println!("throwing rust panic"); + panic!(1234i32); + } + + let mut dropped = false; + let mut cxx_ok = false; + let caught_unwind = catch_unwind(AssertUnwindSafe(|| { + let _drop = DropCheck(&mut dropped); + unsafe { + cxx_catch_callback(callback, &mut cxx_ok); + } + unreachable!("should have unwound instead of returned"); + })); + println!("caught rust panic"); + assert!(dropped); + assert!(caught_unwind.is_err()); + let panic_obj = caught_unwind.unwrap_err(); + let panic_int = *panic_obj.downcast_ref::().unwrap(); + assert_eq!(panic_int, 1234); + assert!(cxx_ok); +} + +fn main() { + unsafe { throw_cxx_exception() }; + throw_rust_panic(); +}