Skip to content

Commit 2889139

Browse files
Rollup merge of rust-lang#65020 - pnkfelix:targetted-fix-for-always-marking-rust-abi-unwind-issue-64655, r=alexcrichton
Always mark rust and rust-call abi's as unwind PR rust-lang#63909 identified a bug that had been injected by PR rust-lang#55982. As discussed on rust-lang#64655 (comment) , we started marking extern items as nounwind, *even* extern items that said they were using "Rust" or "rust-call" ABI. This is a more targeted variant of PR rust-lang#63909 that fixes the above bug. Fix rust-lang#64655 ---- I personally suspect we will want PR rust-lang#63909 to land in the long-term But: * it is not certain that PR rust-lang#63909 *will* land, * more importantly, PR rust-lang#63909 almost certainly will not be backported to beta/stable. The identified bug was more severe than I think anyone realized (apart from perhaps @gnzlbg, as noted [here](rust-lang#63909 (comment))). Thus, I was motivated to write this PR, which fixes *just* the issue with extern rust/rust-call functions, and deliberately avoids injecting further deviation from current behavior (you can see further notes on this in the comments of the code added here).
2 parents c4ec53f + 08ec3fe commit 2889139

File tree

3 files changed

+185
-13
lines changed

3 files changed

+185
-13
lines changed

src/librustc_codegen_llvm/attributes.rs

