Skip to content

Commit b520af6

Browse files
committed
Auto merge of #65646 - Amanieu:foreign-exceptions, r=nikomatsakis
Allow foreign exceptions to unwind through Rust code and Rust panics to unwind through FFI This PR fixes interactions between Rust panics and foreign (mainly C++) exceptions. C++ exceptions (and other FFI exceptions) can now safely unwind through Rust code: - The FFI function causing the unwind must be marked with `#[unwind(allowed)]`. If this is not the case then LLVM may optimize landing pads away with the assumption that they are unreachable. - Drop code will be executed as the exception unwinds through the stack, as with a Rust panic. - `catch_unwind` will *not* catch the exception, instead the exception will silently continue unwinding past it. Rust panics can now safely unwind through C++ code: - C++ destructors will be called as the stack unwinds. - The Rust panic can only be caught with `catch (...)`, after which it can be either rethrown or discarded. - C++ cannot name the type of the Rust exception object used for unwinding, which means that it can't be caught explicitly or have its contents inspected. Tests have been added to ensure all of the above works correctly. Some notes about non-C++ exceptions: - `pthread_cancel` and `pthread_exit` use unwinding on glibc. This has the same behavior as a C++ exception: destructors are run but it cannot be caught by `catch_unwind`. - `longjmp` on Windows is implemented using unwinding. Destructors are run on MSVC, but not on MinGW. In both cases the unwind cannot be caught by `catch_unwind`. - As with C++ exceptions, you need to mark the relevant FFI functions with `#[unwind(allowed)]`, otherwise LLVM will optimize out the destructors since they seem unreachable. I haven't updated any of the documentation, so officially unwinding through FFI is still UB. However this is a step towards making it well-defined. Fixes #65441 cc @gnzlbg r? @alexcrichton
2 parents b43a682 + f223e0d commit b520af6

File tree

17 files changed

+480
-428
lines changed

17 files changed

+480
-428
lines changed

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,11 @@ 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)
256-
- `msvc_try_filter`: `libpanic_unwind/seh.rs` (SEH)
255+
- `eh_catch_typeinfo`: `libpanic_unwind/seh.rs` (SEH)
256+
- `eh_catch_typeinfo`: `libpanic_unwind/emcc.rs` (EMCC)
257257
- `panic`: `libcore/panicking.rs`
258258
- `panic_bounds_check`: `libcore/panicking.rs`
259259
- `panic_impl`: `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/emcc.rs

+42-10
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,48 @@ use alloc::boxed::Box;
1515
use libc::{self, c_int};
1616
use unwind as uw;
1717

18+
// This matches the layout of std::type_info in C++
19+
#[repr(C)]
20+
struct TypeInfo {
21+
vtable: *const usize,
22+
name: *const u8,
23+
}
24+
unsafe impl Sync for TypeInfo {}
25+
26+
extern "C" {
27+
// The leading `\x01` byte here is actually a magical signal to LLVM to
28+
// *not* apply any other mangling like prefixing with a `_` character.
29+
//
30+
// This symbol is the vtable used by C++'s `std::type_info`. Objects of type
31+
// `std::type_info`, type descriptors, have a pointer to this table. Type
32+
// descriptors are referenced by the C++ EH structures defined above and
33+
// that we construct below.
34+
//
35+
// Note that the real size is larger than 3 usize, but we only need our
36+
// vtable to point to the third element.
37+
#[link_name = "\x01_ZTVN10__cxxabiv117__class_type_infoE"]
38+
static CLASS_TYPE_INFO_VTABLE: [usize; 3];
39+
}
40+
41+
// std::type_info for a rust_panic class
42+
#[lang = "eh_catch_typeinfo"]
43+
static EXCEPTION_TYPE_INFO: TypeInfo = TypeInfo {
44+
// Normally we would use .as_ptr().add(2) but this doesn't work in a const context.
45+
vtable: unsafe { &CLASS_TYPE_INFO_VTABLE[2] },
46+
// This intentionally doesn't use the normal name mangling scheme because
47+
// we don't want C++ to be able to produce or catch Rust panics.
48+
name: b"rust_panic\0".as_ptr(),
49+
};
50+
1851
pub fn payload() -> *mut u8 {
1952
ptr::null_mut()
2053
}
2154

2255
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
2356
assert!(!ptr.is_null());
24-
let ex = ptr::read(ptr as *mut _);
25-
__cxa_free_exception(ptr as *mut _);
57+
let adjusted_ptr = __cxa_begin_catch(ptr as *mut libc::c_void);
58+
let ex = ptr::read(adjusted_ptr as *mut _);
59+
__cxa_end_catch();
2660
ex
2761
}
2862

@@ -32,11 +66,8 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
3266
if exception == ptr::null_mut() {
3367
return uw::_URC_FATAL_PHASE1_ERROR as u32;
3468
}
35-
let exception = exception as *mut Box<dyn Any + Send>;
36-
ptr::write(exception, data);
37-
__cxa_throw(exception as *mut _, ptr::null_mut(), ptr::null_mut());
38-
39-
unreachable!()
69+
ptr::write(exception as *mut _, data);
70+
__cxa_throw(exception as *mut _, &EXCEPTION_TYPE_INFO, ptr::null_mut());
4071
}
4172

4273
#[lang = "eh_personality"]
@@ -52,10 +83,11 @@ unsafe extern "C" fn rust_eh_personality(version: c_int,
5283

5384
extern "C" {
5485
fn __cxa_allocate_exception(thrown_size: libc::size_t) -> *mut libc::c_void;
55-
fn __cxa_free_exception(thrown_exception: *mut libc::c_void);
86+
fn __cxa_begin_catch(thrown_exception: *mut libc::c_void) -> *mut libc::c_void;
87+
fn __cxa_end_catch();
5688
fn __cxa_throw(thrown_exception: *mut libc::c_void,
57-
tinfo: *mut libc::c_void,
58-
dest: *mut libc::c_void);
89+
tinfo: *const TypeInfo,
90+
dest: *mut libc::c_void) -> !;
5991
fn __gxx_personality_v0(version: c_int,
6092
actions: uw::_Unwind_Action,
6193
exception_class: uw::_Unwind_Exception_Class,

0 commit comments

Comments
 (0)