Skip to content

Commit 8025e15

Browse files
committed
Rollup merge of rust-lang#48572 - alexcrichton:noexcept-msvc2, r=eddyb
rustc: Tweak funclet cleanups of ffi functions This commit is targeted at addressing rust-lang#48251 by specifically fixing a case where a longjmp over Rust frames on MSVC runs cleanups, accidentally running the "abort the program" cleanup as well. Added in rust-lang#46833 `extern` ABI functions in Rust will abort the process if Rust panics, and currently this is modeled as a normal cleanup like all other destructors. Unfortunately it turns out that `longjmp` on MSVC is implemented with SEH, the same mechanism used to implement panics in Rust. This means that `longjmp` over Rust frames will run Rust cleanups (even though we don't necessarily want it to). Notably this means that if you `longjmp` over a Rust stack frame then that probably means you'll abort the program because one of the cleanups will abort the process. After some discussion on IRC it turns out that `longjmp` doesn't run cleanups for *caught* exceptions, it only runs cleanups for cleanup pads. Using this information this commit tweaks the codegen for an `extern` function to a catch-all clause for exceptions instead of a cleanup block. This catch-all is equivalent to the C++ code: try { foo(); } catch (...) { bar(); } and in fact our codegen here is designed to match exactly what clang emits for that C++ code! With this tweak a longjmp over Rust code will no longer abort the process. A longjmp will continue to "accidentally" run Rust cleanups (destructors) on MSVC. Other non-MSVC platforms will not rust destructors with a longjmp, so we'll probably still recommend "don't have destructors on the stack", but in any case this is a more surgical fix than rust-lang#48567 and should help us stick to standard personality functions a bit longer.
2 parents 75b8c10 + 804666f commit 8025e15

File tree

4 files changed

+127
-6
lines changed

4 files changed

+127
-6
lines changed

src/librustc_trans/mir/mod.rs

+54-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
use common::{C_i32, C_null};
1112
use libc::c_uint;
1213
use llvm::{self, ValueRef, BasicBlockRef};
1314
use llvm::debuginfo::DIScope;
@@ -23,6 +24,7 @@ use common::{CodegenCx, Funclet};
2324
use debuginfo::{self, declare_local, VariableAccess, VariableKind, FunctionDebugContext};
2425
use monomorphize::Instance;
2526
use abi::{ArgAttribute, FnType, PassMode};
27+
use type_::Type;
2628

2729
use syntax_pos::{DUMMY_SP, NO_EXPANSION, BytePos, Span};
2830
use syntax::symbol::keywords;
@@ -222,7 +224,7 @@ pub fn trans_mir<'a, 'tcx: 'a>(
222224

223225
// Compute debuginfo scopes from MIR scopes.
224226
let scopes = debuginfo::create_mir_scopes(cx, mir, &debug_context);
225-
let (landing_pads, funclets) = create_funclets(&bx, &cleanup_kinds, &block_bxs);
227+
let (landing_pads, funclets) = create_funclets(mir, &bx, &cleanup_kinds, &block_bxs);
226228

227229
let mut fx = FunctionCx {
228230
mir,
@@ -333,6 +335,7 @@ pub fn trans_mir<'a, 'tcx: 'a>(
333335
}
334336

