Skip to content

Commit

Permalink
Allow foreign exceptions to unwind through Rust code
Browse files Browse the repository at this point in the history
  • Loading branch information
Amanieu committed Nov 3, 2019
1 parent 9a8bb3a commit 5f1a0af
Show file tree
Hide file tree
Showing 9 changed files with 333 additions and 256 deletions.
3 changes: 1 addition & 2 deletions src/doc/unstable-book/src/language-features/lang-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
15 changes: 11 additions & 4 deletions src/libpanic_unwind/dwarf/eh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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))
}
}
}
Expand All @@ -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)
}
}
Expand Down
277 changes: 160 additions & 117 deletions src/libpanic_unwind/gcc.rs

Large diffs are not rendered by default.

8 changes: 2 additions & 6 deletions src/libpanic_unwind/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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"))]
Expand Down
127 changes: 0 additions & 127 deletions src/libpanic_unwind/seh64_gnu.rs

This file was deleted.

27 changes: 27 additions & 0 deletions src/libunwind/libunwind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!
10 changes: 10 additions & 0 deletions src/test/run-make-fulldeps/foreign-exceptions/Makefile
Original file line number Diff line number Diff line change
@@ -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,$@,$<)
59 changes: 59 additions & 0 deletions src/test/run-make-fulldeps/foreign-exceptions/foo.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
63 changes: 63 additions & 0 deletions src/test/run-make-fulldeps/foreign-exceptions/foo.rs
Original file line number Diff line number Diff line change
@@ -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();
}

0 comments on commit 5f1a0af

Please sign in to comment.