]>
Commit | Line | Data |
---|---|---|
c295e0f8 | 1 | use crate::MirPass; |
94222f64 XL |
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; | |
c295e0f8 | 8 | use rustc_target::spec::PanicStrategy; |
94222f64 XL |
9 | |
10 | /// A pass that runs which is targeted at ensuring that codegen guarantees about | |
11 | /// unwinding are upheld for compilations of panic=abort programs. | |
12 | /// | |
13 | /// When compiling with panic=abort codegen backends generally want to assume | |
14 | /// that all Rust-defined functions do not unwind, and it's UB if they actually | |
15 | /// do unwind. Foreign functions, however, can be declared as "may unwind" via | |
16 | /// their ABI (e.g. `extern "C-unwind"`). To uphold the guarantees that | |
17 | /// Rust-defined functions never unwind a well-behaved Rust program needs to | |
18 | /// catch unwinding from foreign functions and force them to abort. | |
19 | /// | |
20 | /// This pass walks over all functions calls which may possibly unwind, | |
21 | /// and if any are found sets their cleanup to a block that aborts the process. | |
22 | /// This forces all unwinds, in panic=abort mode happening in foreign code, to | |
23 | /// trigger a process abort. | |
24 | #[derive(PartialEq)] | |
25 | pub struct AbortUnwindingCalls; | |
26 | ||
27 | impl<'tcx> MirPass<'tcx> for AbortUnwindingCalls { | |
28 | fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | |
29 | let def_id = body.source.def_id(); | |
30 | let kind = tcx.def_kind(def_id); | |
31 | ||
32 | // We don't simplify the MIR of constants at this time because that | |
33 | // namely results in a cyclic query when we call `tcx.type_of` below. | |
34 | let is_function = match kind { | |
35 | DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true, | |
36 | _ => tcx.is_closure(def_id), | |
37 | }; | |
38 | if !is_function { | |
39 | return; | |
40 | } | |
41 | ||
42 | // This pass only runs on functions which themselves cannot unwind, | |
43 | // forcibly changing the body of the function to structurally provide | |
44 | // this guarantee by aborting on an unwind. If this function can unwind, | |
45 | // then there's nothing to do because it already should work correctly. | |
46 | // | |
47 | // Here we test for this function itself whether its ABI allows | |
48 | // unwinding or not. | |
49 | let body_flags = tcx.codegen_fn_attrs(def_id).flags; | |
50 | let body_ty = tcx.type_of(def_id); | |
51 | let body_abi = match body_ty.kind() { | |
52 | ty::FnDef(..) => body_ty.fn_sig(tcx).abi(), | |
53 | ty::Closure(..) => Abi::RustCall, | |
54 | ty::Generator(..) => Abi::Rust, | |
55 | _ => span_bug!(body.span, "unexpected body ty: {:?}", body_ty), | |
56 | }; | |
57 | let body_can_unwind = layout::fn_can_unwind(tcx, body_flags, body_abi); | |
58 | ||
59 | // Look in this function body for any basic blocks which are terminated | |
60 | // with a function call, and whose function we're calling may unwind. | |
61 | // This will filter to functions with `extern "C-unwind"` ABIs, for | |
62 | // example. | |
63 | let mut calls_to_terminate = Vec::new(); | |
64 | let mut cleanups_to_remove = Vec::new(); | |
65 | for (id, block) in body.basic_blocks().iter_enumerated() { | |
66 | if block.is_cleanup { | |
67 | continue; | |
68 | } | |
5e7ed085 | 69 | let Some(terminator) = &block.terminator else { continue }; |
94222f64 XL |
70 | let span = terminator.source_info.span; |
71 | ||
72 | let call_can_unwind = match &terminator.kind { | |
73 | TerminatorKind::Call { func, .. } => { | |
74 | let ty = func.ty(body, tcx); | |
75 | let sig = ty.fn_sig(tcx); | |
76 | let flags = match ty.kind() { | |
77 | ty::FnPtr(_) => CodegenFnAttrFlags::empty(), | |
78 | ty::FnDef(def_id, _) => tcx.codegen_fn_attrs(*def_id).flags, | |
79 | _ => span_bug!(span, "invalid callee of type {:?}", ty), | |
80 | }; | |
81 | layout::fn_can_unwind(tcx, flags, sig.abi()) | |
82 | } | |
c295e0f8 XL |
83 | TerminatorKind::Drop { .. } | TerminatorKind::DropAndReplace { .. } => { |
84 | tcx.sess.opts.debugging_opts.panic_in_drop == PanicStrategy::Unwind | |
85 | && layout::fn_can_unwind(tcx, CodegenFnAttrFlags::empty(), Abi::Rust) | |
86 | } | |
87 | TerminatorKind::Assert { .. } | TerminatorKind::FalseUnwind { .. } => { | |
94222f64 XL |
88 | layout::fn_can_unwind(tcx, CodegenFnAttrFlags::empty(), Abi::Rust) |
89 | } | |
90 | _ => continue, | |
91 | }; | |
92 | ||
93 | // If this function call can't unwind, then there's no need for it | |
94 | // to have a landing pad. This means that we can remove any cleanup | |
95 | // registered for it. | |
96 | if !call_can_unwind { | |
97 | cleanups_to_remove.push(id); | |
98 | continue; | |
99 | } | |
100 | ||
101 | // Otherwise if this function can unwind, then if the outer function | |
102 | // can also unwind there's nothing to do. If the outer function | |
103 | // can't unwind, however, we need to change the landing pad for this | |
104 | // function call to one that aborts. | |
105 | if !body_can_unwind { | |
106 | calls_to_terminate.push(id); | |
107 | } | |
108 | } | |
109 | ||
110 | // For call instructions which need to be terminated, we insert a | |
111 | // singular basic block which simply terminates, and then configure the | |
112 | // `cleanup` attribute for all calls we found to this basic block we | |
113 | // insert which means that any unwinding that happens in the functions | |
114 | // will force an abort of the process. | |
115 | if !calls_to_terminate.is_empty() { | |
116 | let bb = BasicBlockData { | |
117 | statements: Vec::new(), | |
118 | is_cleanup: true, | |
119 | terminator: Some(Terminator { | |
120 | source_info: SourceInfo::outermost(body.span), | |
121 | kind: TerminatorKind::Abort, | |
122 | }), | |
123 | }; | |
124 | let abort_bb = body.basic_blocks_mut().push(bb); | |
125 | ||
126 | for bb in calls_to_terminate { | |
127 | let cleanup = body.basic_blocks_mut()[bb].terminator_mut().unwind_mut().unwrap(); | |
128 | *cleanup = Some(abort_bb); | |
129 | } | |
130 | } | |
131 | ||
132 | for id in cleanups_to_remove { | |
133 | let cleanup = body.basic_blocks_mut()[id].terminator_mut().unwind_mut().unwrap(); | |
134 | *cleanup = None; | |
135 | } | |
136 | ||
137 | // We may have invalidated some `cleanup` blocks so clean those up now. | |
138 | super::simplify::remove_dead_blocks(tcx, body); | |
139 | } | |
140 | } |