Skip to content

Commit 51e514c

Browse files
committed
Auto merge of #88759 - Amanieu:panic_in_drop, r=nagisa,eddyb
Add -Z panic-in-drop={unwind,abort} command-line option This PR changes `Drop` to abort if an unwinding panic attempts to escape it, making the process abort instead. This has several benefits: - The current behavior when unwinding out of `Drop` is very unintuitive and easy to miss: unwinding continues, but the remaining drops in scope are simply leaked. - A lot of unsafe code doesn't expect drops to unwind, which can lead to unsoundness: - servo/rust-smallvec#14 - bluss/arrayvec#3 - There is a code size and compilation time cost to this: LLVM needs to generate extra landing pads out of all calls in a drop implementation. This can compound when functions are inlined since unwinding will then continue on to process drops in the callee, which can itself unwind, etc. - Initial measurements show a 3% size reduction and up to 10% compilation time reduction on some crates (`syn`). One thing to note about `-Z panic-in-drop=abort` is that *all* crates must be built with this option for it to be sound since it makes the compiler assume that dropping `Box<dyn Any>` will never unwind. cc rust-lang/lang-team#97
2 parents d2dfb0e + 5862a00 commit 51e514c

File tree

11 files changed

+119
-17
lines changed

11 files changed

+119
-17
lines changed

compiler/rustc_codegen_llvm/src/abi.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,12 @@ impl<'tcx> FnAbiLlvmExt<'tcx> for FnAbi<'tcx, Ty<'tcx>> {
511511
}
512512

