Skip to content

Commit c4b23ae

Browse files
committed
Auto merge of #24865 - bluss:range-size, r=alexcrichton
core: Fix size_hint for signed integer `Range<T>` iterators There was an overflow bug in .size_hint() for signed iterators, which produced an hilariously incorrect size or an overflow panic. Incorrect size is a serious bug since the iterators are marked ExactSizeIterator. (And leads to abort() on (-1i8..127).collect() when the collection tries to preallocate too much). > (-1i8..127).size_hint() (18446744073709551488, Some(18446744073709551488)) Bug found using quickcheck. Fixes #24851
2 parents 8871c17 + 95be21d commit c4b23ae

File tree

2 files changed

+41
-4
lines changed

2 files changed

+41
-4
lines changed

src/libcore/iter.rs

+35-4
Original file line numberDiff line numberDiff line change
@@ -2407,12 +2407,14 @@ pub trait Step: PartialOrd {
24072407
/// `start` should always be less than `end`, so the result should never
24082408
/// be negative.
24092409
///
2410+
/// `by` must be > 0.
2411+
///
24102412
/// Returns `None` if it is not possible to calculate steps_between
24112413
/// without overflow.
24122414
fn steps_between(start: &Self, end: &Self, by: &Self) -> Option<usize>;
24132415
}
24142416

2415-
macro_rules! step_impl {
2417+
macro_rules! step_impl_unsigned {
24162418
($($t:ty)*) => ($(
24172419
impl Step for $t {
24182420
#[inline]
@@ -2423,7 +2425,33 @@ macro_rules! step_impl {
24232425
#[allow(trivial_numeric_casts)]
24242426
fn steps_between(start: &$t, end: &$t, by: &$t) -> Option<usize> {
24252427
if *start <= *end {
2426-
Some(((*end - *start) / *by) as usize)
2428+
// Note: We assume $t <= usize here
2429+
Some((*end - *start) as usize / (*by as usize))
2430+
} else {
2431+
Some(0)
2432+
}
2433+
}
2434+
}
2435+
)*)
2436+
}
2437+
macro_rules! step_impl_signed {
2438+
($($t:ty)*) => ($(
2439+
impl Step for $t {
2440+
#[inline]
2441+
fn step(&self, by: &$t) -> Option<$t> {
2442+
(*self).checked_add(*by)
2443+
}
2444+
#[inline]
2445+
#[allow(trivial_numeric_casts)]
2446+
fn steps_between(start: &$t, end: &$t, by: &$t) -> Option<usize> {
2447+
if *start <= *end {
2448+
// Note: We assume $t <= isize here
2449+
// Use .wrapping_sub and cast to usize to compute the
2450+
// difference that may not fit inside the range of isize.
2451+
Some(
2452+
((*end as isize).wrapping_sub(*start as isize) as usize
2453+
/ (*by as usize))
2454+
)
24272455
} else {
24282456
Some(0)
24292457
}
@@ -2447,9 +2475,12 @@ macro_rules! step_impl_no_between {
24472475
)*)
24482476
}
24492477

2450-
step_impl!(usize u8 u16 u32 isize i8 i16 i32);
2478+
step_impl_unsigned!(usize u8 u16 u32);
2479+
step_impl_signed!(isize i8 i16 i32);
2480+
#[cfg(target_pointer_width = "64")]
2481+
step_impl_unsigned!(u64);
24512482
#[cfg(target_pointer_width = "64")]
2452-
step_impl!(u64 i64);
2483+
step_impl_signed!(i64);
24532484
#[cfg(target_pointer_width = "32")]
24542485
step_impl_no_between!(u64 i64);
24552486

src/libcoretest/iter.rs

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use core::iter::*;
1212
use core::iter::order::*;
1313
use core::iter::MinMaxResult::*;
14+
use core::isize;
1415
use core::usize;
1516
use core::cmp;
1617

@@ -758,6 +759,11 @@ fn test_range() {
758759
assert_eq!((usize::MAX - 1..usize::MAX).size_hint(), (1, Some(1)));
759760
assert_eq!((-10..-1).size_hint(), (9, Some(9)));
760761
assert_eq!((-1..-10).size_hint(), (0, Some(0)));
762+
763+
assert_eq!((-70..58i8).size_hint(), (128, Some(128)));
764+
assert_eq!((-128..127i8).size_hint(), (255, Some(255)));
765+
assert_eq!((-2..isize::MAX).size_hint(),
766+
(isize::MAX as usize + 2, Some(isize::MAX as usize + 2)));
761767
}
762768

763769
#[test]

0 commit comments

Comments
 (0)