@@ -250,7 +250,23 @@ fn scrub_stack() {
250250/// Run `f` with this thread watched, and fail if any block it freed held
251251/// the marker.
252252fn watched < T > ( what : & str , f : impl FnOnce ( ) -> T ) -> T {
253- let _gate = GATE . lock ( ) . unwrap_or_else ( |poisoned| poisoned. into_inner ( ) ) ;
253+ let _gate = gate ( ) ;
254+ watched_in_gate ( what, f)
255+ }
256+
257+ /// Take [`GATE`], which serializes every watch: the thread-local flags
258+ /// keep other threads' allocations out of a watch, but [`FOUND`],
259+ /// [`UPSTREAM`], [`LIVE`] and [`TRACKING`] are shared, and a test running
260+ /// alongside on another thread (plain `cargo test`) would otherwise reset
261+ /// or switch them off mid-watch. A test that panicked holding it (a
262+ /// `should_panic` control) poisons it; the state it guards is reset on
263+ /// entry, so the poison is ignored.
264+ fn gate ( ) -> std:: sync:: MutexGuard < ' static , ( ) > {
265+ GATE . lock ( ) . unwrap_or_else ( |poisoned| poisoned. into_inner ( ) )
266+ }
267+
268+ /// [`watched`], for a caller already holding [`GATE`].
269+ fn watched_in_gate < T > ( what : & str , f : impl FnOnce ( ) -> T ) -> T {
254270 FOUND . lock ( ) . unwrap ( ) . clear ( ) ;
255271 * UPSTREAM . lock ( ) . unwrap ( ) = 0 ;
256272 scrub_stack ( ) ;
@@ -276,6 +292,9 @@ fn watched<T>(what: &str, f: impl FnOnce() -> T) -> T {
276292/// than freed, which the free-time scan cannot see. `f` returns nothing,
277293/// so nothing it built is meant to outlive it.
278294fn watched_for_leaks ( what : & str , f : impl FnOnce ( ) ) {
295+ // Held from the reset of `LIVE` to the last read of it, so no other
296+ // watch can clear this one's records or turn `TRACKING` off under it.
297+ let _gate = gate ( ) ;
279298 {
280299 let mut live = LIVE . lock ( ) . unwrap ( ) ;
281300 live. len = 0 ;
@@ -290,20 +309,27 @@ fn watched_for_leaks(what: &str, f: impl FnOnce()) {
290309 }
291310 }
292311 let stop = Stop ;
293- watched ( what, f) ;
312+ watched_in_gate ( what, f) ;
294313 drop ( stop) ;
295- let live = LIVE . lock ( ) . unwrap ( ) ;
314+ // Read under the lock and released before asserting, so a failing
315+ // assertion (the negative control's, by design) does not poison
316+ // `LIVE` for the watches that follow in the same process.
317+ let ( overflowed, leaked) = {
318+ let live = LIVE . lock ( ) . unwrap ( ) ;
319+ let leaked: Vec < usize > = live. blocks [ ..live. len ]
320+ . iter ( )
321+ // SAFETY: every recorded block is still allocated: a free
322+ // removes its record while `TRACKING` is on, and it was on
323+ // until `stop`.
324+ . filter ( |( ptr, size) | unsafe { holds_mark ( * ptr as * const u8 , * size) } )
325+ . map ( |( _, size) | * size)
326+ . collect ( ) ;
327+ ( live. overflowed , leaked)
328+ } ;
296329 assert ! (
297- !live . overflowed,
330+ !overflowed,
298331 "{what}: more than {LIVE_SLOTS} blocks were live at once; raise LIVE_SLOTS"
299332 ) ;
300- let leaked: Vec < usize > = live. blocks [ ..live. len ]
301- . iter ( )
302- // SAFETY: every recorded block is still allocated: a free removes
303- // its record while `TRACKING` is on, and it was on until `stop`.
304- . filter ( |( ptr, size) | unsafe { holds_mark ( * ptr as * const u8 , * size) } )
305- . map ( |( _, size) | * size)
306- . collect ( ) ;
307333 assert ! (
308334 leaked. is_empty( ) ,
309335 "{what}: {} heap blocks allocated during the watch are still live with plaintext in them (sizes {leaked:?})" ,
0 commit comments