]> git.lizzy.rs Git - rust.git/commitdiff
core: Fix size_hint for signed integer Range<T> iterators
authorUlrik Sverdrup <root@localhost>
Mon, 27 Apr 2015 11:27:24 +0000 (13:27 +0200)
committerUlrik Sverdrup <root@localhost>
Mon, 27 Apr 2015 19:14:43 +0000 (21:14 +0200)
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).

All signed range iterators were affected.

> (-1i8..127).size_hint()
(18446744073709551488, Some(18446744073709551488))

Bug found using quickcheck.

Fixes #24851

src/libcore/iter.rs
src/libcoretest/iter.rs

index 233ed018119302deceb23bedcdac7e0175a9b0e0..5e98dd8b7df06092009e1daf6ec0ce05ed37241f 100644 (file)
@@ -2407,12 +2407,14 @@ pub trait Step: PartialOrd {
     /// `start` should always be less than `end`, so the result should never
     /// be negative.
     ///
+    /// `by` must be > 0.
+    ///
     /// Returns `None` if it is not possible to calculate steps_between
     /// without overflow.
     fn steps_between(start: &Self, end: &Self, by: &Self) -> Option<usize>;
 }
 
-macro_rules! step_impl {
+macro_rules! step_impl_unsigned {
     ($($t:ty)*) => ($(
         impl Step for $t {
             #[inline]
@@ -2423,7 +2425,33 @@ fn step(&self, by: &$t) -> Option<$t> {
             #[allow(trivial_numeric_casts)]
             fn steps_between(start: &$t, end: &$t, by: &$t) -> Option<usize> {
                 if *start <= *end {
-                    Some(((*end - *start) / *by) as usize)
+                    // Note: We assume $t <= usize here
+                    Some((*end - *start) as usize / (*by as usize))
+                } else {
+                    Some(0)
+                }
+            }
+        }
+    )*)
+}
+macro_rules! step_impl_signed {
+    ($($t:ty)*) => ($(
+        impl Step for $t {
+            #[inline]
+            fn step(&self, by: &$t) -> Option<$t> {
+                (*self).checked_add(*by)
+            }
+            #[inline]
+            #[allow(trivial_numeric_casts)]
+            fn steps_between(start: &$t, end: &$t, by: &$t) -> Option<usize> {
+                if *start <= *end {
+                    // Note: We assume $t <= isize here
+                    // Use .wrapping_sub and cast to usize to compute the
+                    // difference that may not fit inside the range of isize.
+                    Some(
+                        ((*end as isize).wrapping_sub(*start as isize) as usize
+                        / (*by as usize))
+                    )
                 } else {
                     Some(0)
                 }
@@ -2447,9 +2475,12 @@ fn steps_between(_a: &$t, _b: &$t, _by: &$t) -> Option<usize> {
     )*)
 }
 
-step_impl!(usize u8 u16 u32 isize i8 i16 i32);
+step_impl_unsigned!(usize u8 u16 u32);
+step_impl_signed!(isize i8 i16 i32);
+#[cfg(target_pointer_width = "64")]
+step_impl_unsigned!(u64);
 #[cfg(target_pointer_width = "64")]
-step_impl!(u64 i64);
+step_impl_signed!(i64);
 #[cfg(target_pointer_width = "32")]
 step_impl_no_between!(u64 i64);
 
index 2866c193c3b1546a99f10736c0f4d11bade8343a..bb36630f1682f5ea1c1f869c7a907426509b6231 100644 (file)
@@ -11,6 +11,7 @@
 use core::iter::*;
 use core::iter::order::*;
 use core::iter::MinMaxResult::*;
+use core::isize;
 use core::usize;
 use core::cmp;
 
@@ -758,6 +759,11 @@ fn test_range() {
     assert_eq!((usize::MAX - 1..usize::MAX).size_hint(), (1, Some(1)));
     assert_eq!((-10..-1).size_hint(), (9, Some(9)));
     assert_eq!((-1..-10).size_hint(), (0, Some(0)));
+
+    assert_eq!((-70..58i8).size_hint(), (128, Some(128)));
+    assert_eq!((-128..127i8).size_hint(), (255, Some(255)));
+    assert_eq!((-2..isize::MAX).size_hint(),
+               (isize::MAX as usize + 2, Some(isize::MAX as usize + 2)));
 }
 
 #[test]