335337
fn create_funclets<'a, 'tcx>(
338+
mir: &'a Mir<'tcx>,
336339
bx: &Builder<'a, 'tcx>,
337340
cleanup_kinds: &IndexVec<mir::BasicBlock, CleanupKind>,
338341
block_bxs: &IndexVec<mir::BasicBlock, BasicBlockRef>)
@@ -341,14 +344,59 @@ fn create_funclets<'a, 'tcx>(
341344
{
342345
block_bxs.iter_enumerated().zip(cleanup_kinds).map(|((bb, &llbb), cleanup_kind)| {
343346
match *cleanup_kind {
344-
CleanupKind::Funclet if base::wants_msvc_seh(bx.sess()) => {
347+
CleanupKind::Funclet if base::wants_msvc_seh(bx.sess()) => {}
348+
_ => return (None, None)
349+
}
350+
351+
let cleanup;
352+
let ret_llbb;
353+
match mir[bb].terminator.as_ref().map(|t| &t.kind) {
354+
// This is a basic block that we're aborting the program for,
355+
// notably in an `extern` function. These basic blocks are inserted
356+
// so that we assert that `extern` functions do indeed not panic,
357+
// and if they do we abort the process.
358+
//
359+
// On MSVC these are tricky though (where we're doing funclets). If
360+
// we were to do a cleanuppad (like below) the normal functions like
361+
// `longjmp` would trigger the abort logic, terminating the
362+
// program. Instead we insert the equivalent of `catch(...)` for C++
363+
// which magically doesn't trigger when `longjmp` files over this
364+
// frame.
365+
//
366+
// Lots more discussion can be found on #48251 but this codegen is
367+
// modeled after clang's for:
368+
//
369+
// try {
370+
// foo();
371+
// } catch (...) {
372+
// bar();
373+
// }
374+
Some(&mir::TerminatorKind::Abort) => {
375+
let cs_bx = bx.build_sibling_block(&format!("cs_funclet{:?}", bb));
376+
let cp_bx = bx.build_sibling_block(&format!("cp_funclet{:?}", bb));
377+
ret_llbb = cs_bx.llbb();
378+
379+
let cs = cs_bx.catch_switch(None, None, 1);
380+
cs_bx.add_handler(cs, cp_bx.llbb());
381+
382+
// The "null" here is actually a RTTI type descriptor for the
383+
// C++ personality function, but `catch (...)` has no type so
384+
// it's null. The 64 here is actually a bitfield which
385+
// represents that this is a catch-all block.
386+
let null = C_null(Type::i8p(bx.cx));
387+
let sixty_four = C_i32(bx.cx, 64);
388+
cleanup = cp_bx.catch_pad(cs, &[null, sixty_four, null]);
389+
cp_bx.br(llbb);
390+
}
391+
_ => {
345392
let cleanup_bx = bx.build_sibling_block(&format!("funclet_{:?}", bb));
346-
let cleanup = cleanup_bx.cleanup_pad(None, &[]);
393+
ret_llbb = cleanup_bx.llbb();
394+
cleanup = cleanup_bx.cleanup_pad(None, &[]);
347395
cleanup_bx.br(llbb);
348-
(Some(cleanup_bx.llbb()), Some(Funclet::new(cleanup)))
349396
}
350-
_ => (None, None)
351-
}
397+
};
398+
399+
(Some(ret_llbb), Some(Funclet::new(cleanup)))
352400
}).unzip()
353401
}
354402

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-include ../tools.mk
2+
3+
all: $(call NATIVE_STATICLIB,foo)
4+
$(RUSTC) main.rs
5+
$(call RUN,main)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#include <assert.h>
12+
#include <setjmp.h>
13+
14+
static jmp_buf ENV;
15+
16+
extern void test_middle();
17+
18+
void test_start(void(*f)()) {
19+
if (setjmp(ENV) != 0)
20+
return;
21+
f();
22+
assert(0);
23+
}
24+
25+
void test_end() {
26+
longjmp(ENV, 1);
27+
assert(0);
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#[link(name = "foo", kind = "static")]
12+
extern {
13+
fn test_start(f: extern fn());
14+
fn test_end();
15+
}
16+
17+
fn main() {
18+
unsafe {
19+
test_start(test_middle);
20+
}
21+
}
22+
23+
struct A;
24+
25+
impl Drop for A {
26+
fn drop(&mut self) {
27+
}
28+
}
29+
30+
extern fn test_middle() {
31+
let _a = A;
32+
foo();
33+
}
34+
35+
fn foo() {
36+
let _a = A;
37+
unsafe {
38+
test_end();
39+
}
40+
}

0 commit comments

Comments
 (0)