Never allow unwinding from Drop impls
See original GitHub issueSummary
Code using catch_unwind
is not typically prepared to handle an object that panics in its Drop
impl. Even the standard library has had various bugs in this regard, and if the standard library doesn’t consistently get it right, we can hardly expect others to do so.
This came up in @rust-lang/libs discussion.
We discussed various ways to handle this, including potential tweaks to panic_any
or catch_unwind
to add special handling of types that implement Drop
, but on balance we felt like it would be preferable to decide at the language level to generally not allow unwind from Drop
impls. (We may not be able to universally prohibit this, but we could work towards transitioning there.)
Background reading
https://github.com/rust-lang/rust/issues/86027
About this issue
This issue corresponds to a lang-team design meeting proposal. It corresponds to a possible topic of discussion that may be scheduled for deeper discussion during one of our design meetings.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:14
- Comments:118 (72 by maintainers)
I’m personally not really sold on this. (Though to be fair, I’m not someone that needs to be convinced.) Forbidding unwinding from
Drop::drop
isn’t a soundness fix; the language level soundness fix is injecting a landing pad for nounwind contexts (e.g.extern "C"
andGlobalAlloc
) and the rest is (perhaps a long tail of) library fixes for collections. Unwinding fromdrop
is generally considered a thing to avoid because it could abort even when panic=unwind, but it’s still a useful tool to have[^1].[^1]: As a small case study, I have a crate which involves doing an FFI call that could theoretically fail for resource cleanup on drop. If the library is used correctly and the FFI bindings don’t have any bugs, then the call won’t return an error. However, I still want to check the error, especially when
cfg(debug_assertions)
is true. As such, on drop, if the FFI resource cleanup call returns an error, Ilog::error!
the error, and ifcfg!(debug_assertions) && !thread::unwinding()
,panic!
. The goal is to abort the task to acatch_unwind!
landing pad because something unexpectedly went wrong, but it’s not so unrecoverable that aborting is appropriate.Please forgive the slippery slope comparison, but saying forbidding unwinds from
Drop::drop
is a minor breaking change because panics indrop
already could drop feels a step away from saying making library panics always abort because they already could either unwind or abort, because a consumer could callpanic::always_abort()
.We allow use of
panic!
/catch_unwind
for task-level aborts if the binary author setspanic=unwind
. The argument which I’m trying to convince myself isn’t just based on slippery slope FUD is that the reasoning in allowing this usage applies just as well to allowing unwinding from RAIIdrop
if the developer knows that panic=unwind and the drop won’t be called from a panicking code path.I actively disagree with this assessment of unwinding from
drop
. While I agree that intentional unwinding from drop is a somewhat niche feature (though doing fallible finalization on drop may not uncommonly be written tounwrap
instead of dropping the result), running downstream code from an RAII guard is imho not a niche feature.I really don’t want to ever have to say “this shouldn’t be an RAII
defer!
(or equivalent) because it could panic; just put the code at the scope exit point (and hope we never add an early exit).” In fact I have one bit of API which needs to run a user trait method on a user closure return or unwind for soundness, so I need to have an RAII guard, but if I want to propogate a panic if it’s not an unwind, I’d need to duplicate the cleanup code out of the RAII guard for the happy path as well.We can still get the benefits, though, by adding a new
panic=unwind-not-from-drop
profile configuration. The defaultpanic=unwind
profile would be unaffected, and binaries which are confident that they don’t unwind from drop but don’t want to bepanic=abort
can setpanic=unwind-not-from-drop
for the size benefit.There’s already a position for code which is only sound with
panic=abort
(e.g.take_mut
without an abort bomb) because it isn’t unwind safe. It doesn’t seem too farfetched to say that code which fails to account properly for drop unwinding is only sound whenpanic=unwind-not-from-drop
(orpanic=abort
).The one thing I’d personally want with that feature is a new function to compliment
thread::panicking()
which just tells you if a panic from drop would currently abort.Initial perf results: up to 10% reduction in compilation time (perf) and a 5MB (3%) reduction in the size of
librustc_driver.so
.