@@ -273,11 +273,18 @@ fn test_double_ended_zip() {
273273
274274#[ test]
275275#[ cfg( panic = "unwind" ) ]
276+ /// Regresion test for #137255
277+ /// A previous implementation of Zip TrustedRandomAccess specializations tried to do a lot of work
278+ /// to preserve side-effects of equalizing the iterator lengths during backwards iteration.
279+ /// This lead to several cases of unsoundness, twice due to being left in an inconsistent state
280+ /// after panics.
281+ /// The new implementation does not try as hard, but we still need panic-safety.
276282fn test_nested_zip_panic_safety ( ) {
277283 use std:: panic:: { AssertUnwindSafe , catch_unwind, resume_unwind} ;
278284 use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
279285
280286 let mut panic = true ;
287+ // keeps track of how often element get visited, must be at most once each
281288 let witness = [ 8 , 9 , 10 , 11 , 12 ] . map ( |i| ( i, AtomicUsize :: new ( 0 ) ) ) ;
282289 let a = witness. as_slice ( ) . iter ( ) . map ( |e| {
283290 e. 1 . fetch_add ( 1 , Ordering :: Relaxed ) ;
@@ -287,22 +294,37 @@ fn test_nested_zip_panic_safety() {
287294 }
288295 e. 0
289296 } ) ;
297+ // shorter than `a`, so `a` will get trimmed
290298 let b = [ 1 , 2 , 3 , 4 ] . as_slice ( ) . iter ( ) . copied ( ) ;
299+ // shorter still, so `ab` will get trimmed.`
291300 let c = [ 5 , 6 , 7 ] . as_slice ( ) . iter ( ) . copied ( ) ;
292- let ab = zip ( a, b) ;
293301
302+ // This will panic during backwards trimming.
303+ let ab = zip ( a, b) ;
304+ // This being Zip + TrustedRandomAccess means it will only call `next_back``
305+ // during trimming and otherwise do calls `__iterator_get_unchecked` on `ab`.
294306 let mut abc = zip ( ab, c) ;
295307
296308 assert_eq ! ( abc. len( ) , 3 ) ;
309+ // This will first trigger backwards trimming before it would normally obtain the
310+ // actual element if it weren't for the panic.
311+ // This used to corrupt the internal state of `abc`, which then lead to
312+ // TrustedRandomAccess safety contract violations in calls to `ab`,
313+ // which ultimately lead to UB.
297314 catch_unwind ( AssertUnwindSafe ( || abc. next_back ( ) ) ) . ok ( ) ;
315+ // check for sane outward behavior after the panic, which indicates a sane internal state.
316+ // Technically these outcomes are not required because a panic frees us from correctness obligations.
298317 assert_eq ! ( abc. len( ) , 2 ) ;
299318 assert_eq ! ( abc. next( ) , Some ( ( ( 8 , 1 ) , 5 ) ) ) ;
300319 assert_eq ! ( abc. next_back( ) , Some ( ( ( 9 , 2 ) , 6 ) ) ) ;
301320 for ( i, ( _, w) ) in witness. iter ( ) . enumerate ( ) {
302321 let v = w. load ( Ordering :: Relaxed ) ;
322+ // required by TRA contract
303323 assert ! ( v <= 1 , "expected idx {i} to be visited at most once, actual: {v}" ) ;
304324 }
305- // backwards trimming panicked and should only run once, so this one won't be visited.
325+ // Trimming panicked and should only run once, so this one won't be visited.
326+ // Implementation detail, but not trying to run it again is what keeps
327+ // things simple.
306328 assert_eq ! ( witness[ 3 ] . 1 . load( Ordering :: Relaxed ) , 0 ) ;
307329}
308330
0 commit comments