1
Fork 0

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:
Alex Crichton 2021-06-08 11:23:58 -07:00
parent 2939249f29
commit 1c07096a45
46 changed files with 431 additions and 478 deletions

View file

@ -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) => {

View file

@ -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,
]],
);

View 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);
}
}

View file

@ -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);

View file

@ -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"),

View file

@ -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();
}
}
}