513513
fn apply_attrs_callsite(&self, bx: &mut Builder<'a, 'll, 'tcx>, callsite: &'ll Value) {
514-
// FIXME(wesleywiser, eddyb): We should apply `nounwind` and `noreturn` as appropriate to this callsite.
514+
if self.ret.layout.abi.is_uninhabited() {
515+
llvm::Attribute::NoReturn.apply_callsite(llvm::AttributePlace::Function, callsite);
516+
}
517+
if !self.can_unwind {
518+
llvm::Attribute::NoUnwind.apply_callsite(llvm::AttributePlace::Function, callsite);
519+
}
515520

516521
let mut i = 0;
517522
let mut apply = |cx: &CodegenCx<'_, '_>, attrs: &ArgAttributes| {

compiler/rustc_interface/src/tests.rs

+1
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ fn test_debugging_options_tracking_hash() {
743743
tracked!(no_profiler_runtime, true);
744744
tracked!(osx_rpath_install_name, true);
745745
tracked!(panic_abort_tests, true);
746+
tracked!(panic_in_drop, PanicStrategy::Abort);
746747
tracked!(partially_uninit_const_threshold, Some(123));
747748
tracked!(plt, Some(true));
748749
tracked!(polonius, true);

compiler/rustc_metadata/src/dependency_format.rs

+23-9
Original file line numberDiff line numberDiff line change
@@ -400,21 +400,35 @@ fn verify_ok(tcx: TyCtxt<'_>, list: &[Linkage]) {
400400
continue;
401401
}
402402
let cnum = CrateNum::new(i + 1);
403-
let found_strategy = tcx.panic_strategy(cnum);
404-
let is_compiler_builtins = tcx.is_compiler_builtins(cnum);
405-
if is_compiler_builtins || desired_strategy == found_strategy {
403+
if tcx.is_compiler_builtins(cnum) {
406404
continue;
407405
}
408406

409-
sess.err(&format!(
410-
"the crate `{}` is compiled with the \
407+
let found_strategy = tcx.panic_strategy(cnum);
408+
if desired_strategy != found_strategy {
409+
sess.err(&format!(
410+
"the crate `{}` is compiled with the \
411411
panic strategy `{}` which is \
412412
incompatible with this crate's \
413413
strategy of `{}`",
414-
tcx.crate_name(cnum),
415-
found_strategy.desc(),
416-
desired_strategy.desc()
417-
));
414+
tcx.crate_name(cnum),
415+
found_strategy.desc(),
416+
desired_strategy.desc()
417+
));
418+
}
419+
420+
let found_drop_strategy = tcx.panic_in_drop_strategy(cnum);
421+
if tcx.sess.opts.debugging_opts.panic_in_drop != found_drop_strategy {
422+
sess.err(&format!(
423+
"the crate `{}` is compiled with the \
424+
panic-in-drop strategy `{}` which is \
425+
incompatible with this crate's \
426+
strategy of `{}`",
427+
tcx.crate_name(cnum),
428+
found_drop_strategy.desc(),
429+
tcx.sess.opts.debugging_opts.panic_in_drop.desc()
430+
));
431+
}
418432
}
419433
}
420434
}

compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs

+1
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ provide! { <'tcx> tcx, def_id, other, cdata,
159159
has_panic_handler => { cdata.root.has_panic_handler }
160160
is_profiler_runtime => { cdata.root.profiler_runtime }
161161
panic_strategy => { cdata.root.panic_strategy }
162+
panic_in_drop_strategy => { cdata.root.panic_in_drop_strategy }
162163
extern_crate => {
163164
let r = *cdata.extern_crate.lock();
164165
r.map(|c| &*tcx.arena.alloc(c))

compiler/rustc_metadata/src/rmeta/encoder.rs

+1
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
692692
hash: tcx.crate_hash(LOCAL_CRATE),
693693
stable_crate_id: tcx.def_path_hash(LOCAL_CRATE.as_def_id()).stable_crate_id(),
694694
panic_strategy: tcx.sess.panic_strategy(),
695+
panic_in_drop_strategy: tcx.sess.opts.debugging_opts.panic_in_drop,
695696
edition: tcx.sess.edition(),
696697
has_global_allocator: tcx.has_global_allocator(LOCAL_CRATE),
697698
has_panic_handler: tcx.has_panic_handler(LOCAL_CRATE),

compiler/rustc_metadata/src/rmeta/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ crate struct CrateRoot<'tcx> {
205205
hash: Svh,
206206
stable_crate_id: StableCrateId,
207207
panic_strategy: PanicStrategy,
208+
panic_in_drop_strategy: PanicStrategy,
208209
edition: Edition,
209210
has_global_allocator: bool,
210211
has_panic_handler: bool,

compiler/rustc_middle/src/query/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1167,6 +1167,10 @@ rustc_queries! {
11671167
fatal_cycle
11681168
desc { "query a crate's configured panic strategy" }
11691169
}
1170+
query panic_in_drop_strategy(_: CrateNum) -> PanicStrategy {
1171+
fatal_cycle
1172+
desc { "query a crate's configured panic-in-drop strategy" }
1173+
}
11701174
query is_no_builtins(_: CrateNum) -> bool {
11711175
fatal_cycle
11721176
desc { "test whether a crate has `#![no_builtins]`" }

compiler/rustc_mir_transform/src/abort_unwinding_calls.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use rustc_middle::mir::*;
55
use rustc_middle::ty::layout;
66
use rustc_middle::ty::{self, TyCtxt};
77
use rustc_target::spec::abi::Abi;
8+
use rustc_target::spec::PanicStrategy;
89

910
/// A pass that runs which is targeted at ensuring that codegen guarantees about
1011
/// unwinding are upheld for compilations of panic=abort programs.
@@ -82,10 +83,11 @@ impl<'tcx> MirPass<'tcx> for AbortUnwindingCalls {
8283
};
8384
layout::fn_can_unwind(tcx, flags, sig.abi())
8485
}
85-
TerminatorKind::Drop { .. }
86-
| TerminatorKind::DropAndReplace { .. }
87-
| TerminatorKind::Assert { .. }
88-
| TerminatorKind::FalseUnwind { .. } => {
86+
TerminatorKind::Drop { .. } | TerminatorKind::DropAndReplace { .. } => {
87+
tcx.sess.opts.debugging_opts.panic_in_drop == PanicStrategy::Unwind
88+
&& layout::fn_can_unwind(tcx, CodegenFnAttrFlags::empty(), Abi::Rust)
89+
}
90+
TerminatorKind::Assert { .. } | TerminatorKind::FalseUnwind { .. } => {
8991
layout::fn_can_unwind(tcx, CodegenFnAttrFlags::empty(), Abi::Rust)
9092
}
9193
_ => continue,

compiler/rustc_session/src/options.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ mod desc {
349349
pub const parse_threads: &str = parse_number;
350350
pub const parse_passes: &str = "a space-separated list of passes, or `all`";
351351
pub const parse_panic_strategy: &str = "either `unwind` or `abort`";
352+
pub const parse_opt_panic_strategy: &str = parse_panic_strategy;
352353
pub const parse_relro_level: &str = "one of: `full`, `partial`, or `off`";
353354
pub const parse_sanitizers: &str =
354355
"comma separated list of sanitizers: `address`, `hwaddress`, `leak`, `memory` or `thread`";
@@ -549,7 +550,7 @@ mod parse {
549550
}
550551
}
551552

552-
crate fn parse_panic_strategy(slot: &mut Option<PanicStrategy>, v: Option<&str>) -> bool {
553+
crate fn parse_opt_panic_strategy(slot: &mut Option<PanicStrategy>, v: Option<&str>) -> bool {
553554
match v {
554555
Some("unwind") => *slot = Some(PanicStrategy::Unwind),
555556
Some("abort") => *slot = Some(PanicStrategy::Abort),
@@ -558,6 +559,15 @@ mod parse {
558559
true
559560
}
560561

562+
crate fn parse_panic_strategy(slot: &mut PanicStrategy, v: Option<&str>) -> bool {
563+
match v {
564+
Some("unwind") => *slot = PanicStrategy::Unwind,
565+
Some("abort") => *slot = PanicStrategy::Abort,
566+
_ => return false,
567+
}
568+
true
569+
}
570+
561571
crate fn parse_relro_level(slot: &mut Option<RelroLevel>, v: Option<&str>) -> bool {
562572
match v {
563573
Some(s) => match s.parse::<RelroLevel>() {
@@ -958,7 +968,7 @@ options! {
958968
"optimization level (0-3, s, or z; default: 0)"),
959969
overflow_checks: Option<bool> = (None, parse_opt_bool, [TRACKED],
960970
"use overflow checks for integer arithmetic"),
961-
panic: Option<PanicStrategy> = (None, parse_panic_strategy, [TRACKED],
971+
panic: Option<PanicStrategy> = (None, parse_opt_panic_strategy, [TRACKED],
962972
"panic strategy to compile crate with"),
963973
passes: Vec<String> = (Vec::new(), parse_list, [TRACKED],
964974
"a list of extra LLVM passes to run (space separated)"),
@@ -1186,6 +1196,8 @@ options! {
11861196
"pass `-install_name @rpath/...` to the macOS linker (default: no)"),
11871197
panic_abort_tests: bool = (false, parse_bool, [TRACKED],
11881198
"support compiling tests with panic=abort (default: no)"),
1199+
panic_in_drop: PanicStrategy = (PanicStrategy::Unwind, parse_panic_strategy, [TRACKED],
1200+
"panic strategy for panics in drops"),
11891201
parse_only: bool = (false, parse_bool, [UNTRACKED],
11901202
"parse only; do not compile, assemble, or link (default: no)"),
11911203
partially_uninit_const_threshold: Option<usize> = (None, parse_opt_number, [TRACKED],

compiler/rustc_typeck/src/collect.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use rustc_session::lint;
4646
use rustc_session::parse::feature_err;
4747
use rustc_span::symbol::{kw, sym, Ident, Symbol};
4848
use rustc_span::{Span, DUMMY_SP};
49-
use rustc_target::spec::{abi, SanitizerSet};
49+
use rustc_target::spec::{abi, PanicStrategy, SanitizerSet};
5050
use rustc_trait_selection::traits::error_reporting::suggestions::NextTypeParamName;
5151
use std::iter;
5252

@@ -2683,6 +2683,13 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
26832683
codegen_fn_attrs.flags |= CodegenFnAttrFlags::TRACK_CALLER;
26842684
}
26852685

2686+
// With -Z panic-in-drop=abort, drop_in_place never unwinds.
2687+
if tcx.sess.opts.debugging_opts.panic_in_drop == PanicStrategy::Abort {
2688+
if Some(id) == tcx.lang_items().drop_in_place_fn() {
2689+
codegen_fn_attrs.flags |= CodegenFnAttrFlags::NEVER_UNWIND;
2690+
}
2691+
}
2692+
26862693
let supported_target_features = tcx.supported_target_features(LOCAL_CRATE);
26872694

26882695
let mut inline_span = None;
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// compile-flags: -Z panic-in-drop=abort -O
2+
3+
// Ensure that unwinding code paths are eliminated from the output after
4+
// optimization.
5+
6+
#![crate_type = "lib"]
7+
use std::any::Any;
8+
use std::mem::forget;
9+
10+
pub struct ExternDrop;
11+
impl Drop for ExternDrop {
12+
#[inline(always)]
13+
fn drop(&mut self) {
14+
// This call may potentially unwind.
15+
extern "Rust" {
16+
fn extern_drop();
17+
}
18+
unsafe {
19+
extern_drop();
20+
}
21+
}
22+
}
23+
24+
struct AssertNeverDrop;
25+
impl Drop for AssertNeverDrop {
26+
#[inline(always)]
27+
fn drop(&mut self) {
28+
// This call should be optimized away as unreachable.
29+
extern "C" {
30+
fn should_not_appear_in_output();
31+
}
32+
unsafe {
33+
should_not_appear_in_output();
34+
}
35+
}
36+
}
37+
38+
// CHECK-LABEL: normal_drop
39+
// CHECK-NOT: should_not_appear_in_output
40+
#[no_mangle]
41+
pub fn normal_drop(x: ExternDrop) {
42+
let guard = AssertNeverDrop;
43+
drop(x);
44+
forget(guard);
45+
}
46+
47+
// CHECK-LABEL: indirect_drop
48+
// CHECK-NOT: should_not_appear_in_output
49+
#[no_mangle]
50+
pub fn indirect_drop(x: Box<dyn Any>) {
51+
let guard = AssertNeverDrop;
52+
drop(x);
53+
forget(guard);
54+
}

0 commit comments

Comments
 (0)