@@ -213,6 +213,151 @@ fn test_into_iter_drops() {
213213 assert_eq ! ( i. get( ) , 5 ) ;
214214}
215215
216+ // Targeted reproduction attempt for the Gemini audit's "slice fabrication over
217+ // partially-moved arrays" finding. The claim: every `get_unchecked(index..index_back)`
218+ // in the iterator auto-derefs through the whole-array `slice::from_raw_parts` over
219+ // `0..N`, and once leading/trailing elements have been moved out, forming that
220+ // whole-array reference is claimed to be UB.
221+ //
222+ // To give that claim the strongest possible chance to fire under Miri, the element
223+ // type is `Niche(NonZeroU32)`: a moved-out slot left as a zeroed bit pattern would be
224+ // a *validity-invalid* `NonZeroU32`, not merely uninitialized - so a reference spanning
225+ // it would be UB that Tree Borrows + validity checking must catch. A `Cell`-backed Drop
226+ // counter additionally proves exactly-once drop accounting through each path.
227+ //
228+ // The helpers below exercise *every* slice-forming call site the report named
229+ // (`as_slice`, `as_mut_slice`, `next`, `nth`, `next_back`, `nth_back`, `fold`, `rfold`,
230+ // `Drop`, and `Clone`) while the backing array is in a partially-moved state.
231+ //
232+ // Run under both aliasing models to adjudicate:
233+ // cargo +nightly miri test --test iter partial_move
234+ // MIRIFLAGS="-Zmiri-tree-borrows" cargo +nightly miri test --test iter partial_move
235+
236+ use std:: num:: NonZeroU32 ;
237+
238+ struct Niche < ' a > ( NonZeroU32 , & ' a Cell < u32 > ) ;
239+
240+ impl Clone for Niche < ' _ > {
241+ fn clone ( & self ) -> Self {
242+ Niche ( self . 0 , self . 1 )
243+ }
244+ }
245+
246+ impl Drop for Niche < ' _ > {
247+ fn drop ( & mut self ) {
248+ // Touch the value so a moved-out/zeroed slot is observable as a validity bug,
249+ // and count the drop so callers can assert exactly-once semantics.
250+ assert ! ( self . 0 . get( ) != 0 , "dropped a moved-out / invalid Niche" ) ;
251+ self . 1 . set ( self . 1 . get ( ) + 1 ) ;
252+ }
253+ }
254+
255+ // Build a fresh 5-element iterator whose elements all count drops into `c`.
256+ fn mk_iter ( c : & Cell < u32 > ) -> generic_array:: GenericArrayIter < Niche < ' _ > , U5 > {
257+ GenericArray :: < Niche , U5 > :: from_iter (
258+ ( 1 ..=5 ) . map ( |n| Niche ( NonZeroU32 :: new ( n) . unwrap ( ) , c) ) ,
259+ )
260+ . into_iter ( )
261+ }
262+
263+ #[ test]
264+ fn test_partial_move_as_slice_both_ends ( ) {
265+ let c = Cell :: new ( 0 ) ;
266+ {
267+ let mut iter = mk_iter ( & c) ;
268+ let _front = iter. next ( ) . unwrap ( ) ; // index advances; slot 0 moved out
269+ let _back = iter. next_back ( ) . unwrap ( ) ; // index_back retreats; slot 4 moved out
270+ // as_slice / as_mut_slice now form the whole-array ref with slots 0 and 4 dead.
271+ assert_eq ! ( iter. as_slice( ) . len( ) , 3 ) ;
272+ for n in iter. as_mut_slice ( ) {
273+ assert ! ( n. 0 . get( ) != 0 ) ;
274+ }
275+ // Debug also routes through as_slice().
276+ let _ = format ! ( "{:?} {:?}" , iter. as_slice( ) . len( ) , c. get( ) ) ;
277+ drop ( iter) ; // Drop forms the remaining [1..4] slice and drop_in_place's it.
278+ }
279+ assert_eq ! ( c. get( ) , 5 , "all 5 elements dropped exactly once" ) ;
280+ }
281+
282+ #[ test]
283+ fn test_partial_move_drain_to_empty ( ) {
284+ // Every slot moved out before Drop: Drop must form a zero-length slice, not touch
285+ // any of the (now invalid) backing memory.
286+ let c = Cell :: new ( 0 ) ;
287+ {
288+ let mut iter = mk_iter ( & c) ;
289+ while iter. next ( ) . is_some ( ) { }
290+ assert_eq ! ( iter. as_slice( ) . len( ) , 0 ) ;
291+ drop ( iter) ;
292+ }
293+ assert_eq ! ( c. get( ) , 5 ) ;
294+ }
295+
296+ #[ test]
297+ fn test_partial_move_nth_and_nth_back ( ) {
298+ // nth() drop_in_place's the skipped prefix slice, then next() reads through the
299+ // whole-array deref; nth_back() does the mirror on the suffix.
300+ let c = Cell :: new ( 0 ) ;
301+ {
302+ let mut iter = mk_iter ( & c) ;
303+ let _ = iter. nth ( 1 ) . unwrap ( ) ; // drops slots [0..1], returns slot 1
304+ let _ = iter. nth_back ( 1 ) . unwrap ( ) ; // drops slots [4..5)->[3..4], returns slot 3
305+ assert_eq ! ( iter. as_slice( ) . len( ) , 1 ) ; // only slot 2 remains live
306+ drop ( iter) ;
307+ }
308+ assert_eq ! ( c. get( ) , 5 ) ;
309+ }
310+
311+ #[ test]
312+ fn test_partial_move_fold_after_consume ( ) {
313+ // fold() forms get_unchecked(index..index_back) and ptr::reads through it while
314+ // mutating the index, with both outer ends already moved out.
315+ let c = Cell :: new ( 0 ) ;
316+ {
317+ let mut iter = mk_iter ( & c) ;
318+ let _ = iter. next ( ) . unwrap ( ) ;
319+ let _ = iter. next_back ( ) . unwrap ( ) ;
320+ let sum = iter. fold ( 0u32 , |acc, n| acc + n. 0 . get ( ) ) ;
321+ assert_eq ! ( sum, 2 + 3 + 4 ) ;
322+ }
323+ assert_eq ! ( c. get( ) , 5 ) ;
324+ }
325+
326+ #[ test]
327+ fn test_partial_move_rfold_after_consume ( ) {
328+ let c = Cell :: new ( 0 ) ;
329+ {
330+ let mut iter = mk_iter ( & c) ;
331+ let _ = iter. next ( ) . unwrap ( ) ;
332+ let _ = iter. next_back ( ) . unwrap ( ) ;
333+ let sum = iter. rfold ( 0u32 , |acc, n| acc + n. 0 . get ( ) ) ;
334+ assert_eq ! ( sum, 2 + 3 + 4 ) ;
335+ }
336+ assert_eq ! ( c. get( ) , 5 ) ;
337+ }
338+
339+ #[ test]
340+ fn test_partial_move_clone_after_consume ( ) {
341+ // Clone is the spiciest path: it ptr::read's the *entire* partially-moved backing
342+ // array (bitwise) into a new iter, then writes clones into the live prefix via
343+ // as_mut_slice(). If forming a ref over moved-out slots were UB, this is where it
344+ // would bite hardest.
345+ let c = Cell :: new ( 0 ) ;
346+ {
347+ let mut iter = mk_iter ( & c) ;
348+ let _ = iter. next ( ) . unwrap ( ) ; // slot 0 dead
349+ let _ = iter. next_back ( ) . unwrap ( ) ; // slot 4 dead
350+ let cloned = iter. clone ( ) ; // bitwise-copies [_, 2, 3, 4, _], clones live 2,3,4
351+ assert_eq ! ( cloned. as_slice( ) . len( ) , 3 ) ;
352+ // Iterator::map (lazy) over the cloned by-value iter, consuming the 3 clones.
353+ let s: u32 = cloned. map ( |n| n. 0 . get ( ) ) . sum ( ) ;
354+ assert_eq ! ( s, 2 + 3 + 4 ) ;
355+ drop ( iter) ;
356+ }
357+ // 5 originals + 3 clones = 8 drops.
358+ assert_eq ! ( c. get( ) , 8 ) ;
359+ }
360+
216361#[ test]
217362fn test_from_failing_iter ( ) {
218363 let res: Result < GenericArray < _ , U5 > , ( ) > = GenericArray :: from_fallible_iter (
0 commit comments