Skip to content

Commit 0581d4a

Browse files
committed
Improve debugging ergonomics on mismatched catch/catch_unwind
1 parent 1665ecf commit 0581d4a

File tree

6 files changed

+66
-9
lines changed

6 files changed

+66
-9
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ jobs:
115115
run: LITHIUM_BACKEND=panic cross test --target $target
116116
- name: Test with Wasm backend (debug)
117117
run: RUSTFLAGS="-Z emscripten_wasm_eh" RUSTDOCFLAGS="-Z emscripten_wasm_eh" LITHIUM_BACKEND=wasm cross test --target $target -Z build-std
118+
- name: Test with Itanium backend (debug)
119+
run: RUSTFLAGS="-Z emscripten_wasm_eh" RUSTDOCFLAGS="-Z emscripten_wasm_eh" LITHIUM_BACKEND=itanium cross test --target $target -Z build-std
118120
- name: Test with Emscripten backend (debug)
119121
run: LITHIUM_BACKEND=emscripten cross test --target $target
120122
- name: Test with std thread locals (debug)
@@ -124,6 +126,8 @@ jobs:
124126
# XXX: https://github.com/rust-lang/rust/issues/132416
125127
# - name: Test with Wasm backend (release)
126128
# run: RUSTFLAGS="-Z emscripten_wasm_eh" RUSTDOCFLAGS="-Z emscripten_wasm_eh" LITHIUM_BACKEND=wasm cross test --target $target --release -Z build-std
129+
# - name: Test with Itanium backend (release)
130+
# run: RUSTFLAGS="-Z emscripten_wasm_eh" RUSTDOCFLAGS="-Z emscripten_wasm_eh" LITHIUM_BACKEND=itanium cross test --target $target --release -Z build-std
127131
- name: Test with Emscripten backend (release)
128132
run: LITHIUM_BACKEND=emscripten cross test --target $target --release
129133
- name: Test with std thread locals (release)
@@ -152,13 +156,17 @@ jobs:
152156
run: LITHIUM_BACKEND=panic ci/cargo-wasi test --target $target
153157
- name: Test with Wasm backend (debug)
154158
run: LITHIUM_BACKEND=wasm ci/cargo-wasi test --target $target
159+
- name: Test with Itanium backend (debug)
160+
run: LITHIUM_BACKEND=itanium ci/cargo-wasi test --target $target
155161
- name: Test with std thread locals (debug)
156162
run: LITHIUM_THREAD_LOCAL=std ci/cargo-wasi test --target $target
157163
- name: Test with panic backend (release)
158164
run: LITHIUM_BACKEND=panic ci/cargo-wasi test --target $target --release
159165
# XXX: Upstream bug at https://github.com/rust-lang/rust/issues/132416
160166
# - name: Test with Wasm backend (release)
161167
# run: LITHIUM_BACKEND=wasm ci/cargo-wasi test --target $target --release
168+
# - name: Test with Itanium backend (release)
169+
# run: LITHIUM_BACKEND=itanium ci/cargo-wasi test --target $target --release
162170
# - name: Test with std thread locals (release)
163171
# run: LITHIUM_THREAD_LOCAL=std ci/cargo-wasi test --target $target --release
164172

