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