Skip to content

Commit e6ec0d1

Browse files
committed
Auto merge of #68835 - CAD97:sound-range-inclusive, r=Mark-Simulacrum
Remove problematic specialization from RangeInclusive Fixes #67194 using the approach [outlined by Mark-Simulacrum](#67194 (comment)). > I believe the property we want is that if `PartialEq(&self, &other) == true`, then `self.next() == other.next()`. It is true that this is satisfied by removing the specialization and always doing `is_empty.unwrap_or_default()`; the "wrong" behavior there arises from calling `next()` having an effect on initially empty ranges, as we should be in `is_empty = true` but are not (yet) there. It might be possible to detect that the current state is always empty (i.e., `start > end`) and then not fill in the empty slot. I think this might solve the problem without regressing tests; however, this could have performance implications. > That approach essentially states that we only use the `is_empty` slot for cases where `start <= end`. That means that `Idx: !Step` and `start > end` would both behave the same, and correctly -- we do not need the boolean if we're not ever going to emit any values from the iterator. This is implemented here by replacing the `is_empty: Option<bool>` slot with an `exhausted: bool` slot. This flag is - `false` upon construction, - `false` when iteration has not yielded an element -- importantly, this means it is always `false` for an iterator empty by construction, - `false` when iteration has yielded an element and the iterator is not exhausted, and - only `true` when iteration has been used to exhaust the iterator. For completeness, this also adds a note to the `Debug` representation to note when the range is exhausted.
2 parents 4d1241f + 136008c commit e6ec0d1

File tree

3 files changed

+44
-69
lines changed

3 files changed

+44
-69
lines changed

src/libcore/iter/range.rs

