2
2
3
3
use std:: fmt;
4
4
use std:: iter;
5
- use std:: panic;
5
+ use std:: panic:: { self , PanicInfo } ;
6
6
use std:: str:: FromStr ;
7
7
8
8
use proc_macro;
@@ -23,16 +23,59 @@ pub enum LexError {
23
23
24
24
fn nightly_works ( ) -> bool {
25
25
use std:: sync:: atomic:: * ;
26
+ use std:: sync:: Once ;
27
+
26
28
static WORKS : AtomicUsize = ATOMIC_USIZE_INIT ;
29
+ static INIT : Once = Once :: new ( ) ;
27
30
28
31
match WORKS . load ( Ordering :: SeqCst ) {
29
32
1 => return false ,
30
33
2 => return true ,
31
34
_ => { }
32
35
}
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 ( )
36
79
}
37
80
38
81
fn mismatch ( ) -> ! {
0 commit comments