rustc_mir_transform/abort_unwinding_calls.rs
1use rustc_abi::ExternAbi;
2use rustc_ast::InlineAsmOptions;
3use rustc_middle::mir::*;
4use rustc_middle::ty::{self, TyCtxt, layout};
5use rustc_span::{span_bug, sym};
6use rustc_target::spec::PanicStrategy;
7
8use crate::PassPolicy;
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(super) struct AbortUnwindingCalls;
26
27impl<'tcx> crate::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 if !kind.is_fn_like() {
35 return;
36 }
37
38 // Represent whether this compilation target fundamentally doesn't
39 // support unwinding at all at an ABI level. If this the target has no
40 // support for unwinding then cleanup actions, for example, are all
41 // unnecessary and can be considered unreachable.
42 //
43 // Currently this is only true for wasm targets on panic=abort when the
44 // `exception-handling` target feature is disabled. In such a
45 // configuration it's illegal to emit exception-related instructions so
46 // it's not possible to unwind.
47 let target_supports_unwinding = !(tcx.sess.target.is_like_wasm
48 && tcx.sess.panic_strategy() == PanicStrategy::Abort
49 && !tcx.asm_target_features(def_id).contains(&sym::exception_handling));
50
51 // Here we test for this function itself whether its ABI allows
52 // unwinding or not.
53 let body_ty = tcx.type_of(def_id).skip_binder();
54 let body_abi = match body_ty.kind() {
55 ty::FnDef(..) => body_ty.fn_sig(tcx).abi(),
56 ty::Closure(..) => ExternAbi::RustCall,
57 ty::CoroutineClosure(..) => ExternAbi::RustCall,
58 ty::Coroutine(..) => ExternAbi::Rust,
59 ty::Error(_) => return,
60 _ => span_bug!(body.span, "unexpected body ty: {:?}", body_ty),
61 };
62 let body_can_unwind = layout::fn_can_unwind(tcx, Some(def_id), body_abi);
63
64 // Look in this function body for any basic blocks which are terminated
65 // with a function call, and whose function we're calling may unwind.
66 // This will filter to functions with `extern "C-unwind"` ABIs, for
67 // example.
68 for block in body.basic_blocks.as_mut() {
69 let Some(terminator) = &mut block.terminator else { continue };
70 let span = terminator.source_info.span;
71
72 // If we see an `UnwindResume` terminator inside a function then:
73 //
74 // * If the target doesn't support unwinding at all, then this is an
75 // unreachable block.
76 // * If the body cannot unwind, we need to replace it with
77 // `UnwindTerminate`.
78 if let TerminatorKind::UnwindResume = &terminator.kind {
79 if !target_supports_unwinding {
80 terminator.kind = TerminatorKind::Unreachable;
81 } else if !body_can_unwind {
82 terminator.kind = TerminatorKind::UnwindTerminate(UnwindTerminateReason::Abi);
83 }
84 }
85
86 if block.is_cleanup {
87 continue;
88 }
89
90 let call_can_unwind = match &terminator.kind {
91 TerminatorKind::Call { func, .. } => {
92 let ty = func.ty(&body.local_decls, tcx);
93 let sig = ty.fn_sig(tcx);
94 let fn_def_id = match ty.kind() {
95 ty::FnPtr(..) => None,
96 &ty::FnDef(def_id, _) => Some(def_id),
97 _ => span_bug!(span, "invalid callee of type {:?}", ty),
98 };
99 layout::fn_can_unwind(tcx, fn_def_id, sig.abi())
100 }
101 TerminatorKind::Drop { .. } => {
102 tcx.sess.opts.unstable_opts.panic_in_drop == PanicStrategy::Unwind
103 && layout::fn_can_unwind(tcx, None, ExternAbi::Rust)
104 }
105 TerminatorKind::Assert { .. } | TerminatorKind::FalseUnwind { .. } => {
106 layout::fn_can_unwind(tcx, None, ExternAbi::Rust)
107 }
108 TerminatorKind::InlineAsm { options, .. } => {
109 options.contains(InlineAsmOptions::MAY_UNWIND)
110 }
111 _ if terminator.unwind().is_some() => {
112 span_bug!(span, "unexpected terminator that may unwind {:?}", terminator)
113 }
114 _ => continue,
115 };
116
117 if !call_can_unwind || !target_supports_unwinding {
118 // If this function call can't unwind, or if the target doesn't
119 // support unwinding at all, then there's no need for it
120 // to have a landing pad. This means that we can remove any cleanup
121 // registered for it (and turn it into `UnwindAction::Unreachable`).
122 let cleanup = block.terminator_mut().unwind_mut().unwrap();
123 *cleanup = UnwindAction::Unreachable;
124 } else if !body_can_unwind
125 && matches!(terminator.unwind(), Some(UnwindAction::Continue))
126 {
127 // Otherwise if this function can unwind, then if the outer function
128 // can also unwind there's nothing to do. If the outer function
129 // can't unwind, however, we need to ensure that any `UnwindAction::Continue`
130 // is replaced with terminate. For those with `UnwindAction::Cleanup`,
131 // cleanup will still happen, and terminate will happen afterwards handled by
132 // the `UnwindResume` -> `UnwindTerminate` terminator replacement.
133 let cleanup = block.terminator_mut().unwind_mut().unwrap();
134 *cleanup = UnwindAction::Terminate(UnwindTerminateReason::Abi);
135 }
136 }
137
138 // We may have invalidated some `cleanup` blocks so clean those up now.
139 super::simplify::remove_dead_blocks(body);
140 }
141
142 fn policy(&self, _ctx: &crate::PassCtx<'_>) -> PassPolicy {
143 // Implements part of MIR semantics, turning effectively implicit aborts into explicit
144 // ones.
145 PassPolicy::Required
146 }
147}