build.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,16 @@ fn main() {
4848
if is_nightly && cfg("target_os") == "emscripten" && !has_cfg("emscripten_wasm_eh") {
4949
"emscripten"
5050
} else if is_nightly && cfg("target_arch") == "wasm32" {
51-
"wasm"
51+
// Catching a foreign Itanium exception from within Rust is (currently) guaranteed to
52+
// abort, but the optimizations we use for Wasm cause std to read uninitialized memory
53+
// in this case. Make missing `catch` or misplaced `catch_unwind` calls easier to debug
54+
// by switching to the more robust, but slower mechanism in debug mode. We can't use
55+
// `has_cfg("debug_assertions")` due to https://github.com/rust-lang/cargo/issues/7634.
56+
if std::env::var("PROFILE").unwrap_or_default() == "debug" {
57+
"itanium"
58+
} else {
59+
"wasm"
60+
}
5261
} else if is_nightly
5362
&& (has_cfg("unix") || (has_cfg("windows") && cfg("target_env") == "gnu"))
5463
{

src/backend/itanium.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ unsafe impl ThrowByPointer for ActiveBackend {
4242
unsafe fn throw(ex: *mut Header) -> ! {
4343
// SAFETY: We provide a valid exception header.
4444
unsafe {
45-
_Unwind_RaiseException(ex.cast());
45+
raise(ex.cast());
4646
}
4747
}
4848

@@ -81,7 +81,7 @@ unsafe impl ThrowByPointer for ActiveBackend {
8181
// If project-ffi-unwind changes the rustc behavior, we might have to update this
8282
// code.
8383
unsafe {
84-
_Unwind_RaiseException(ex);
84+
raise(ex);
8585
}
8686
}
8787

@@ -140,7 +140,8 @@ const fn get_unwinder_private_word_count() -> usize {
140140
target_arch = "sparc64",
141141
target_arch = "riscv64",
142142
target_arch = "riscv32",
143-
target_arch = "loongarch64"
143+
target_arch = "loongarch64",
144+
target_arch = "wasm32"
144145
)) {
145146
2
146147
} else {
@@ -159,6 +160,29 @@ unsafe extern "C" fn cleanup(_code: i32, _ex: *mut Header) {
159160
);
160161
}
161162

163+
#[cfg(not(target_arch = "wasm32"))]
162164
unsafe extern "C-unwind" {
163165
fn _Unwind_RaiseException(ex: *mut u8) -> !;
164166
}
167+
168+
/// Raise an Itanium EH ABI-compatible exception.
169+
///
170+
/// # Safety
171+
///
172+
/// `ex` must point at a valid instance of `_Unwind_Exception`.
173+
#[inline]
174+
unsafe fn raise(ex: *mut u8) -> ! {
175+
#[cfg(not(target_arch = "wasm32"))]
176+
// SAFETY: Passthrough.
177+
unsafe {
178+
_Unwind_RaiseException(ex);
179+
}
180+
181+
// Although Wasm has its own backend, it has worse debug experience than Itanium can offer, so
182+
// we teach this backend how to handle Wasm as well.
183+
#[cfg(target_arch = "wasm32")]
184+
// SAFETY: Passthrough.
185+
unsafe {
186+
core::arch::wasm32::throw::<0>(ex);
187+
}
188+
}

src/backend/panic.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::ThrowByPointer;
2+
use crate::abort;
23
use alloc::boxed::Box;
34
use core::mem::ManuallyDrop;
45
use core::panic::AssertUnwindSafe;
@@ -94,3 +95,11 @@ unsafe impl ThrowByPointer for ActiveBackend {
9495

9596
#[repr(transparent)]
9697
pub(crate) struct LithiumMarker;
98+
99+
impl Drop for LithiumMarker {
100+
fn drop(&mut self) {
101+
abort(
102+
"A Lithium exception was caught by a non-Lithium catch mechanism. This is undefined behavior. The process will now terminate.\n",
103+
);
104+
}
105+
}

src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,10 @@
189189
feature(core_intrinsics, rustc_attrs)
190190
)]
191191
#![cfg_attr(backend = "seh", feature(fn_ptr_trait, std_internals))]
192-
#![cfg_attr(backend = "wasm", feature(wasm_exception_handling_intrinsics))]
192+
#![cfg_attr(
193+
any(backend = "wasm", all(backend = "itanium", target_arch = "wasm32")),
194+
feature(wasm_exception_handling_intrinsics)
195+
)]
193196
#![deny(unsafe_op_in_unsafe_fn)]
194197
#![warn(
195198
clippy::cargo,
@@ -289,7 +292,6 @@ pub use api::{InFlightException, catch, intercept, throw};
289292
/// Abort the process with a message.
290293
///
291294
/// If `std` is available, this also outputs a message to stderr before aborting.
292-
#[allow(dead_code, reason = "not used by all backends")]
293295
#[cold]
294296
#[inline(never)]
295297
fn abort(message: &str) -> ! {

src/stacked_exceptions.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,14 @@ impl<E> RethrowHandle for PointerRethrowHandle<E> {
8585
type Header = <ActiveBackend as ThrowByPointer>::ExceptionHeader;
8686

8787
/// An exception object, to be used by the backend.
88+
///
89+
/// Neither the backend header nor the cause are dropped automatically when `Exception` is dropped.
90+
/// Use [`Exception::cause`] to extract the cause and drop data owned by the backend header in
91+
/// [`ActiveBackend::intercept`]. This allows the panic backend to abort in `Drop` when it's
92+
/// accidentally caught by user code.
8893
#[repr(C)] // ensure `header` is at the same offset regardless of `E`
8994
pub struct Exception<E> {
90-
header: Header,
95+
header: ManuallyDrop<Header>,
9196
cause: ManuallyDrop<Unaligned<E>>,
9297
}
9398

@@ -98,7 +103,7 @@ impl<E> Exception<E> {
98103
/// Create a new exception to be thrown.
99104
fn new(cause: E) -> Self {
100105
Self {
101-
header: ActiveBackend::new_header(),
106+
header: ManuallyDrop::new(ActiveBackend::new_header()),
102107
cause: ManuallyDrop::new(Unaligned(cause)),
103108
}
104109
}
@@ -110,7 +115,7 @@ impl<E> Exception<E> {
110115
/// `ex` must be a unique pointer at an exception object.
111116
pub const unsafe fn header(ex: *mut Self) -> *mut Header {
112117
// SAFETY: Required transitively.
113-
unsafe { &raw mut (*ex).header }
118+
unsafe { &raw mut (*ex).header }.cast()
114119
}
115120

116121
/// Restore pointer from pointer to header.

0 commit comments

Comments
 (0)