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