+39-13
Original file line numberDiff line numberDiff line change
@@ -273,25 +273,51 @@ pub fn from_fn_attrs(
273273
} else if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_ALLOCATOR_NOUNWIND) {
274274
// Special attribute for allocator functions, which can't unwind
275275
false
276-
} else if let Some(id) = id {
276+
} else if let Some(_) = id {
277+
// rust-lang/rust#64655, rust-lang/rust#63909: to minimize
278+
// risk associated with changing cases where nounwind
279+
// attribute is attached, this code is deliberately mimicking
280+
// old control flow based on whether `id` is `Some` or `None`.
281+
//
282+
// However, in the long term we should either:
283+
// - fold this into final else (i.e. stop inspecting `id`)
284+
// - or, adopt Rust PR #63909.
285+
//
286+
// see also Rust RFC 2753.
287+
277288
let sig = cx.tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), &sig);
278-
if cx.tcx.is_foreign_item(id) {
279-
// Foreign items like `extern "C" { fn foo(); }` are assumed not to
280-
// unwind
281-
false
282-
} else if sig.abi != Abi::Rust && sig.abi != Abi::RustCall {
283-
// Any items defined in Rust that *don't* have the `extern` ABI are
284-
// defined to not unwind. We insert shims to abort if an unwind
285-
// happens to enforce this.
286-
false
287-
} else {
288-
// Anything else defined in Rust is assumed that it can possibly
289-
// unwind
289+
if sig.abi == Abi::Rust || sig.abi == Abi::RustCall {
290+
// Any Rust method (or `extern "Rust" fn` or `extern
291+
// "rust-call" fn`) is explicitly allowed to unwind
292+
// (unless it has no-unwind attribute, handled above).
290293
true
294+
} else {
295+
// Anything else is either:
296+
//
297+
// 1. A foreign item using a non-Rust ABI (like `extern "C" { fn foo(); }`), or
298+
//
299+
// 2. A Rust item using a non-Rust ABI (like `extern "C" fn foo() { ... }`).
300+
//
301+
// Foreign items (case 1) are assumed to not unwind; it is
302+
// UB otherwise. (At least for now; see also
303+
// rust-lang/rust#63909 and Rust RFC 2753.)
304+
//
305+
// Items defined in Rust with non-Rust ABIs (case 2) are also
306+
// not supposed to unwind. Whether this should be enforced
307+
// (versus stating it is UB) and *how* it would be enforced
308+
// is currently under discussion; see rust-lang/rust#58794.
309+
//
310+
// In either case, we mark item as explicitly nounwind.
311+
false
291312
}
292313
} else {
293314
// assume this can possibly unwind, avoiding the application of a
294315
// `nounwind` attribute below.
316+
//
317+
// (But: See comments in previous branch. Specifically, it is
318+
// unclear whether there is real value in the assumption this
319+
// can unwind. The conservatism here may just be papering over
320+
// a real problem by making some UB a bit harder to hit.)
295321
true
296322
});
297323

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// run-pass
2+
// ignore-wasm32-bare compiled with panic=abort by default
3+
4+
// rust-lang/rust#64655: with panic=unwind, a panic from a subroutine
5+
// should still run destructors as it unwinds the stack. However,
6+
// bugs with how the nounwind LLVM attribute was applied led to this
7+
// simple case being mishandled *if* you had fat LTO turned on.
8+
9+
// Unlike issue-64655-extern-rust-must-allow-unwind.rs, the issue
10+
// embodied in this test cropped up regardless of optimization level.
11+
// Therefore it seemed worthy of being enshrined as a dedicated unit
12+
// test.
13+
14+
// LTO settings cannot be combined with -C prefer-dynamic
15+
// no-prefer-dynamic
16+
17+
// The revisions just enumerate lto settings (the opt-level appeared irrelevant in practice)
18+
19+
// revisions: no thin fat
20+
//[no]compile-flags: -C lto=no
21+
//[thin]compile-flags: -C lto=thin
22+
//[fat]compile-flags: -C lto=fat
23+
24+
#![feature(core_panic)]
25+
26+
// (For some reason, reproducing the LTO issue requires pulling in std
27+
// explicitly this way.)
28+
#![no_std]
29+
extern crate std;
30+
31+
fn main() {
32+
use std::sync::atomic::{AtomicUsize, Ordering};
33+
use std::boxed::Box;
34+
35+
static SHARED: AtomicUsize = AtomicUsize::new(0);
36+
37+
assert_eq!(SHARED.fetch_add(0, Ordering::SeqCst), 0);
38+
39+
let old_hook = std::panic::take_hook();
40+
41+
std::panic::set_hook(Box::new(|_| { } )); // no-op on panic.
42+
43+
let handle = std::thread::spawn(|| {
44+
struct Droppable;
45+
impl Drop for Droppable {
46+
fn drop(&mut self) {
47+
SHARED.fetch_add(1, Ordering::SeqCst);
48+
}
49+
}
50+
51+
let _guard = Droppable;
52+
let s = "issue-64655-allow-unwind-when-calling-panic-directly.rs";
53+
core::panicking::panic(&("???", s, 17, 4));
54+
});
55+
56+
let wait = handle.join();
57+
58+
// Reinstate handler to ease observation of assertion failures.
59+
std::panic::set_hook(old_hook);
60+
61+
assert!(wait.is_err());
62+
63+
assert_eq!(SHARED.fetch_add(0, Ordering::SeqCst), 1);
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// run-pass
2+
// ignore-wasm32-bare compiled with panic=abort by default
3+
4+
// rust-lang/rust#64655: with panic=unwind, a panic from a subroutine
5+
// should still run destructors as it unwinds the stack. However,
6+
// bugs with how the nounwind LLVM attribute was applied led to this
7+
// simple case being mishandled *if* you had optimization *and* fat
8+
// LTO turned on.
9+
10+
// This test is the closest thing to a "regression test" we can do
11+
// without actually spawning subprocesses and comparing stderr
12+
// results.
13+
//
14+
// This test takes the code from the above issue and adapts it to
15+
// better fit our test infrastructure:
16+
//
17+
// * Instead of relying on `println!` to observe whether the destructor
18+
// is run, we instead run the code in a spawned thread and
19+
// communicate the destructor's operation via a synchronous atomic
20+
// in static memory.
21+
//
22+
// * To keep the output from confusing a casual user, we override the
23+
// panic hook to be a no-op (rather than printing a message to
24+
// stderr).
25+
//
26+
// (pnkfelix has confirmed by hand that these additions do not mask
27+
// the underlying bug.)
28+
29+
// LTO settings cannot be combined with -C prefer-dynamic
30+
// no-prefer-dynamic
31+
32+
// The revisions combine each lto setting with each optimization
33+
// setting; pnkfelix observed three differing behaviors at opt-levels
34+
// 0/1/2+3 for this test, so it seems prudent to be thorough.
35+
36+
// revisions: no0 no1 no2 no3 thin0 thin1 thin2 thin3 fat0 fat1 fat2 fat3
37+
38+
//[no0]compile-flags: -C opt-level=0 -C lto=no
39+
//[no1]compile-flags: -C opt-level=1 -C lto=no
40+
//[no2]compile-flags: -C opt-level=2 -C lto=no
41+
//[no3]compile-flags: -C opt-level=3 -C lto=no
42+
//[thin0]compile-flags: -C opt-level=0 -C lto=thin
43+
//[thin1]compile-flags: -C opt-level=1 -C lto=thin
44+
//[thin2]compile-flags: -C opt-level=2 -C lto=thin
45+
//[thin3]compile-flags: -C opt-level=3 -C lto=thin
46+
//[fat0]compile-flags: -C opt-level=0 -C lto=fat
47+
//[fat1]compile-flags: -C opt-level=1 -C lto=fat
48+
//[fat2]compile-flags: -C opt-level=2 -C lto=fat
49+
//[fat3]compile-flags: -C opt-level=3 -C lto=fat
50+
51+
fn main() {
52+
use std::sync::atomic::{AtomicUsize, Ordering};
53+
54+
static SHARED: AtomicUsize = AtomicUsize::new(0);
55+
56+
assert_eq!(SHARED.fetch_add(0, Ordering::SeqCst), 0);
57+
58+
let old_hook = std::panic::take_hook();
59+
60+
std::panic::set_hook(Box::new(|_| { } )); // no-op on panic.
61+
62+
let handle = std::thread::spawn(|| {
63+
struct Droppable;
64+
impl Drop for Droppable {
65+
fn drop(&mut self) {
66+
SHARED.fetch_add(1, Ordering::SeqCst);
67+
}
68+
}
69+
70+
let _guard = Droppable;
71+
None::<()>.expect("???");
72+
});
73+
74+
let wait = handle.join();
75+
76+
// reinstate handler to ease observation of assertion failures.
77+
std::panic::set_hook(old_hook);
78+
79+
assert!(wait.is_err());
80+
81+
assert_eq!(SHARED.fetch_add(0, Ordering::SeqCst), 1);
82+
}

0 commit comments

Comments
 (0)