Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guard against unwinding in cleanup code #92911

Merged
merged 5 commits into from
Feb 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 56 additions & 13 deletions compiler/rustc_codegen_ssa/src/mir/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,21 +135,38 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'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);
Expand Down Expand Up @@ -486,17 +503,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
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);
let fn_abi = bx.fn_abi_of_instance(instance, ty::List::empty());
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.
Expand Down Expand Up @@ -1398,6 +1412,35 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
})
}

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");
self.set_debug_loc(&mut bx, mir::SourceInfo::outermost(self.mir.span));

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 {
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_codegen_ssa/src/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()),
Expand Down
3 changes: 1 addition & 2 deletions library/core/src/panicking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
12 changes: 1 addition & 11 deletions src/test/codegen/drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,7 @@ pub fn droppy() {
// FIXME(eddyb) the `void @` forces a match on the instruction, instead of the
// comment, that's `; call core::ptr::drop_in_place::<drop::SomeUniqueName>`
// for the `v0` mangling, should switch to matching on that once `legacy` is gone.
// CHECK-NOT: invoke void @{{.*}}drop_in_place{{.*}}SomeUniqueName
// CHECK: call void @{{.*}}drop_in_place{{.*}}SomeUniqueName
// CHECK: call void @{{.*}}drop_in_place{{.*}}SomeUniqueName
// CHECK-NOT: call void @{{.*}}drop_in_place{{.*}}SomeUniqueName
// CHECK: invoke void @{{.*}}drop_in_place{{.*}}SomeUniqueName
// CHECK-NOT: invoke void @{{.*}}drop_in_place{{.*}}SomeUniqueName
// CHECK: call void @{{.*}}drop_in_place{{.*}}SomeUniqueName
// CHECK-NOT: call void @{{.*}}drop_in_place{{.*}}SomeUniqueName
// CHECK: invoke void @{{.*}}drop_in_place{{.*}}SomeUniqueName
// CHECK-NOT: invoke void @{{.*}}drop_in_place{{.*}}SomeUniqueName
// CHECK: call void @{{.*}}drop_in_place{{.*}}SomeUniqueName
// CHECK-COUNT-6: {{(call|invoke) void @.*}}drop_in_place{{.*}}SomeUniqueName
// CHECK-NOT: {{(call|invoke) void @.*}}drop_in_place{{.*}}SomeUniqueName
// The next line checks for the } that ends the function definition
// CHECK-LABEL: {{^[}]}}
Expand Down
2 changes: 1 addition & 1 deletion src/test/codegen/unwind-landingpad-cold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// get the `cold` attribute.

// CHECK-LABEL: @check_cold
// CHECK: call void {{.+}}drop_in_place{{.+}} [[ATTRIBUTES:#[0-9]+]]
// CHECK: {{(call|invoke) void .+}}drop_in_place{{.+}} [[ATTRIBUTES:#[0-9]+]]
// CHECK: attributes [[ATTRIBUTES]] = { cold }
#[no_mangle]
pub fn check_cold(f: fn(), x: Box<u32>) {
Expand Down
10 changes: 10 additions & 0 deletions src/test/run-make-fulldeps/foreign-double-unwind/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-include ../tools.mk

all: foo
$(call RUN,foo) | $(CGREP) -v unreachable
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be ! grep unreachable ? -v inverts the match, so if the program writes any output grep reports success.

We want instead to search for "unreachable" and fail the build if it is found.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$(CGREP) is not grep.


foo: foo.rs $(call NATIVE_STATICLIB,foo)
$(RUSTC) $< -lfoo $(EXTRARSCXXFLAGS)

$(TMPDIR)/libfoo.o: foo.cpp
$(call COMPILE_OBJ_CXX,$@,$<)
33 changes: 33 additions & 0 deletions src/test/run-make-fulldeps/foreign-double-unwind/foo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <cstdio>
#include <exception>

void println(const char* s) {
puts(s);
fflush(stdout);
}

struct outer_exception {};
struct inner_exception {};

extern "C" {
void throw_cxx_exception() {
if (std::uncaught_exception()) {
println("throwing inner C++ exception");
throw inner_exception();
} else {
println("throwing outer C++ exception");
throw outer_exception();
}
}

void cxx_catch_callback(void (*cb)()) {
try {
cb();
println("unreachable: callback returns");
} catch (outer_exception) {
println("unreachable: caught outer exception in catch (...)");
} catch (inner_exception) {
println("unreachable: caught inner exception in catch (...)");
}
}
}
26 changes: 26 additions & 0 deletions src/test/run-make-fulldeps/foreign-double-unwind/foo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Tests that C++ double unwinding through Rust code will be properly guarded
// against instead of exhibiting undefined behaviour.

#![feature(c_unwind)]

extern "C-unwind" {
fn throw_cxx_exception();
fn cxx_catch_callback(cb: extern "C-unwind" fn());
}

struct ThrowOnDrop;

impl Drop for ThrowOnDrop {
fn drop(&mut self) {
unsafe { throw_cxx_exception() };
}
}

extern "C-unwind" fn test_double_unwind() {
let _a = ThrowOnDrop;
let _b = ThrowOnDrop;
}

fn main() {
unsafe { cxx_catch_callback(test_double_unwind) };
}