Skip to content

Commit a6d8ad3

Browse files
Rollup merge of rust-lang#154191 - pitaj:fix-154124, r=tgross35
refactor RangeFromIter overflow-checks impl Crates with different overflow-checks settings accessing the same RangeFromIter resulted in incorrect values being yielded Fixes rust-lang#154124 r? @tgross35
2 parents a2000c2 + 8befc9d commit a6d8ad3

5 files changed

Lines changed: 122 additions & 34 deletions

File tree

library/core/src/range/iter.rs

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,9 @@ range_incl_exact_iter_impl! {
298298
#[derive(Debug, Clone)]
299299
pub struct RangeFromIter<A> {
300300
start: A,
301-
/// Whether the first element of the iterator has yielded.
301+
/// Whether the maximum value of the iterator has yielded.
302302
/// Only used when overflow checks are enabled.
303-
first: bool,
303+
exhausted: bool,
304304
}
305305

306306
impl<A: Step> RangeFromIter<A> {
@@ -309,10 +309,12 @@ impl<A: Step> RangeFromIter<A> {
309309
#[rustc_inherit_overflow_checks]
310310
#[unstable(feature = "new_range_api", issue = "125687")]
311311
pub fn remainder(self) -> RangeFrom<A> {
312-
if intrinsics::overflow_checks() {
313-
if !self.first {
314-
return RangeFrom { start: Step::forward(self.start, 1) };
315-
}
312+
// Need to handle this case even if overflow-checks are disabled,
313+
// because a `RangeFromIter` could be exhausted in a crate with
314+
// overflow-checks enabled, but then passed to a crate with them
315+
// disabled before this is called.
316+
if self.exhausted {
317+
return RangeFrom { start: Step::forward(self.start, 1) };
316318
}
317319

318320
RangeFrom { start: self.start }
@@ -326,14 +328,29 @@ impl<A: Step> Iterator for RangeFromIter<A> {
326328
#[inline]
327329
#[rustc_inherit_overflow_checks]
328330
fn next(&mut self) -> Option<A> {
331+
if self.exhausted {
332+
// This should panic if overflow checks are enabled, since
333+
// `forward_checked` returned `None` in prior iteration.
334+
self.start = Step::forward(self.start.clone(), 1);
335+
336+
// If we get here, if means this iterator was exhausted by a crate
337+
// with overflow-checks enabled, but now we're iterating in a crate with
338+
// overflow-checks disabled. Since we successfully incremented `self.start`
339+
// above (in many cases this will wrap around to MIN), we now unset
340+
// the flag so we don't repeat this process in the next iteration.
341+
//
342+
// This could also happen if `forward_checked` returned None but
343+
// (for whatever reason, not applicable to any std implementors)
344+
// `forward` doesn't panic when overflow-checks are enabled. In that
345+
// case, this is also the correct behavior.
346+
self.exhausted = false;
347+
}
329348
if intrinsics::overflow_checks() {
330-
if self.first {
331-
self.first = false;
349+
let Some(n) = Step::forward_checked(self.start.clone(), 1) else {
350+
self.exhausted = true;
332351
return Some(self.start.clone());
333-
}
334-
335-
self.start = Step::forward(self.start.clone(), 1);
336-
return Some(self.start.clone());
352+
};
353+
return Some(mem::replace(&mut self.start, n));
337354
}
338355

339356
let n = Step::forward(self.start.clone(), 1);
@@ -348,18 +365,22 @@ impl<A: Step> Iterator for RangeFromIter<A> {
348365
#[inline]
349366
#[rustc_inherit_overflow_checks]
350367
fn nth(&mut self, n: usize) -> Option<A> {
368+
// Typically `forward` will cause an overflow-check panic here,
369+
// but unset the exhausted flag to handle the uncommon cases.
370+
// See the comments in `next` for more details.
371+
if self.exhausted {
372+
self.start = Step::forward(self.start.clone(), 1);
373+
self.exhausted = false;
374+
}
351375
if intrinsics::overflow_checks() {
352-
if self.first {
353-
self.first = false;
354-
355-
let plus_n = Step::forward(self.start.clone(), n);
376+
let plus_n = Step::forward(self.start.clone(), n);
377+
if let Some(plus_n1) = Step::forward_checked(plus_n.clone(), 1) {
378+
self.start = plus_n1;
379+
} else {
356380
self.start = plus_n.clone();
357-
return Some(plus_n);
381+
self.exhausted = true;
358382
}
359-
360-
let plus_n = Step::forward(self.start.clone(), n);
361-
self.start = Step::forward(plus_n.clone(), 1);
362-
return Some(self.start.clone());
383+
return Some(plus_n);
363384
}
364385

365386
let plus_n = Step::forward(self.start.clone(), n);
@@ -380,6 +401,6 @@ impl<A: Step> IntoIterator for RangeFrom<A> {
380401
type IntoIter = RangeFromIter<A>;
381402

382403
fn into_iter(self) -> Self::IntoIter {
383-
RangeFromIter { start: self.start, first: true }
404+
RangeFromIter { start: self.start, exhausted: false }
384405
}
385406
}

tests/codegen-llvm/fromrangeiter-overflow-checks.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// runtime check that panics after yielding the maximum value of the range bound type. That is
33
// tested for by tests/ui/iterators/rangefrom-overflow-overflow-checks.rs
44
//
5-
// This test ensures that such a runtime check is *not* emitted when debug-assertions are
5+
// This test ensures such runtime checks are optimized out when debug-assertions are
66
// enabled, but overflow-checks are explicitly disabled.
77

88
//@ revisions: DEBUG NOCHECKS
@@ -11,17 +11,24 @@
1111

1212
#![crate_type = "lib"]
1313
#![feature(new_range_api)]
14-
use std::range::{RangeFrom, RangeFromIter};
14+
use std::range::RangeFrom;
1515

16-
// CHECK-LABEL: @iterrangefrom_remainder(
16+
// CHECK-LABEL: @rangefrom_increments(
1717
#[no_mangle]
18-
pub unsafe fn iterrangefrom_remainder(x: RangeFromIter<i32>) -> RangeFrom<i32> {
19-
// DEBUG: i32 noundef %x
20-
// NOCHECKS: i32 noundef returned %x
21-
// DEBUG: br i1
22-
// DEBUG: call core::panicking::panic_const::panic_const_add_overflow
23-
// DEBUG: unreachable
24-
// NOCHECKS-NOT: unreachable
25-
// NOCHECKS: ret i32 %x
26-
x.remainder()
18+
pub unsafe fn rangefrom_increments(range: RangeFrom<i32>) -> RangeFrom<i32> {
19+
// Iterator is contained entirely within this function, so the optimizer should
20+
// be able to see that `exhausted` is never set and optimize out any branches.
21+
22+
// CHECK: i32 noundef %range
23+
// DEBUG: switch i32 %range
24+
// DEBUG: call core::panicking::panic_const::panic_const_add_overflow
25+
// DEBUG: unreachable
26+
// NOCHECKS-NOT: unreachable
27+
// NOCHECKS: [[REM:%[a-z_0-9.]+]] = add i32 %range, 2
28+
// NOCHECKS-NEXT: ret i32 [[REM]]
29+
30+
let mut iter = range.into_iter();
31+
let _ = iter.next();
32+
let _ = iter.next();
33+
iter.remainder()
2734
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//@ compile-flags: -C overflow-checks=no
2+
3+
#![crate_type = "lib"]
4+
#![feature(new_range_api)]
5+
6+
use std::range::RangeFromIter;
7+
8+
pub fn next(iter: &mut RangeFromIter<u8>) -> u8 {
9+
iter.next().unwrap()
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//@ compile-flags: -C overflow-checks=yes
2+
3+
#![crate_type = "lib"]
4+
#![feature(new_range_api)]
5+
6+
use std::range::RangeFromIter;
7+
8+
pub fn next(iter: &mut RangeFromIter<u8>) -> u8 {
9+
iter.next().unwrap()
10+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//@ run-pass
2+
//@ needs-unwind
3+
//@ aux-build:rangefrom-overflow-2crates-ocno.rs
4+
//@ aux-build:rangefrom-overflow-2crates-ocyes.rs
5+
6+
// For #154124
7+
// Test that two crates with different overflow-checks have the same results,
8+
// even when the iterator is passed between them.
9+
10+
#![feature(new_range_api)]
11+
12+
extern crate rangefrom_overflow_2crates_ocno;
13+
extern crate rangefrom_overflow_2crates_ocyes;
14+
15+
use rangefrom_overflow_2crates_ocno::next as next_ocno;
16+
use rangefrom_overflow_2crates_ocyes::next as next_ocyes;
17+
18+
fn main() {
19+
let mut iter_ocyes = std::range::RangeFrom::from(0_u8..).into_iter();
20+
let mut iter_ocno = iter_ocyes.clone();
21+
22+
for n in 0_u8..=255 {
23+
assert_eq!(n, next_ocno(&mut iter_ocyes.clone()));
24+
assert_eq!(n, next_ocyes(&mut iter_ocyes));
25+
assert_eq!(n, next_ocyes(&mut iter_ocno.clone()));
26+
assert_eq!(n, next_ocno(&mut iter_ocno));
27+
}
28+
29+
// `iter_ocno` should have wrapped
30+
assert_eq!(0, next_ocyes(&mut iter_ocno.clone()));
31+
assert_eq!(0, next_ocno(&mut iter_ocno));
32+
// `iter_ocyes` should be exhausted,
33+
// which will wrap when called without overflow-checks
34+
assert_eq!(0, next_ocno(&mut iter_ocyes.clone()));
35+
// and panic when called with overflow-checks
36+
let r = std::panic::catch_unwind(move || {
37+
let _ = next_ocyes(&mut iter_ocyes);
38+
});
39+
assert!(r.is_err());
40+
}

0 commit comments

Comments
 (0)