@@ -273,11 +273,18 @@ fn test_double_ended_zip() {
273
273
274
274
#[ test]
275
275
#[ 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.
276
282
fn test_nested_zip_panic_safety ( ) {
277
283
use std:: panic:: { AssertUnwindSafe , catch_unwind, resume_unwind} ;
278
284
use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
279
285
280
286
let mut panic = true ;
287
+ // keeps track of how often element get visited, must be at most once each
281
288
let witness = [ 8 , 9 , 10 , 11 , 12 ] . map ( |i| ( i, AtomicUsize :: new ( 0 ) ) ) ;
282
289
let a = witness. as_slice ( ) . iter ( ) . map ( |e| {
283
290
e. 1 . fetch_add ( 1 , Ordering :: Relaxed ) ;
@@ -287,22 +294,37 @@ fn test_nested_zip_panic_safety() {
287
294
}
288
295
e. 0
289
296
} ) ;
297
+ // shorter than `a`, so `a` will get trimmed
290
298
let b = [ 1 , 2 , 3 , 4 ] . as_slice ( ) . iter ( ) . copied ( ) ;
299
+ // shorter still, so `ab` will get trimmed.`
291
300
let c = [ 5 , 6 , 7 ] . as_slice ( ) . iter ( ) . copied ( ) ;
292
- let ab = zip ( a, b) ;
293
301
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`.
294
306
let mut abc = zip ( ab, c) ;
295
307
296
308
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.
297
314
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.
298
317
assert_eq ! ( abc. len( ) , 2 ) ;
299
318
assert_eq ! ( abc. next( ) , Some ( ( ( 8 , 1 ) , 5 ) ) ) ;
300
319
assert_eq ! ( abc. next_back( ) , Some ( ( ( 9 , 2 ) , 6 ) ) ) ;
301
320
for ( i, ( _, w) ) in witness. iter ( ) . enumerate ( ) {
302
321
let v = w. load ( Ordering :: Relaxed ) ;
322
+ // required by TRA contract
303
323
assert ! ( v <= 1 , "expected idx {i} to be visited at most once, actual: {v}" ) ;
304
324
}
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.
306
328
assert_eq ! ( witness[ 3 ] . 1 . load( Ordering :: Relaxed ) , 0 ) ;
307
329
}
308
330
0 commit comments