From 689d39d88b8840b536289afba15e5d767f849e9a Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 11 Aug 2026 17:20:08 +0100 Subject: [PATCH] Handle multiple action records in EH personality function --- library/std/src/sys/personality/dwarf/eh.rs | 24 ++++++++++++++++- tests/ui/panics/lsda-multiple-action.rs | 30 +++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/ui/panics/lsda-multiple-action.rs diff --git a/library/std/src/sys/personality/dwarf/eh.rs b/library/std/src/sys/personality/dwarf/eh.rs index ef5112ad74f13..96f395d5d950e 100644 --- a/library/std/src/sys/personality/dwarf/eh.rs +++ b/library/std/src/sys/personality/dwarf/eh.rs @@ -48,9 +48,20 @@ pub struct EHContext<'a> { type LPad = *const u8; pub enum EHAction { None, + /// Destructors should be executed when stack unwinds. Cleanup(LPad), + /// Stack unwind should be stopped as the exception is going to be caught by `catch_unwind`. Catch(LPad), + /// Stack unwind should be stopped for termination (`UnwindAction::Terminate`). + /// + /// Note that due to inlining the landing pad can execute destructors before terminating. So + /// this is different from `Terminate`. + /// + /// Handling of this is mostly identical to `Catch`; except that Rust frames that have no + /// destructors but only `UnwindAction::Terminate` is considered as plain-old-frame (POF) and + /// forced unwind is allowed to unwind past it; so this is treated as `None` during forced unwind. Filter(LPad), + /// Process should be terminated as the call site does not permit unwinding. Terminate, } @@ -160,7 +171,18 @@ unsafe fn interpret_cs_action( let action_record = unsafe { action_table.offset(cs_action_entry as isize - 1) }; let mut action_reader = DwarfReader::new(action_record); let ttype_index = unsafe { action_reader.read_sleb128() }; - if ttype_index == 0 { + let next_action = unsafe { action_reader.read_sleb128() }; + if next_action != 0 { + // We observed multiple actions. As Rust does not have exception specification, this + // indicates that we have at least 2 of "cleanup", "catch" and "filter", so we should + // catch all exceptions. + // + // Note that even for the case of "cleanup" + "filter", decoding them as "catch" is + // fine: "filter" behaves identically to "catch" except for forced unwind; in case of + // forced unwind, hitting a "cleanup" landing pad is UB as it indicates that we're + // unwinding past a non-POF Rust frame. + EHAction::Catch(lpad) + } else if ttype_index == 0 { EHAction::Cleanup(lpad) } else if ttype_index > 0 { // Stop unwinding Rust panics at catch_unwind. diff --git a/tests/ui/panics/lsda-multiple-action.rs b/tests/ui/panics/lsda-multiple-action.rs new file mode 100644 index 0000000000000..8598b0cfe23a8 --- /dev/null +++ b/tests/ui/panics/lsda-multiple-action.rs @@ -0,0 +1,30 @@ +//@ run-pass +//@ needs-unwind +//@ needs-threads +//@ ignore-backends: gcc +//@ compile-flags: -Copt-level=3 + +struct Guard; + +impl Drop for Guard { + fn drop(&mut self) { + core::hint::black_box(()); + } +} + +#[inline(never)] +fn unwind() { + if core::hint::black_box(true) { + std::panic::resume_unwind(Box::new(())); + } +} + +fn main() { + // The `catch_unwind` will generate `landingpad catch` and the destructor will generate + // `landingpad cleanup`; after LLVM inlining it will become `landingpad cleanup catch`, and this + // is translated to action record chains in LSDA. + let _ = std::panic::catch_unwind(|| { + let _guard = Guard; + unwind(); + }); +}