22
33use std:: fmt;
44use std:: iter;
5- use std:: panic;
5+ use std:: panic:: { self , PanicInfo } ;
66use std:: str:: FromStr ;
77
88use proc_macro;
@@ -23,16 +23,59 @@ pub enum LexError {
2323
2424fn 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
3881fn mismatch ( ) -> ! {
0 commit comments