]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir_transform/src/abort_unwinding_calls.rs
Rollup merge of #88578 - notriddle:notriddle/suggest-add-reference-to-for-loop-iter...
[rust.git] / compiler / rustc_mir_transform / src / abort_unwinding_calls.rs
1 use crate::MirPass;
2 use rustc_hir::def::DefKind;
3 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
4 use rustc_middle::mir::*;
5 use rustc_middle::ty::layout;
6 use rustc_middle::ty::{self, TyCtxt};
7 use rustc_target::spec::abi::Abi;
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         let is_function = match kind {
34             DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true,
35             _ => tcx.is_closure(def_id),
36         };
37         if !is_function {
38             return;
39         }
40
41         // This pass only runs on functions which themselves cannot unwind,
42         // forcibly changing the body of the function to structurally provide
43         // this guarantee by aborting on an unwind. If this function can unwind,
44         // then there's nothing to do because it already should work correctly.
45         //
46         // Here we test for this function itself whether its ABI allows
47         // unwinding or not.
48         let body_flags = tcx.codegen_fn_attrs(def_id).flags;
49         let body_ty = tcx.type_of(def_id);
50         let body_abi = match body_ty.kind() {
51             ty::FnDef(..) => body_ty.fn_sig(tcx).abi(),
52             ty::Closure(..) => Abi::RustCall,
53             ty::Generator(..) => Abi::Rust,
54             _ => span_bug!(body.span, "unexpected body ty: {:?}", body_ty),
55         };
56         let body_can_unwind = layout::fn_can_unwind(tcx, body_flags, body_abi);
57
58         // Look in this function body for any basic blocks which are terminated
59         // with a function call, and whose function we're calling may unwind.
60         // This will filter to functions with `extern "C-unwind"` ABIs, for
61         // example.
62         let mut calls_to_terminate = Vec::new();
63         let mut cleanups_to_remove = Vec::new();
64         for (id, block) in body.basic_blocks().iter_enumerated() {
65             if block.is_cleanup {
66                 continue;
67             }
68             let terminator = match &block.terminator {
69                 Some(terminator) => terminator,
70                 None => continue,
71             };
72             let span = terminator.source_info.span;
73
74             let call_can_unwind = match &terminator.kind {
75                 TerminatorKind::Call { func, .. } => {
76                     let ty = func.ty(body, tcx);
77                     let sig = ty.fn_sig(tcx);
78                     let flags = match ty.kind() {
79                         ty::FnPtr(_) => CodegenFnAttrFlags::empty(),
80                         ty::FnDef(def_id, _) => tcx.codegen_fn_attrs(*def_id).flags,
81                         _ => span_bug!(span, "invalid callee of type {:?}", ty),
82                     };
83                     layout::fn_can_unwind(tcx, flags, sig.abi())
84                 }
85                 TerminatorKind::Drop { .. }
86                 | TerminatorKind::DropAndReplace { .. }
87                 | TerminatorKind::Assert { .. }
88                 | TerminatorKind::FalseUnwind { .. } => {
89                     layout::fn_can_unwind(tcx, CodegenFnAttrFlags::empty(), Abi::Rust)
90                 }
91                 _ => continue,
92             };
93
94             // If this function call can't unwind, then there's no need for it
95             // to have a landing pad. This means that we can remove any cleanup
96             // registered for it.
97             if !call_can_unwind {
98                 cleanups_to_remove.push(id);
99                 continue;
100             }
101
102             // Otherwise if this function can unwind, then if the outer function
103             // can also unwind there's nothing to do. If the outer function
104             // can't unwind, however, we need to change the landing pad for this
105             // function call to one that aborts.
106             if !body_can_unwind {
107                 calls_to_terminate.push(id);
108             }
109         }
110
111         // For call instructions which need to be terminated, we insert a
112         // singular basic block which simply terminates, and then configure the
113         // `cleanup` attribute for all calls we found to this basic block we
114         // insert which means that any unwinding that happens in the functions
115         // will force an abort of the process.
116         if !calls_to_terminate.is_empty() {
117             let bb = BasicBlockData {
118                 statements: Vec::new(),
119                 is_cleanup: true,
120                 terminator: Some(Terminator {
121                     source_info: SourceInfo::outermost(body.span),
122                     kind: TerminatorKind::Abort,
123                 }),
124             };
125             let abort_bb = body.basic_blocks_mut().push(bb);
126
127             for bb in calls_to_terminate {
128                 let cleanup = body.basic_blocks_mut()[bb].terminator_mut().unwind_mut().unwrap();
129                 *cleanup = Some(abort_bb);
130             }
131         }
132
133         for id in cleanups_to_remove {
134             let cleanup = body.basic_blocks_mut()[id].terminator_mut().unwind_mut().unwrap();
135             *cleanup = None;
136         }
137
138         // We may have invalidated some `cleanup` blocks so clean those up now.
139         super::simplify::remove_dead_blocks(tcx, body);
140     }
141 }