Skip to content

Commit 5f1a0af

Browse files
committed
Allow foreign exceptions to unwind through Rust code
1 parent 9a8bb3a commit 5f1a0af

File tree

9 files changed

+333
-256
lines changed

9 files changed

+333
-256
lines changed

src/doc/unstable-book/src/language-features/lang-items.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,8 @@ the source code.
249249
- Runtime
250250
- `start`: `libstd/rt.rs`
251251
- `eh_personality`: `libpanic_unwind/emcc.rs` (EMCC)
252-
- `eh_personality`: `libpanic_unwind/seh64_gnu.rs` (SEH64 GNU)
252+
- `eh_personality`: `libpanic_unwind/gcc.rs` (GNU)
253253
- `eh_personality`: `libpanic_unwind/seh.rs` (SEH)
254-
- `eh_unwind_resume`: `libpanic_unwind/seh64_gnu.rs` (SEH64 GNU)
255254
- `eh_unwind_resume`: `libpanic_unwind/gcc.rs` (GCC)
256255
- `msvc_try_filter`: `libpanic_unwind/seh.rs` (SEH)
257256
- `panic`: `libcore/panicking.rs`

src/libpanic_unwind/dwarf/eh.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub enum EHAction {
5151

5252
pub const USING_SJLJ_EXCEPTIONS: bool = cfg!(all(target_os = "ios", target_arch = "arm"));
5353

54-
pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>)
54+
pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>, foreign_exception: bool)
5555
-> Result<EHAction, ()>
5656
{
5757
if lsda.is_null() {
@@ -96,7 +96,7 @@ pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>)
9696
return Ok(EHAction::None)
9797
} else {
9898
let lpad = lpad_base + cs_lpad;
99-
return Ok(interpret_cs_action(cs_action, lpad))
99+
return Ok(interpret_cs_action(cs_action, lpad, foreign_exception))
100100
}
101101
}
102102
}
@@ -121,16 +121,23 @@ pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>)
121121
// Can never have null landing pad for sjlj -- that would have
122122
// been indicated by a -1 call site index.
123123
let lpad = (cs_lpad + 1) as usize;
124-
return Ok(interpret_cs_action(cs_action, lpad))
124+
return Ok(interpret_cs_action(cs_action, lpad, foreign_exception))
125125
}
126126
}
127127
}
128128
}
129129