+12-22
Original file line numberDiff line numberDiff line change
@@ -341,16 +341,15 @@ impl<A: Step> Iterator for ops::RangeInclusive<A> {
341341

342342
#[inline]
343343
fn next(&mut self) -> Option<A> {
344-
self.compute_is_empty();
345-
if self.is_empty.unwrap_or_default() {
344+
if self.is_empty() {
346345
return None;
347346
}
348347
let is_iterating = self.start < self.end;
349-
self.is_empty = Some(!is_iterating);
350348
Some(if is_iterating {
351349
let n = self.start.add_one();
352350
mem::replace(&mut self.start, n)
353351
} else {
352+
self.exhausted = true;
354353
self.start.clone()
355354
})
356355
}
@@ -369,8 +368,7 @@ impl<A: Step> Iterator for ops::RangeInclusive<A> {
369368

370369
#[inline]
371370
fn nth(&mut self, n: usize) -> Option<A> {
372-
self.compute_is_empty();
373-
if self.is_empty.unwrap_or_default() {
371+
if self.is_empty() {
374372
return None;
375373
}
376374

@@ -379,21 +377,20 @@ impl<A: Step> Iterator for ops::RangeInclusive<A> {
379377

380378
match plus_n.partial_cmp(&self.end) {
381379
Some(Less) => {
382-
self.is_empty = Some(false);
383380
self.start = plus_n.add_one();
384381
return Some(plus_n);
385382
}
386383
Some(Equal) => {
387-
self.is_empty = Some(true);
388384
self.start = plus_n.clone();
385+
self.exhausted = true;
389386
return Some(plus_n);
390387
}
391388
_ => {}
392389
}
393390
}
394391

395392
self.start = self.end.clone();
396-
self.is_empty = Some(true);
393+
self.exhausted = true;
397394
None
398395
}
399396

@@ -404,8 +401,6 @@ impl<A: Step> Iterator for ops::RangeInclusive<A> {
404401
F: FnMut(B, Self::Item) -> R,
405402
R: Try<Ok = B>,
406403
{
407-
self.compute_is_empty();
408-
409404
if self.is_empty() {
410405
return Try::from_ok(init);
411406
}
@@ -418,7 +413,7 @@ impl<A: Step> Iterator for ops::RangeInclusive<A> {
418413
accum = f(accum, n)?;
419414
}
420415

421-
self.is_empty = Some(true);
416+
self.exhausted = true;
422417

423418
if self.start == self.end {
424419
accum = f(accum, self.start.clone())?;
@@ -447,24 +442,22 @@ impl<A: Step> Iterator for ops::RangeInclusive<A> {
447442
impl<A: Step> DoubleEndedIterator for ops::RangeInclusive<A> {
448443
#[inline]
449444
fn next_back(&mut self) -> Option<A> {
450-
self.compute_is_empty();
451-
if self.is_empty.unwrap_or_default() {
445+
if self.is_empty() {
452446
return None;
453447
}
454448
let is_iterating = self.start < self.end;
455-
self.is_empty = Some(!is_iterating);
456449
Some(if is_iterating {
457450
let n = self.end.sub_one();
458451
mem::replace(&mut self.end, n)
459452
} else {
453+
self.exhausted = true;
460454
self.end.clone()
461455
})
462456
}
463457

464458
#[inline]
465459
fn nth_back(&mut self, n: usize) -> Option<A> {
466-
self.compute_is_empty();
467-
if self.is_empty.unwrap_or_default() {
460+
if self.is_empty() {
468461
return None;
469462
}
470463

@@ -473,21 +466,20 @@ impl<A: Step> DoubleEndedIterator for ops::RangeInclusive<A> {
473466

474467
match minus_n.partial_cmp(&self.start) {
475468
Some(Greater) => {
476-
self.is_empty = Some(false);
477469
self.end = minus_n.sub_one();
478470
return Some(minus_n);
479471
}
480472
Some(Equal) => {
481-
self.is_empty = Some(true);
482473
self.end = minus_n.clone();
474+
self.exhausted = true;
483475
return Some(minus_n);
484476
}
485477
_ => {}
486478
}
487479
}
488480

489481
self.end = self.start.clone();
490-
self.is_empty = Some(true);
482+
self.exhausted = true;
491483
None
492484
}
493485

@@ -498,8 +490,6 @@ impl<A: Step> DoubleEndedIterator for ops::RangeInclusive<A> {
498490
F: FnMut(B, Self::Item) -> R,
499491
R: Try<Ok = B>,
500492
{
501-
self.compute_is_empty();
502-
503493
if self.is_empty() {
504494
return Try::from_ok(init);
505495
}
@@ -512,7 +502,7 @@ impl<A: Step> DoubleEndedIterator for ops::RangeInclusive<A> {
512502
accum = f(accum, n)?;
513503
}
514504

515-
self.is_empty = Some(true);
505+
self.exhausted = true;
516506

517507
if self.start == self.end {
518508
accum = f(accum, self.start.clone())?;

src/libcore/ops/range.rs

+14-31
Original file line numberDiff line numberDiff line change
@@ -340,24 +340,21 @@ pub struct RangeInclusive<Idx> {
340340
// support that mode.
341341
pub(crate) start: Idx,
342342
pub(crate) end: Idx,
343-
pub(crate) is_empty: Option<bool>,
343+
344344
// This field is:
345-
// - `None` when next() or next_back() was never called
346-
// - `Some(false)` when `start < end`
347-
// - `Some(true)` when `end < start`
348-
// - `Some(false)` when `start == end` and the range hasn't yet completed iteration
349-
// - `Some(true)` when `start == end` and the range has completed iteration
350-
// The field cannot be a simple `bool` because the `..=` constructor can
351-
// accept non-PartialOrd types, also we want the constructor to be const.
345+
// - `false` upon construction
346+
// - `false` when iteration has yielded an element and the iterator is not exhausted
347+
// - `true` when iteration has been used to exhaust the iterator
348+
//
349+
// This is required to support PartialEq and Hash without a PartialOrd bound or specialization.
350+
pub(crate) exhausted: bool,
352351
}
353352

354353
#[stable(feature = "inclusive_range", since = "1.26.0")]
355354
impl<Idx: PartialEq> PartialEq for RangeInclusive<Idx> {
356355
#[inline]
357356
fn eq(&self, other: &Self) -> bool {
358-
self.start == other.start
359-
&& self.end == other.end
360-
&& self.is_exhausted() == other.is_exhausted()
357+
self.start == other.start && self.end == other.end && self.exhausted == other.exhausted
361358
}
362359
}
363360

@@ -369,8 +366,7 @@ impl<Idx: Hash> Hash for RangeInclusive<Idx> {
369366
fn hash<H: Hasher>(&self, state: &mut H) {
370367
self.start.hash(state);
371368
self.end.hash(state);
372-
// Ideally we would hash `is_exhausted` here as well, but there's no
373-
// way for us to call it.
369+
self.exhausted.hash(state);
374370
}
375371
}
376372

@@ -389,7 +385,7 @@ impl<Idx> RangeInclusive<Idx> {
389385
#[rustc_promotable]
390386
#[rustc_const_stable(feature = "const_range_new", since = "1.32.0")]
391387
pub const fn new(start: Idx, end: Idx) -> Self {
392-
Self { start, end, is_empty: None }
388+
Self { start, end, exhausted: false }
393389
}
394390

395391
/// Returns the lower bound of the range (inclusive).
@@ -465,18 +461,13 @@ impl<Idx: fmt::Debug> fmt::Debug for RangeInclusive<Idx> {
465461
self.start.fmt(fmt)?;
466462
write!(fmt, "..=")?;
467463
self.end.fmt(fmt)?;
464+
if self.exhausted {
465+
write!(fmt, " (exhausted)")?;
466+
}
468467
Ok(())
469468
}
470469
}
471470

472-
impl<Idx: PartialEq<Idx>> RangeInclusive<Idx> {
473-
// Returns true if this is a range that started non-empty, and was iterated
474-
// to exhaustion.
475-
fn is_exhausted(&self) -> bool {
476-
Some(true) == self.is_empty && self.start == self.end
477-
}
478-
}
479-
480471
impl<Idx: PartialOrd<Idx>> RangeInclusive<Idx> {
481472
/// Returns `true` if `item` is contained in the range.
482473
///
@@ -544,15 +535,7 @@ impl<Idx: PartialOrd<Idx>> RangeInclusive<Idx> {
544535
#[unstable(feature = "range_is_empty", reason = "recently added", issue = "48111")]
545536
#[inline]
546537
pub fn is_empty(&self) -> bool {
547-
self.is_empty.unwrap_or_else(|| !(self.start <= self.end))
548-
}
549-
550-
// If this range's `is_empty` is field is unknown (`None`), update it to be a concrete value.
551-
#[inline]
552-
pub(crate) fn compute_is_empty(&mut self) {
553-
if self.is_empty.is_none() {
554-
self.is_empty = Some(!(self.start <= self.end));
555-
}
538+
self.exhausted || !(self.start <= self.end)
556539
}
557540
}
558541

src/test/codegen/issue-45222.rs

+18-16
Original file line numberDiff line numberDiff line change
@@ -25,28 +25,30 @@ pub fn check_foo2() -> u64 {
2525
}
2626

2727
// Simplified example of #45222
28-
29-
fn triangle_inc(n: u64) -> u64 {
30-
let mut count = 0;
31-
for j in 0 ..= n {
32-
count += j;
33-
}
34-
count
35-
}
36-
37-
// CHECK-LABEL: @check_triangle_inc
38-
#[no_mangle]
39-
pub fn check_triangle_inc() -> u64 {
40-
// CHECK: ret i64 5000050000
41-
triangle_inc(100000)
42-
}
28+
//
29+
// Temporarily disabled in #68835 to fix a soundness hole.
30+
//
31+
// fn triangle_inc(n: u64) -> u64 {
32+
// let mut count = 0;
33+
// for j in 0 ..= n {
34+
// count += j;
35+
// }
36+
// count
37+
// }
38+
//
39+
// // COMMENTEDCHECK-LABEL: @check_triangle_inc
40+
// #[no_mangle]
41+
// pub fn check_triangle_inc() -> u64 {
42+
// // COMMENTEDCHECK: ret i64 5000050000
43+
// triangle_inc(100000)
44+
// }
4345

4446
// Demo in #48012
4547

4648
fn foo3r(n: u64) -> u64 {
4749
let mut count = 0;
4850
(0..n).for_each(|_| {
49-
(0 ..= n).rev().for_each(|j| {
51+
(0..=n).rev().for_each(|j| {
5052
count += j;
5153
})
5254
});

0 commit comments

Comments
 (0)