Guard against unwinding in cleanup code

This commit is contained in:
Gary Guo 2022-01-14 23:54:26 +00:00
parent 3cfa4def7c
commit f74e8c7afc
3 changed files with 59 additions and 15 deletions

View file

@ -135,21 +135,38 @@ fn do_call<Bx: BuilderMethods<'a, 'tcx>>(
// If there is a cleanup block and the function we're calling can unwind, then
// do an invoke, otherwise do a call.
let fn_ty = bx.fn_decl_backend_type(&fn_abi);
if let Some(cleanup) = cleanup.filter(|_| fn_abi.can_unwind) {
let unwind_block = if let Some(cleanup) = cleanup.filter(|_| fn_abi.can_unwind) {
Some(self.llblock(fx, cleanup))
} else if fx.mir[self.bb].is_cleanup
&& fn_abi.can_unwind
&& !base::wants_msvc_seh(fx.cx.tcx().sess)
{
// Exception must not propagate out of the execution of a cleanup (doing so
// can cause undefined behaviour). We insert a double unwind guard for
// functions that can potentially unwind to protect against this.
//
// This is not necessary for SEH which does not use successive unwinding
// like Itanium EH. EH frames in SEH are different from normal function
// frames and SEH will abort automatically if an exception tries to
// propagate out from cleanup.
Some(fx.double_unwind_guard())
} else {
None
};
if let Some(unwind_block) = unwind_block {
let ret_llbb = if let Some((_, target)) = destination {
fx.llbb(target)
} else {
fx.unreachable_block()
};
let invokeret = bx.invoke(
fn_ty,
fn_ptr,
&llargs,
ret_llbb,
self.llblock(fx, cleanup),
self.funclet(fx),
);
let invokeret =
bx.invoke(fn_ty, fn_ptr, &llargs, ret_llbb, unwind_block, self.funclet(fx));
bx.apply_attrs_callsite(&fn_abi, invokeret);
if fx.mir[self.bb].is_cleanup {
bx.apply_attrs_to_cleanup_callsite(invokeret);
}
if let Some((ret_dest, target)) = destination {
let mut ret_bx = fx.build_block(target);
@ -486,9 +503,6 @@ fn codegen_abort_terminator(
let span = terminator.source_info.span;
self.set_debug_loc(&mut bx, terminator.source_info);
// Get the location information.
let location = self.get_caller_location(&mut bx, terminator.source_info).immediate();
// Obtain the panic entry point.
let def_id = common::langcall(bx.tcx(), Some(span), "", LangItem::PanicNoUnwind);
let instance = ty::Instance::mono(bx.tcx(), def_id);
@ -496,7 +510,7 @@ fn codegen_abort_terminator(
let llfn = bx.get_fn_addr(instance);
// Codegen the actual panic invoke/call.
helper.do_call(self, &mut bx, fn_abi, llfn, &[location], None, None);
helper.do_call(self, &mut bx, fn_abi, llfn, &[], None, None);
}
/// Returns `true` if this is indeed a panic intrinsic and codegen is done.
@ -1398,6 +1412,33 @@ fn unreachable_block(&mut self) -> Bx::BasicBlock {
})
}
fn double_unwind_guard(&mut self) -> Bx::BasicBlock {
self.double_unwind_guard.unwrap_or_else(|| {
assert!(!base::wants_msvc_seh(self.cx.sess()));
let mut bx = self.new_block("abort");
let llpersonality = self.cx.eh_personality();
let llretty = self.landing_pad_type();
bx.cleanup_landing_pad(llretty, llpersonality);
let def_id = common::langcall(bx.tcx(), None, "", LangItem::PanicNoUnwind);
let instance = ty::Instance::mono(bx.tcx(), def_id);
let fn_abi = bx.fn_abi_of_instance(instance, ty::List::empty());
let fn_ptr = bx.get_fn_addr(instance);
let fn_ty = bx.fn_decl_backend_type(&fn_abi);
let llret = bx.call(fn_ty, fn_ptr, &[], None);
bx.apply_attrs_callsite(&fn_abi, llret);
bx.apply_attrs_to_cleanup_callsite(llret);
bx.unreachable();
let llbb = bx.llbb();
self.double_unwind_guard = Some(llbb);
llbb
})
}
// FIXME(eddyb) replace with `build_sibling_block`/`append_sibling_block`
// (which requires having a `Bx` already, and not all callers do).
fn new_block(&self, name: &str) -> Bx {

View file

@ -62,6 +62,9 @@ pub struct FunctionCx<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> {
/// Cached unreachable block
unreachable_block: Option<Bx::BasicBlock>,
/// Cached double unwind guarding block
double_unwind_guard: Option<Bx::BasicBlock>,
/// The location where each MIR arg/var/tmp/ret is stored. This is
/// usually an `PlaceRef` representing an alloca, but not always:
/// sometimes we can skip the alloca and just store the value
@ -169,6 +172,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
personality_slot: None,
cached_llbbs,
unreachable_block: None,
double_unwind_guard: None,
cleanup_kinds,
landing_pads: IndexVec::from_elem(None, mir.basic_blocks()),
funclets: IndexVec::from_fn_n(|_| None, mir.basic_blocks().len()),

View file

@ -87,8 +87,7 @@ fn panic_bounds_check(index: usize, len: usize) -> ! {
#[cfg(not(bootstrap))]
#[cold]
#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never))]
#[track_caller]
#[inline(never)]
#[lang = "panic_no_unwind"] // needed by codegen for panic in nounwind function
fn panic_no_unwind() -> ! {
if cfg!(feature = "panic_immediate_abort") {