130-
fn interpret_cs_action(cs_action: u64, lpad: usize) -> EHAction {
130+
fn interpret_cs_action(cs_action: u64, lpad: usize, foreign_exception: bool) -> EHAction {
131131
if cs_action == 0 {
132+
// If cs_action is 0 then this is a cleanup (Drop::drop). We run these
133+
// for both Rust panics and foriegn exceptions.
132134
EHAction::Cleanup(lpad)
135+
} else if foreign_exception {
136+
// catch_unwind should not catch foreign exceptions, only Rust panics.
137+
// Instead just continue unwinding.
138+
EHAction::None
133139
} else {
140+
// Stop unwinding Rust panics at catch_unwind.
134141
EHAction::Catch(lpad)
135142
}
136143
}

src/libpanic_unwind/gcc.rs

+160-117
Large diffs are not rendered by default.

src/libpanic_unwind/lib.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
//! essentially gets categorized into three buckets currently:
66
//!
77
//! 1. MSVC targets use SEH in the `seh.rs` file.
8-
//! 2. The 64-bit MinGW target half-uses SEH and half-use gcc-like information
9-
//! in the `seh64_gnu.rs` module.
10-
//! 3. All other targets use libunwind/libgcc in the `gcc/mod.rs` module.
8+
//! 2. Emscripten uses C++ exceptions in the `emcc.rs` file.
9+
//! 3. All other targets use libunwind/libgcc in the `gcc.rs` file.
1110
//!
1211
//! More documentation about each implementation can be found in the respective
1312
//! module.
@@ -52,9 +51,6 @@ cfg_if::cfg_if! {
5251
} else if #[cfg(target_env = "msvc")] {
5352
#[path = "seh.rs"]
5453
mod imp;
55-
} else if #[cfg(all(windows, target_arch = "x86_64", target_env = "gnu"))] {
56-
#[path = "seh64_gnu.rs"]
57-
mod imp;
5854
} else {
5955
// Rust runtime's startup objects depend on these symbols, so make them public.
6056
#[cfg(all(target_os="windows", target_arch = "x86", target_env="gnu"))]

src/libpanic_unwind/seh64_gnu.rs

-127
This file was deleted.

src/libunwind/libunwind.rs

+27
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,30 @@ if #[cfg(not(all(target_os = "ios", target_arch = "arm")))] {
244244
}
245245
}
246246
} // cfg_if!
247+
248+
cfg_if::cfg_if! {
249+
if #[cfg(all(windows, target_arch = "x86_64", target_env = "gnu"))] {
250+
// We declare these as opaque types. This is fine since you just need to
251+
// pass them to _GCC_specific_handler and forget about them.
252+
pub enum EXCEPTION_RECORD {}
253+
pub type LPVOID = *mut c_void;
254+
pub enum CONTEXT {}
255+
pub enum DISPATCHER_CONTEXT {}
256+
pub type EXCEPTION_DISPOSITION = c_int;
257+
type PersonalityFn = unsafe extern "C" fn(version: c_int,
258+
actions: _Unwind_Action,
259+
exception_class: _Unwind_Exception_Class,
260+
exception_object: *mut _Unwind_Exception,
261+
context: *mut _Unwind_Context)
262+
-> _Unwind_Reason_Code;
263+
264+
extern "C" {
265+
pub fn _GCC_specific_handler(exceptionRecord: *mut EXCEPTION_RECORD,
266+
establisherFrame: LPVOID,
267+
contextRecord: *mut CONTEXT,
268+
dispatcherContext: *mut DISPATCHER_CONTEXT,
269+
personality: PersonalityFn)
270+
-> EXCEPTION_DISPOSITION;
271+
}
272+
}
273+
} // cfg_if!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-include ../tools.mk
2+
3+
all: foo
4+
$(call RUN,foo)
5+
6+
foo: foo.rs $(call NATIVE_STATICLIB,foo)
7+
$(RUSTC) $< -lfoo $(EXTRACXXFLAGS)
8+
9+
$(TMPDIR)/libfoo.o: foo.cpp
10+
$(call COMPILE_OBJ_CXX,$@,$<)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include <assert.h>
2+
#include <stddef.h>
3+
#include <stdio.h>
4+
5+
void println(const char* s) {
6+
puts(s);
7+
fflush(stdout);
8+
}
9+
10+
struct exception {};
11+
12+
struct drop_check {
13+
bool* ok;
14+
~drop_check() {
15+
println("~drop_check");
16+
17+
if (ok)
18+
*ok = true;
19+
}
20+
};
21+
22+
extern "C" {
23+
void rust_catch_callback(void (*cb)(), bool* rust_ok);
24+
25+
static void callback() {
26+
println("throwing C++ exception");
27+
throw exception();
28+
}
29+
30+
void throw_cxx_exception() {
31+
bool rust_ok = false;
32+
try {
33+
rust_catch_callback(callback, &rust_ok);
34+
assert(false && "unreachable");
35+
} catch (exception e) {
36+
println("caught C++ exception");
37+
assert(rust_ok);
38+
return;
39+
}
40+
assert(false && "did not catch thrown C++ exception");
41+
}
42+
43+
void cxx_catch_callback(void (*cb)(), bool* cxx_ok) {
44+
drop_check x;
45+
x.ok = NULL;
46+
try {
47+
cb();
48+
} catch (exception e) {
49+
assert(false && "shouldn't be able to catch a rust panic");
50+
} catch (...) {
51+
println("caught foreign exception in catch (...)");
52+
// Foreign exceptions are caught by catch (...). We only set the ok
53+
// flag if we successfully caught the panic. The destructor of
54+
// drop_check will then set the flag to true if it is executed.
55+
x.ok = cxx_ok;
56+
throw;
57+
}
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Tests that C++ exceptions can unwind through Rust code, run destructors and
2+
// are ignored by catch_unwind. Also tests that Rust panics can unwind through
3+
// C++ code.
4+
5+
#![feature(unwind_attributes)]
6+
7+
use std::panic::{catch_unwind, AssertUnwindSafe};
8+
9+
struct DropCheck<'a>(&'a mut bool);
10+
impl<'a> Drop for DropCheck<'a> {
11+
fn drop(&mut self) {
12+
println!("DropCheck::drop");
13+
*self.0 = true;
14+
}
15+
}
16+
17+
extern "C" {
18+
fn throw_cxx_exception();
19+
20+
#[unwind(allowed)]
21+
fn cxx_catch_callback(cb: extern "C" fn(), ok: *mut bool);
22+
}
23+
24+
#[no_mangle]
25+
#[unwind(allowed)]
26+
extern "C" fn rust_catch_callback(cb: extern "C" fn(), rust_ok: &mut bool) {
27+
let _caught_unwind = catch_unwind(AssertUnwindSafe(|| {
28+
let _drop = DropCheck(rust_ok);
29+
cb();
30+
unreachable!("should have unwound instead of returned");
31+
}));
32+
unreachable!("catch_unwind should not have caught foreign exception");
33+
}
34+
35+
fn throw_rust_panic() {
36+
#[unwind(allowed)]
37+
extern "C" fn callback() {
38+
println!("throwing rust panic");
39+
panic!(1234i32);
40+
}
41+
42+
let mut dropped = false;
43+
let mut cxx_ok = false;
44+
let caught_unwind = catch_unwind(AssertUnwindSafe(|| {
45+
let _drop = DropCheck(&mut dropped);
46+
unsafe {
47+
cxx_catch_callback(callback, &mut cxx_ok);
48+
}
49+
unreachable!("should have unwound instead of returned");
50+
}));
51+
println!("caught rust panic");
52+
assert!(dropped);
53+
assert!(caught_unwind.is_err());
54+
let panic_obj = caught_unwind.unwrap_err();
55+
let panic_int = *panic_obj.downcast_ref::<i32>().unwrap();
56+
assert_eq!(panic_int, 1234);
57+
assert!(cxx_ok);
58+
}
59+
60+
fn main() {
61+
unsafe { throw_cxx_exception() };
62+
throw_rust_panic();
63+
}

0 commit comments

Comments
 (0)