]> git.proxmox.com Git - rustc.git/blob - compiler/rustc_mir_transform/src/abort_unwinding_calls.rs
New upstream version 1.74.1+dfsg1
[rustc.git] / compiler / rustc_mir_transform / src / abort_unwinding_calls.rs
1 use crate::MirPass;
2 use rustc_ast::InlineAsmOptions;
3 use rustc_middle::mir::*;
4 use rustc_middle::ty::layout;
5 use rustc_middle::ty::{self, TyCtxt};
6 use rustc_target::spec::abi::Abi;
7 use rustc_target::spec::PanicStrategy;
8
9 /// A pass that runs which is targeted at ensuring that codegen guarantees about
10 /// unwinding are upheld for compilations of panic=abort programs.
11 ///
12 /// When compiling with panic=abort codegen backends generally want to assume
13 /// that all Rust-defined functions do not unwind, and it's UB if they actually
14 /// do unwind. Foreign functions, however, can be declared as "may unwind" via
15 /// their ABI (e.g. `extern "C-unwind"`). To uphold the guarantees that
16 /// Rust-defined functions never unwind a well-behaved Rust program needs to
17 /// catch unwinding from foreign functions and force them to abort.
18 ///
19 /// This pass walks over all functions calls which may possibly unwind,
20 /// and if any are found sets their cleanup to a block that aborts the process.
21 /// This forces all unwinds, in panic=abort mode happening in foreign code, to
22 /// trigger a process abort.
23 #[derive(PartialEq)]
24 pub struct AbortUnwindingCalls;
25
26 impl<'tcx> MirPass<'tcx> for AbortUnwindingCalls {
27 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
28 let def_id = body.source.def_id();
29 let kind = tcx.def_kind(def_id);
30
31 // We don't simplify the MIR of constants at this time because that
32 // namely results in a cyclic query when we call `tcx.type_of` below.
33 if !kind.is_fn_like() {
34 return;
35 }
36
37 // Here we test for this function itself whether its ABI allows
38 // unwinding or not.
39 let body_ty = tcx.type_of(def_id).skip_binder();
40 let body_abi = match body_ty.kind() {
41 ty::FnDef(..) => body_ty.fn_sig(tcx).abi(),
42 ty::Closure(..) => Abi::RustCall,
43 ty::Generator(..) => Abi::Rust,
44 _ => span_bug!(body.span, "unexpected body ty: {:?}", body_ty),
45 };
46 let body_can_unwind = layout::fn_can_unwind(tcx, Some(def_id), body_abi);
47
48 // Look in this function body for any basic blocks which are terminated
49 // with a function call, and whose function we're calling may unwind.
50 // This will filter to functions with `extern "C-unwind"` ABIs, for
51 // example.
52 let mut calls_to_terminate = Vec::new();
53 let mut cleanups_to_remove = Vec::new();
54 for (id, block) in body.basic_blocks.iter_enumerated() {
55 if block.is_cleanup {
56 continue;
57 }
58 let Some(terminator) = &block.terminator else { continue };
59 let span = terminator.source_info.span;
60
61 let call_can_unwind = match &terminator.kind {
62 TerminatorKind::Call { func, .. } => {
63 let ty = func.ty(body, tcx);
64 let sig = ty.fn_sig(tcx);
65 let fn_def_id = match ty.kind() {
66 ty::FnPtr(_) => None,
67 &ty::FnDef(def_id, _) => Some(def_id),
68 _ => span_bug!(span, "invalid callee of type {:?}", ty),
69 };
70 layout::fn_can_unwind(tcx, fn_def_id, sig.abi())
71 }
72 TerminatorKind::Drop { .. } => {
73 tcx.sess.opts.unstable_opts.panic_in_drop == PanicStrategy::Unwind
74 && layout::fn_can_unwind(tcx, None, Abi::Rust)
75 }
76 TerminatorKind::Assert { .. } | TerminatorKind::FalseUnwind { .. } => {
77 layout::fn_can_unwind(tcx, None, Abi::Rust)
78 }
79 TerminatorKind::InlineAsm { options, .. } => {
80 options.contains(InlineAsmOptions::MAY_UNWIND)
81 }
82 _ if terminator.unwind().is_some() => {
83 span_bug!(span, "unexpected terminator that may unwind {:?}", terminator)
84 }
85 _ => continue,
86 };
87
88 // If this function call can't unwind, then there's no need for it
89 // to have a landing pad. This means that we can remove any cleanup
90 // registered for it.
91 if !call_can_unwind {
92 cleanups_to_remove.push(id);
93 continue;
94 }
95
96 // Otherwise if this function can unwind, then if the outer function
97 // can also unwind there's nothing to do. If the outer function
98 // can't unwind, however, we need to change the landing pad for this
99 // function call to one that aborts.
100 if !body_can_unwind {
101 calls_to_terminate.push(id);
102 }
103 }
104
105 for id in calls_to_terminate {
106 let cleanup = body.basic_blocks_mut()[id].terminator_mut().unwind_mut().unwrap();
107 *cleanup = UnwindAction::Terminate(UnwindTerminateReason::Abi);
108 }
109
110 for id in cleanups_to_remove {
111 let cleanup = body.basic_blocks_mut()[id].terminator_mut().unwind_mut().unwrap();
112 *cleanup = UnwindAction::Unreachable;
113 }
114
115 // We may have invalidated some `cleanup` blocks so clean those up now.
116 super::simplify::remove_dead_blocks(tcx, body);
117 }
118 }