From 2b30f8668de3ae2bd3141612449ab5918bababd4 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Mon, 13 Jul 2026 21:29:12 -0400 Subject: [PATCH] Fix unwinding bug caused by inlining --- src/intrinsic/mod.rs | 10 ++++++++++ tests/run/catch_unwind.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 tests/run/catch_unwind.rs diff --git a/src/intrinsic/mod.rs b/src/intrinsic/mod.rs index 39f825e66f6..aea455babbd 100644 --- a/src/intrinsic/mod.rs +++ b/src/intrinsic/mod.rs @@ -4,6 +4,8 @@ mod simd; #[cfg(feature = "master")] use std::iter; +#[cfg(feature = "master")] +use gccjit::FnAttribute; use gccjit::{ComparisonOp, Function, FunctionType, RValue, ToRValue, Type, UnaryOp}; use rustc_abi::{Align, BackendRepr, HasDataLayout, WrappingRange}; use rustc_codegen_ssa::base::wants_msvc_seh; @@ -1489,6 +1491,14 @@ fn get_rust_try_fn<'a, 'gcc, 'tcx>( rustc_hir::Safety::Unsafe, )); let rust_try = gen_fn(cx, "__rust_try", rust_fn_sig, codegen); + // NOTE: The `__rust_try` shim must not be inlined into its caller. As explained on + // `codegen_gnu_try` above, the shim exists precisely so that its try/catch has the correct + // (single) personality function. When GCC inlines the shim into the caller under + // optimization (-O2/-O3), the try/catch landing pad is degraded into a + // cleanup-and-`_Unwind_Resume`: the exception is re-raised instead of being caught, so + // `catch_unwind` silently stops catching in release mode. Keeping the shim out-of-line + // preserves the catch semantics. + rust_try.1.add_attribute(FnAttribute::NoInline); cx.rust_try_fn.set(Some(rust_try)); rust_try } diff --git a/tests/run/catch_unwind.rs b/tests/run/catch_unwind.rs new file mode 100644 index 00000000000..1d6ccca08d5 --- /dev/null +++ b/tests/run/catch_unwind.rs @@ -0,0 +1,36 @@ +// Compiler: +// +// Run-time: +// status: 0 +// stdout: Caught + +// Regression test for a release-mode (-O3) unwinding bug: `catch_unwind` must +// still catch a panic when the panicking closure is inlined across the catch +// boundary. The `#[inline]` `call_once` below forces that inlining. +// +// Previously, GCC inlined the out-of-line `__rust_try` shim into its caller +// under optimization, which degraded the try/catch landing pad into a plain +// `_Unwind_Resume`. The panic was then re-raised instead of caught, escaped +// `main`, and the process exited with code 101 (in release only; debug and the +// LLVM backend were unaffected). See `get_rust_try_fn` in `src/intrinsic/mod.rs`, +// which now marks `__rust_try` as `NoInline`. + +#![feature(fn_traits, unboxed_closures)] + +struct Wrapper(A); + +impl R> FnOnce<()> for Wrapper { + type Output = R; + + #[inline] + extern "rust-call" fn call_once(self, _args: ()) -> R { + (self.0)() + } +} + +fn main() { + std::panic::set_hook(Box::new(|_| {})); + let result = std::panic::catch_unwind(Wrapper(|| panic!())); + assert!(result.is_err()); + println!("Caught"); +}