rustc: Fill out remaining parts of C-unwind ABI
This commit intends to fill out some of the remaining pieces of the C-unwind ABI. This has a number of other changes with it though to move this design space forward a bit. Notably contained within here is: * On `panic=unwind`, the `extern "C"` ABI is now considered as "may unwind". This fixes a longstanding soundness issue where if you `panic!()` in an `extern "C"` function defined in Rust that's actually UB because the LLVM representation for the function has the `nounwind` attribute, but then you unwind. * Whether or not a function unwinds now mainly considers the ABI of the function instead of first checking the panic strategy. This fixes a miscompile of `extern "C-unwind"` with `panic=abort` because that ABI can still unwind. * The aborting stub for non-unwinding ABIs with `panic=unwind` has been reimplemented. Previously this was done as a small tweak during MIR generation, but this has been moved to a separate and dedicated MIR pass. This new pass will, for appropriate functions and function calls, insert a `cleanup` landing pad for any function call that may unwind within a function that is itself not allowed to unwind. Note that this subtly changes some behavior from before where previously on an unwind which was caught-to-abort it would run active destructors in the function, and now it simply immediately aborts the process. * The `#[unwind]` attribute has been removed and all users in tests and such are now using `C-unwind` and `#![feature(c_unwind)]`. I think this is largely the last piece of the RFC to implement. Unfortunately I believe this is still not stabilizable as-is because activating the feature gate changes the behavior of the existing `extern "C"` ABI in a way that has no replacement. My thinking for how to enable this is that we add support for the `C-unwind` ABI on stable Rust first, and then after it hits stable we change the behavior of the `C` ABI. That way anyone straddling stable/beta/nightly can switch to `C-unwind` safely.
This commit is contained in:
parent
2939249f29
commit
1c07096a45
46 changed files with 431 additions and 478 deletions
|
@ -18,12 +18,7 @@ use super::{
|
|||
|
||||
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
||||
fn fn_can_unwind(&self, attrs: CodegenFnAttrFlags, abi: Abi) -> bool {
|
||||
layout::fn_can_unwind(
|
||||
self.tcx.sess.panic_strategy(),
|
||||
attrs,
|
||||
layout::conv_from_spec_abi(*self.tcx, abi),
|
||||
abi,
|
||||
)
|
||||
layout::fn_can_unwind(*self.tcx, attrs, abi)
|
||||
}
|
||||
|
||||
pub(super) fn eval_terminator(
|
||||
|
@ -77,7 +72,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
|
|||
(
|
||||
fn_val,
|
||||
caller_abi,
|
||||
self.fn_can_unwind(layout::fn_ptr_codegen_fn_attr_flags(), caller_abi),
|
||||
self.fn_can_unwind(CodegenFnAttrFlags::empty(), caller_abi),
|
||||
)
|
||||
}
|
||||
ty::FnDef(def_id, substs) => {
|
||||
|
|
|
@ -16,7 +16,7 @@ use std::fmt;
|
|||
use std::iter;
|
||||
|
||||
use crate::transform::{
|
||||
add_call_guards, add_moves_for_packed_drops, no_landing_pads, remove_noop_landing_pads,
|
||||
abort_unwinding_calls, add_call_guards, add_moves_for_packed_drops, remove_noop_landing_pads,
|
||||
run_passes, simplify,
|
||||
};
|
||||
use crate::util::elaborate_drops::{self, DropElaborator, DropFlagMode, DropStyle};
|
||||
|
@ -81,10 +81,10 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<'
|
|||
MirPhase::Const,
|
||||
&[&[
|
||||
&add_moves_for_packed_drops::AddMovesForPackedDrops,
|
||||
&no_landing_pads::NoLandingPads,
|
||||
&remove_noop_landing_pads::RemoveNoopLandingPads,
|
||||
&simplify::SimplifyCfg::new("make_shim"),
|
||||
&add_call_guards::CriticalCallEdges,
|
||||
&abort_unwinding_calls::AbortUnwindingCalls,
|
||||
]],
|
||||
);
|
||||
|
||||
|
|
124
compiler/rustc_mir/src/transform/abort_unwinding_calls.rs
Normal file
124
compiler/rustc_mir/src/transform/abort_unwinding_calls.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
use crate::transform::MirPass;
|
||||
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::layout;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_target::spec::abi::Abi;
|
||||
|
||||
/// 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 struct AbortUnwindingCalls;
|
||||
|
||||
impl<'tcx> MirPass<'tcx> for AbortUnwindingCalls {
|
||||
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
// This pass only runs on functions which themselves cannot unwind,
|
||||
// forcibly changing the body of the function to structurally provide
|
||||
// this guarantee by aborting on an unwind. If this function can unwind,
|
||||
// then there's nothing to do because it already should work correctly.
|
||||
//
|
||||
// Here we test for this function itself whether its ABI allows
|
||||
// unwinding or not.
|
||||
let body_flags = tcx.codegen_fn_attrs(body.source.def_id()).flags;
|
||||
let body_ty = tcx.type_of(body.source.def_id());
|
||||
let body_abi = match body_ty.kind() {
|
||||
ty::FnDef(..) => body_ty.fn_sig(tcx).abi(),
|
||||
ty::Closure(..) => Abi::RustCall,
|
||||
ty::Generator(..) => Abi::Rust,
|
||||
_ => span_bug!(body.span, "unexpected body ty: {:?}", body_ty),
|
||||
};
|
||||
let body_can_unwind = layout::fn_can_unwind(tcx, body_flags, 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.
|
||||
let mut calls_to_terminate = Vec::new();
|
||||
let mut cleanups_to_remove = Vec::new();
|
||||
for (id, block) in body.basic_blocks().iter_enumerated() {
|
||||
if block.is_cleanup {
|
||||
continue;
|
||||
}
|
||||
|
||||
let (func, source_info) = match &block.terminator {
|
||||
Some(Terminator { kind: TerminatorKind::Call { func, .. }, source_info }) => {
|
||||
(func, source_info)
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
let ty = func.ty(body, tcx);
|
||||
let sig = ty.fn_sig(tcx);
|
||||
let flags = match ty.kind() {
|
||||
ty::FnPtr(_) => CodegenFnAttrFlags::empty(),
|
||||
ty::FnDef(def_id, _) => tcx.codegen_fn_attrs(*def_id).flags,
|
||||
_ => span_bug!(source_info.span, "invalid callee of type {:?}", ty),
|
||||
};
|
||||
|
||||
let call_can_unwind = layout::fn_can_unwind(tcx, flags, sig.abi());
|
||||
|
||||
// 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.
|
||||
if !call_can_unwind {
|
||||
cleanups_to_remove.push(id);
|
||||
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 change the landing pad for this
|
||||
// function call to one that aborts.
|
||||
if !body_can_unwind {
|
||||
calls_to_terminate.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
// For call instructions which need to be terminated, we insert a
|
||||
// singular basic block which simply terminates, and then configure the
|
||||
// `cleanup` attribute for all calls we found to this basic block we
|
||||
// insert which means that any unwinding that happens in the functions
|
||||
// will force an abort of the process.
|
||||
if !calls_to_terminate.is_empty() {
|
||||
let bb = BasicBlockData {
|
||||
statements: Vec::new(),
|
||||
is_cleanup: true,
|
||||
terminator: Some(Terminator {
|
||||
source_info: SourceInfo::outermost(body.span),
|
||||
kind: TerminatorKind::Abort,
|
||||
}),
|
||||
};
|
||||
let abort_bb = body.basic_blocks_mut().push(bb);
|
||||
|
||||
for bb in calls_to_terminate {
|
||||
let cleanup = match &mut body.basic_blocks_mut()[bb].terminator {
|
||||
Some(Terminator { kind: TerminatorKind::Call { cleanup, .. }, .. }) => cleanup,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
*cleanup = Some(abort_bb);
|
||||
}
|
||||
}
|
||||
|
||||
for id in cleanups_to_remove {
|
||||
let cleanup = match &mut body.basic_blocks_mut()[id].terminator {
|
||||
Some(Terminator { kind: TerminatorKind::Call { cleanup, .. }, .. }) => cleanup,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
*cleanup = None;
|
||||
}
|
||||
|
||||
// We may have invalidated some `cleanup` blocks so clean those up now.
|
||||
super::simplify::remove_dead_blocks(tcx, body);
|
||||
}
|
||||
}
|
|
@ -53,7 +53,6 @@ use crate::dataflow::impls::{
|
|||
MaybeBorrowedLocals, MaybeLiveLocals, MaybeRequiresStorage, MaybeStorageLive,
|
||||
};
|
||||
use crate::dataflow::{self, Analysis};
|
||||
use crate::transform::no_landing_pads::no_landing_pads;
|
||||
use crate::transform::simplify;
|
||||
use crate::transform::MirPass;
|
||||
use crate::util::dump_mir;
|
||||
|
@ -960,8 +959,6 @@ fn create_generator_drop_shim<'tcx>(
|
|||
)
|
||||
}
|
||||
|
||||
no_landing_pads(tcx, &mut body);
|
||||
|
||||
// Make sure we remove dead blocks to remove
|
||||
// unrelated code from the resume part of the function
|
||||
simplify::remove_dead_blocks(tcx, &mut body);
|
||||
|
@ -1133,8 +1130,6 @@ fn create_generator_resume_function<'tcx>(
|
|||
make_generator_state_argument_indirect(tcx, body);
|
||||
make_generator_state_argument_pinned(tcx, body);
|
||||
|
||||
no_landing_pads(tcx, body);
|
||||
|
||||
// Make sure we remove dead blocks to remove
|
||||
// unrelated code from the drop part of the function
|
||||
simplify::remove_dead_blocks(tcx, body);
|
||||
|
|
|
@ -13,6 +13,7 @@ use rustc_middle::ty::{self, TyCtxt, TypeFoldable};
|
|||
use rustc_span::{Span, Symbol};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub mod abort_unwinding_calls;
|
||||
pub mod add_call_guards;
|
||||
pub mod add_moves_for_packed_drops;
|
||||
pub mod add_retag;
|
||||
|
@ -39,7 +40,6 @@ pub mod lower_intrinsics;
|
|||
pub mod lower_slice_len;
|
||||
pub mod match_branches;
|
||||
pub mod multiple_return_terminators;
|
||||
pub mod no_landing_pads;
|
||||
pub mod nrvo;
|
||||
pub mod promote_consts;
|
||||
pub mod remove_noop_landing_pads;
|
||||
|
@ -451,7 +451,6 @@ fn run_post_borrowck_cleanup_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tc
|
|||
|
||||
let post_borrowck_cleanup: &[&dyn MirPass<'tcx>] = &[
|
||||
// Remove all things only needed by analysis
|
||||
&no_landing_pads::NoLandingPads,
|
||||
&simplify_branches::SimplifyBranches::new("initial"),
|
||||
&remove_noop_landing_pads::RemoveNoopLandingPads,
|
||||
&cleanup_post_borrowck::CleanupNonCodegenStatements,
|
||||
|
@ -459,7 +458,6 @@ fn run_post_borrowck_cleanup_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tc
|
|||
// These next passes must be executed together
|
||||
&add_call_guards::CriticalCallEdges,
|
||||
&elaborate_drops::ElaborateDrops,
|
||||
&no_landing_pads::NoLandingPads,
|
||||
// AddMovesForPackedDrops needs to run after drop
|
||||
// elaboration.
|
||||
&add_moves_for_packed_drops::AddMovesForPackedDrops,
|
||||
|
@ -530,6 +528,10 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
|||
|
||||
// Some cleanup necessary at least for LLVM and potentially other codegen backends.
|
||||
let pre_codegen_cleanup: &[&dyn MirPass<'tcx>] = &[
|
||||
// This will remove extraneous landing pads which are no longer
|
||||
// necessary as well as well as forcing any call in a non-unwinding
|
||||
// function calling a possibly-unwinding function to abort the process.
|
||||
&abort_unwinding_calls::AbortUnwindingCalls,
|
||||
&add_call_guards::CriticalCallEdges,
|
||||
// Dump the end result for testing and debugging purposes.
|
||||
&dump_mir::Marker("PreCodegen"),
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
//! This pass removes the unwind branch of all the terminators when the no-landing-pads option is
|
||||
//! specified.
|
||||
|
||||
use crate::transform::MirPass;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_target::spec::PanicStrategy;
|
||||
|
||||
pub struct NoLandingPads;
|
||||
|
||||
impl<'tcx> MirPass<'tcx> for NoLandingPads {
|
||||
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
no_landing_pads(tcx, body)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_landing_pads<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
|
||||
if tcx.sess.panic_strategy() != PanicStrategy::Abort {
|
||||
return;
|
||||
}
|
||||
|
||||
for block in body.basic_blocks_mut() {
|
||||
let terminator = block.terminator_mut();
|
||||
if let Some(unwind) = terminator.kind.unwind_mut() {
|
||||
unwind.take();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue