@@ -62,43 +62,79 @@ impl PanicDetail {
6262 }
6363}
6464
65+ enum PanicState {
66+ /// The current thread is not in a `panic_detail::catch_unwind` call.
67+ OutsideCatchUnwind ,
68+ /// The current thread is inside a `panic_detail::catch_unwind` call, but no panic has occurred
69+ /// yet.
70+ InsideCatchUnwind ,
71+ /// The current thread panicked while inside `panic_detail::catch_unwind`, producing a
72+ /// `PanicDetail`, and is in the process of unwinding.
73+ Unwinding ( PanicDetail ) ,
74+ }
75+
6576thread_local ! {
66- static CURRENT_PANIC_DETAIL : Cell <Option <PanicDetail >> = Cell :: new( None ) ;
77+ static CURRENT_PANIC_DETAIL : Cell <PanicState > =
78+ Cell :: new( PanicState :: OutsideCatchUnwind ) ;
6779}
6880
6981/// Panic hook for use with [`std::panic::set_hook`]. This builds a `PanicDetail` for each panic
7082/// and stores it for later retrieval by [`take_current`].
71- pub fn panic_hook ( info : & PanicInfo ) {
72- let bt = Backtrace :: new ( ) ;
73- let detail = PanicDetail {
74- msg : panic_to_string ( info. payload ( ) ) ,
75- loc : info. location ( ) . map ( |l| l. to_string ( ) ) ,
76- relevant_loc : guess_relevant_loc ( & bt) ,
77- backtrace : Some ( bt) ,
78- span : CURRENT_SPAN . with ( |cell| cell. get ( ) ) ,
79- } ;
80- let old = CURRENT_PANIC_DETAIL . with ( |cell| cell. replace ( Some ( detail) ) ) ;
81- if let Some ( old) = old {
82- warn ! ( "discarding old panic detail: {:?}" , old) ;
83- }
84- }
83+ pub fn panic_hook ( default_hook : & dyn Fn ( & PanicInfo ) , info : & PanicInfo ) {
84+ CURRENT_PANIC_DETAIL . with ( |cell| {
85+ // Take the old value, replacing it with something arbitrary.
86+ let old = cell. replace ( PanicState :: OutsideCatchUnwind ) ;
87+ match old {
88+ PanicState :: OutsideCatchUnwind => {
89+ // No special behavior is needed. Call the default panic hook instead.
90+ default_hook ( info) ;
91+ return ;
92+ }
93+ PanicState :: InsideCatchUnwind => { }
94+ PanicState :: Unwinding ( pd) => {
95+ warn ! ( "discarding old panic detail: {:?}" , pd) ;
96+ }
97+ }
8598
86- /// Get the [`PanicDetail`] of the most recent panic. This clears the internal storage, so if this
87- /// is called twice in a row without an intervening panic, the second call always returns `None`.
88- fn take_current ( ) -> Option < PanicDetail > {
89- CURRENT_PANIC_DETAIL . with ( |cell| cell. take ( ) )
99+ // We are inside `panic_detail::catch_unwind`. Build a `PanicDetail` for this panic and
100+ // save it.
101+ let bt = Backtrace :: new ( ) ;
102+ let detail = PanicDetail {
103+ msg : panic_to_string ( info. payload ( ) ) ,
104+ loc : info. location ( ) . map ( |l| l. to_string ( ) ) ,
105+ relevant_loc : guess_relevant_loc ( & bt) ,
106+ backtrace : Some ( bt) ,
107+ span : CURRENT_SPAN . with ( |cell| cell. get ( ) ) ,
108+ } ;
109+ cell. set ( PanicState :: Unwinding ( detail) ) ;
110+ } ) ;
90111}
91112
92113/// Like `std::panic::catch_unwind`, but returns a `PanicDetail` instead of `Box<dyn Any>` on
93114/// panic.
94115pub fn catch_unwind < F : FnOnce ( ) -> R + UnwindSafe , R > ( f : F ) -> Result < R , PanicDetail > {
95- panic:: catch_unwind ( f) . map_err ( |e| {
96- take_current ( ) . unwrap_or_else ( || {
97- let msg = panic_to_string ( & e) ;
98- warn ! ( "missing panic detail; caught message {:?}" , msg) ;
99- PanicDetail :: new ( msg)
100- } )
101- } )
116+ let old = CURRENT_PANIC_DETAIL . with ( |cell| cell. replace ( PanicState :: InsideCatchUnwind ) ) ;
117+ let r = panic:: catch_unwind ( f) ;
118+ let new = CURRENT_PANIC_DETAIL . with ( |cell| cell. replace ( old) ) ;
119+
120+ match r {
121+ Ok ( x) => {
122+ debug_assert ! ( matches!( new, PanicState :: InsideCatchUnwind ) ) ;
123+ Ok ( x)
124+ }
125+ Err ( e) => {
126+ debug_assert ! ( !matches!( new, PanicState :: OutsideCatchUnwind ) ) ;
127+ let pd = match new {
128+ PanicState :: Unwinding ( pd) => pd,
129+ _ => {
130+ let msg = panic_to_string ( & e) ;
131+ warn ! ( "missing panic detail; caught message {:?}" , msg) ;
132+ PanicDetail :: new ( msg)
133+ }
134+ } ;
135+ Err ( pd)
136+ }
137+ }
102138}
103139
104140/// Crude heuristic to guess the first interesting location in a [`Backtrace`], skipping over
0 commit comments