-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
rustc: Tweak funclet cleanups of ffi functions #48572
Conversation
(rust_highfive has picked a reviewer for you, use r? to override) |
src/librustc_trans/mir/mod.rs
Outdated
let cs = cs_bx.catch_switch(None, None, 1); | ||
cs_bx.add_handler(cs, cp_bx.llbb()); | ||
|
||
// No idea what this null/64 are for, but it's what clang does! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pointer usually points to an RTTI type descriptor, catch(...) doesn't have a type so this is null.
As for the 64, its a bit in a bitfield describing if the catch is by reference, const, etc. In this case, 64 indicates catch-all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, thanks!
5b2244f
to
e2af240
Compare
Won't be able to review till friday.
…On Feb 27, 2018 13:08, "Alex Crichton" ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In src/librustc_trans/mir/mod.rs
<#48572 (comment)>:
> + // modeled after clang's for:
+ //
+ // try {
+ // foo();
+ // } catch (...) {
+ // bar();
+ // }
+ Some(&mir::TerminatorKind::Abort) => {
+ let cs_bx = bx.build_sibling_block(&format!("cs_funclet{:?}", bb));
+ let cp_bx = bx.build_sibling_block(&format!("cp_funclet{:?}", bb));
+ ret_llbb = cs_bx.llbb();
+
+ let cs = cs_bx.catch_switch(None, None, 1);
+ cs_bx.add_handler(cs, cp_bx.llbb());
+
+ // No idea what this null/64 are for, but it's what clang does!
Aha, thanks!
—
You are receiving this because you were assigned.
Reply to this email directly, view it on GitHub
<#48572 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AApc0kmkUkL0x5EJ-Cyj4rdTOnnb5-_Xks5tY_BLgaJpZM4SUUR0>
.
|
@bors r+ LGTM, although I'm wondering if putting the aborts in MIR (instead of the backend) was a mistake. |
📌 Commit e2af240 has been approved by |
foo(); | ||
} | ||
|
||
fn foo() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to understand how this test works. According to earlier comment, longjmp will still run destructors on MSVC. As such, A's destructor will run, causing a panic, but since we're in unwind mode, this will be a double unwind, causing an abort for that reason, or...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes that was a mistake on my part, this test was originally included in a PR where it was fixed, but I changed strategies at the last minute.
Fails on appveyor |
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.
e2af240
to
804666f
Compare
@bors: r=eddyb |
📌 Commit 804666f has been approved by |
🌲 The tree is currently closed for pull requests below priority 200, this pull request will be tested once the tree is reopened |
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.
Nominating for a backport to beta as I think this turned out to be a small enough change and this'll help re-close #48251 |
Note that this:
still breaks thread cancellation on GNU/Linux if |
@fweimer can you expand a bit on that? I wasn't aware of this problem! |
@alexcrichton Not sure where to start. I previously tried to ask about this over there: |
Indiscriminate unwinding through an FFI boundary is a very likely to end badly. There may be code on the other side written in a language that does not support unwinding (e.g. C). Unwinding through such code will leak all its resources (memory, open files, acquired mutexes, etc), even if the compiler has generated unwind tables. There are cases when you do know that it's ok to unwind through FFI, but doing so should be opt-in. We already have the |
@fweimer it sounds like pthread_cancel has problems with C++ noexcept anyway? In that sense I'd at least by default not expect pthread_cancel to work whereas longjmp/setjmp seems more standard to be expected to work at least. That's not to say we couldn't get it working, but as @vadimcn mentioned it's pretty scary to unwind without Rust exceptions so it's in general best to avoid that. |
@alexcrichton |
Er sorry I'm not really sure I understand, can you provide an example of the breakage? |
This specific PR only affects Windows, not POSIX platforms. Admittedly, there might be yet another case where on Windows unwinding is used and, unlike On POSIX, meanwhile, code like one below, is and always was undefined Rust (ignore all the bad bindings and stuff, it is something I prototyped quickly to demonstrate the point): use std::os::unix::thread::*;
extern "C" {
pub fn pthread_cancel(h: RawPthread) -> i32;
pub fn pthread_create(t: *mut RawPthread, a: *mut (), t: extern "C" fn(*const ()) -> *const (), b: *mut ()) -> i32;
}
extern "C" fn banana(x: *const ()) -> *const () {
::std::thread::sleep(::std::time::Duration::from_millis(500)); // might unwind due to pthread_cancel and is UB
x
}
fn main() {
unsafe {
let mut t: RawPthread = ::std::mem::zeroed();
pthread_create(&mut t, ::std::ptr::null_mut(), banana, ::std::ptr::null_mut());
pthread_cancel(t);
}
::std::thread::sleep(::std::time::Duration::from_millis(1000));
} Currently, the safety implemented in #46833 is disabled, so this code will silently work (it does not work when the safety net is re-enabled), however, to really make sure this code is not undefined, you need to specify the #[unwind]
extern "C" fn banana(x: *const ()) -> *const () {
::std::thread::sleep(::std::time::Duration::from_millis(500)); // might unwind due to pthread_cancel and is not UB anymore, because `banana` is specified to unwind.
x
} This is exactly the same rule, that applies to any |
We discussed in the @rust-lang/compiler meeting. We would prefer not to backport this -- hence I'm removing the label. The main reason is that there doesn't seem to be an urgency; the aborts are currently disabled, so we can just let this ride the trains as usual. |
This commit is targeted at addressing #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 #46833
extern
ABI functions inRust 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, thesame mechanism used to implement panics in Rust. This means that
longjmp
overRust 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 thatprobably 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 cleanupsfor caught exceptions, it only runs cleanups for cleanup pads. Using this
information this commit tweaks the codegen for an
extern
function toa catch-all clause for exceptions instead of a cleanup block. This catch-all is
equivalent to the C++ code:
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 #48567 and should help us stick to standard
personality functions a bit longer.