]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_mir_transform/src/abort_unwinding_calls.rs
New upstream version 1.61.0+dfsg1
[rustc.git] / compiler / rustc_mir_transform / src / abort_unwinding_calls.rs
CommitLineData
c295e0f8 1use crate::MirPass;
94222f64
XL
2use rustc_hir::def::DefKind;
3use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
4use rustc_middle::mir::*;
5use rustc_middle::ty::layout;
6use rustc_middle::ty::{self, TyCtxt};
7use rustc_target::spec::abi::Abi;
c295e0f8 8use 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)]
25pub struct AbortUnwindingCalls;
26
27impl<'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}