Allow foreign exceptions to unwind through Rust code

This commit is contained in:
Amanieu d'Antras 2019-10-21 02:46:04 +01:00
parent 9a8bb3a26e
commit 5f1a0afd22
9 changed files with 338 additions and 261 deletions

View File

@ -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`

View File

@ -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<EHAction, ()>
{
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)
}
}

View File

@ -133,133 +133,176 @@ fn rust_exception_class() -> uw::_Unwind_Exception_Class {
// 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<EHAction, ()>
{
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) -> ! {

View File

@ -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 @@
} 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"))]

View File

@ -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<dyn Any + Send>,
}
pub unsafe fn panic(data: Box<dyn Any + Send>) -> 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,
&params as *const c::ULONG_PTR);
u32::max_value()
}
pub fn payload() -> *mut u8 {
ptr::null_mut()
}
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
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,
&params as *const c::ULONG_PTR);
intrinsics::abort();
}
unsafe fn find_landing_pad(dc: &c::DISPATCHER_CONTEXT) -> Option<usize> {
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(),
}
}

View File

@ -244,3 +244,30 @@ pub unsafe fn _Unwind_RaiseException(exc: *mut _Unwind_Exception) -> _Unwind_Rea
}
}
} // 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!

View File

@ -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,$@,$<)

View File

@ -0,0 +1,59 @@
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
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;
}
}
}

View File

@ -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::<i32>().unwrap();
assert_eq!(panic_int, 1234);
assert!(cxx_ok);
}
fn main() {
unsafe { throw_cxx_exception() };
throw_rust_panic();
}