Skip to content

Commit 311f788

Browse files
authored
Merge pull request dtolnay#123 from dtolnay/panic
Hide panic output in nightly_works
2 parents 2ff99ce + 78ef773 commit 311f788

File tree

1 file changed

+47
-4
lines changed

1 file changed

+47
-4
lines changed

src/unstable.rs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use std::fmt;
44
use std::iter;
5-
use std::panic;
5+
use std::panic::{self, PanicInfo};
66
use std::str::FromStr;
77

88
use proc_macro;
@@ -23,16 +23,59 @@ pub enum LexError {
2323

2424
fn nightly_works() -> bool {
2525
use std::sync::atomic::*;
26+
use std::sync::Once;
27+
2628
static WORKS: AtomicUsize = ATOMIC_USIZE_INIT;
29+
static INIT: Once = Once::new();
2730

2831
match WORKS.load(Ordering::SeqCst) {
2932
1 => return false,
3033
2 => return true,
3134
_ => {}
3235
}
33-
let works = panic::catch_unwind(|| proc_macro::Span::call_site()).is_ok();
34-
WORKS.store(works as usize + 1, Ordering::SeqCst);
35-
works
36+
37+
// Swap in a null panic hook to avoid printing "thread panicked" to stderr,
38+
// then use catch_unwind to determine whether the compiler's proc_macro is
39+
// working. When proc-macro2 is used from outside of a procedural macro all
40+
// of the proc_macro crate's APIs currently panic.
41+
//
42+
// The Once is to prevent the possibility of this ordering:
43+
//
44+
// thread 1 calls take_hook, gets the user's original hook
45+
// thread 1 calls set_hook with the null hook
46+
// thread 2 calls take_hook, thinks null hook is the original hook
47+
// thread 2 calls set_hook with the null hook
48+
// thread 1 calls set_hook with the actual original hook
49+
// thread 2 calls set_hook with what it thinks is the original hook
50+
//
51+
// in which the user's hook has been lost.
52+
//
53+
// There is still a race condition where a panic in a different thread can
54+
// happen during the interval that the user's original panic hook is
55+
// unregistered such that their hook is incorrectly not called. This is
56+
// sufficiently unlikely and less bad than printing panic messages to stderr
57+
// on correct use of this crate. Maybe there is a libstd feature request
58+
// here. For now, if a user needs to guarantee that this failure mode does
59+
// not occur, they need to call e.g. `proc_macro2::Span::call_site()` from
60+
// the main thread before launching any other threads.
61+
INIT.call_once(|| {
62+
type PanicHook = Fn(&PanicInfo) + Sync + Send + 'static;
63+
64+
let null_hook: Box<PanicHook> = Box::new(|_panic_info| { /* ignore */ });
65+
let sanity_check = &*null_hook as *const PanicHook;
66+
let original_hook = panic::take_hook();
67+
panic::set_hook(null_hook);
68+
69+
let works = panic::catch_unwind(|| proc_macro::Span::call_site()).is_ok();
70+
WORKS.store(works as usize + 1, Ordering::SeqCst);
71+
72+
let hopefully_null_hook = panic::take_hook();
73+
panic::set_hook(original_hook);
74+
if sanity_check != &*hopefully_null_hook {
75+
panic!("observed race condition in proc_macro2::nightly_works");
76+
}
77+
});
78+
nightly_works()
3679
}
3780

3881
fn mismatch() -> ! {

0 commit comments

Comments
 (0)