@@ -107,9 +107,19 @@ fn test_zip_next_back_side_effects_exhausted() {
107107 iter. next ( ) ;
108108 iter. next ( ) ;
109109 iter. next ( ) ;
110- iter. next ( ) ;
110+ assert_eq ! ( iter. next( ) , None ) ;
111111 assert_eq ! ( iter. next_back( ) , None ) ;
112- assert_eq ! ( a, vec![ 1 , 2 , 3 , 4 , 6 , 5 ] ) ;
112+
113+ assert ! ( a. starts_with( & [ 1 , 2 , 3 ] ) ) ;
114+ let a_len = a. len ( ) ;
115+ // Tail-side-effects of forward-iteration are "at most one" per next().
116+ // And for reverse iteration we don't guarantee much either.
117+ // But we can put some bounds on the possible behaviors.
118+ assert ! ( a_len <= 6 ) ;
119+ assert ! ( a_len >= 3 ) ;
120+ a. sort ( ) ;
121+ assert_eq ! ( a, & [ 1 , 2 , 3 , 4 , 5 , 6 ] [ ..a. len( ) ] ) ;
122+
113123 assert_eq ! ( b, vec![ 200 , 300 , 400 ] ) ;
114124}
115125
@@ -120,7 +130,8 @@ fn test_zip_cloned_sideffectful() {
120130
121131 for _ in xs. iter ( ) . cloned ( ) . zip ( ys. iter ( ) . cloned ( ) ) { }
122132
123- assert_eq ! ( & xs, & [ 1 , 1 , 1 , 0 ] [ ..] ) ;
133+ // Zip documentation permits either case.
134+ assert ! ( [ & [ 1 , 1 , 1 , 0 ] , & [ 1 , 1 , 0 , 0 ] ] . iter( ) . any( |v| & xs == * v) ) ;
124135 assert_eq ! ( & ys, & [ 1 , 1 ] [ ..] ) ;
125136
126137 let xs = [ CountClone :: new ( ) , CountClone :: new ( ) ] ;
@@ -139,7 +150,8 @@ fn test_zip_map_sideffectful() {
139150
140151 for _ in xs. iter_mut ( ) . map ( |x| * x += 1 ) . zip ( ys. iter_mut ( ) . map ( |y| * y += 1 ) ) { }
141152
142- assert_eq ! ( & xs, & [ 1 , 1 , 1 , 1 , 1 , 0 ] ) ;
153+ // Zip documentation permits either case.
154+ assert ! ( [ & [ 1 , 1 , 1 , 1 , 1 , 0 ] , & [ 1 , 1 , 1 , 1 , 0 , 0 ] ] . iter( ) . any( |v| & xs == * v) ) ;
143155 assert_eq ! ( & ys, & [ 1 , 1 , 1 , 1 ] ) ;
144156
145157 let mut xs = [ 0 ; 4 ] ;
@@ -168,7 +180,8 @@ fn test_zip_map_rev_sideffectful() {
168180
169181 {
170182 let mut it = xs. iter_mut ( ) . map ( |x| * x += 1 ) . zip ( ys. iter_mut ( ) . map ( |y| * y += 1 ) ) ;
171- ( & mut it) . take ( 5 ) . count ( ) ;
183+ // the current impl only trims the tails if the iterator isn't exhausted
184+ ( & mut it) . take ( 3 ) . count ( ) ;
172185 it. next_back ( ) ;
173186 }
174187 assert_eq ! ( & xs, & [ 1 , 1 , 1 , 1 , 1 , 1 ] ) ;
@@ -211,9 +224,18 @@ fn test_zip_nth_back_side_effects_exhausted() {
211224 iter. next ( ) ;
212225 iter. next ( ) ;
213226 iter. next ( ) ;
214- iter. next ( ) ;
227+ assert_eq ! ( iter. next( ) , None ) ;
215228 assert_eq ! ( iter. nth_back( 0 ) , None ) ;
216- assert_eq ! ( a, vec![ 1 , 2 , 3 , 4 , 6 , 5 ] ) ;
229+ assert ! ( a. starts_with( & [ 1 , 2 , 3 ] ) ) ;
230+ let a_len = a. len ( ) ;
231+ // Tail-side-effects of forward-iteration are "at most one" per next().
232+ // And for reverse iteration we don't guarantee much either.
233+ // But we can put some bounds on the possible behaviors.
234+ assert ! ( a_len <= 6 ) ;
235+ assert ! ( a_len >= 3 ) ;
236+ a. sort ( ) ;
237+ assert_eq ! ( a, & [ 1 , 2 , 3 , 4 , 5 , 6 ] [ ..a. len( ) ] ) ;
238+
217239 assert_eq ! ( b, vec![ 200 , 300 , 400 ] ) ;
218240}
219241
@@ -237,32 +259,6 @@ fn test_zip_trusted_random_access_composition() {
237259 assert_eq ! ( z2. next( ) . unwrap( ) , ( ( 1 , 1 ) , 1 ) ) ;
238260}
239261
240- #[ test]
241- #[ cfg( panic = "unwind" ) ]
242- fn test_zip_trusted_random_access_next_back_drop ( ) {
243- use std:: panic:: { AssertUnwindSafe , catch_unwind} ;
244-
245- let mut counter = 0 ;
246-
247- let it = [ 42 ] . iter ( ) . map ( |e| {
248- let c = counter;
249- counter += 1 ;
250- if c == 0 {
251- panic ! ( "bomb" ) ;
252- }
253-
254- e
255- } ) ;
256- let it2 = [ ( ) ; 0 ] . iter ( ) ;
257- let mut zip = it. zip ( it2) ;
258- catch_unwind ( AssertUnwindSafe ( || {
259- zip. next_back ( ) ;
260- } ) )
261- . unwrap_err ( ) ;
262- assert ! ( zip. next( ) . is_none( ) ) ;
263- assert_eq ! ( counter, 1 ) ;
264- }
265-
266262#[ test]
267263fn test_double_ended_zip ( ) {
268264 let xs = [ 1 , 2 , 3 , 4 , 5 , 6 ] ;
@@ -275,6 +271,63 @@ fn test_double_ended_zip() {
275271 assert_eq ! ( it. next( ) , None ) ;
276272}
277273
274+ #[ test]
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.
282+ fn test_nested_zip_panic_safety ( ) {
283+ use std:: panic:: { AssertUnwindSafe , catch_unwind, resume_unwind} ;
284+ use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
285+
286+ let mut panic = true ;
287+ // keeps track of how often element get visited, must be at most once each
288+ let witness = [ 8 , 9 , 10 , 11 , 12 ] . map ( |i| ( i, AtomicUsize :: new ( 0 ) ) ) ;
289+ let a = witness. as_slice ( ) . iter ( ) . map ( |e| {
290+ e. 1 . fetch_add ( 1 , Ordering :: Relaxed ) ;
291+ if panic {
292+ panic = false ;
293+ resume_unwind ( Box :: new ( ( ) ) )
294+ }
295+ e. 0
296+ } ) ;
297+ // shorter than `a`, so `a` will get trimmed
298+ let b = [ 1 , 2 , 3 , 4 ] . as_slice ( ) . iter ( ) . copied ( ) ;
299+ // shorter still, so `ab` will get trimmed.`
300+ let c = [ 5 , 6 , 7 ] . as_slice ( ) . iter ( ) . copied ( ) ;
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`.
306+ let mut abc = zip ( ab, c) ;
307+
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.
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.
317+ assert_eq ! ( abc. len( ) , 2 ) ;
318+ assert_eq ! ( abc. next( ) , Some ( ( ( 8 , 1 ) , 5 ) ) ) ;
319+ assert_eq ! ( abc. next_back( ) , Some ( ( ( 9 , 2 ) , 6 ) ) ) ;
320+ for ( i, ( _, w) ) in witness. iter ( ) . enumerate ( ) {
321+ let v = w. load ( Ordering :: Relaxed ) ;
322+ // required by TRA contract
323+ assert ! ( v <= 1 , "expected idx {i} to be visited at most once, actual: {v}" ) ;
324+ }
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.
328+ assert_eq ! ( witness[ 3 ] . 1 . load( Ordering :: Relaxed ) , 0 ) ;
329+ }
330+
278331#[ test]
279332fn test_issue_82282 ( ) {
280333 fn overflowed_zip ( arr : & [ i32 ] ) -> impl Iterator < Item = ( i32 , & ( ) ) > {
0 commit comments