Skip to content

Commit

Permalink
Avoid integer overflow in arithmetic
Browse files Browse the repository at this point in the history
  • Loading branch information
jhpratt committed Sep 19, 2023
1 parent 5813d61 commit 5b48552
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 8 deletions.
6 changes: 6 additions & 0 deletions tests/offset_date_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1270,3 +1270,9 @@ fn saturating_sub_duration() {
datetime!(+999999 - 12 - 31 23:59:59.999_999_999 +10)
);
}

#[test]
#[should_panic = "overflow adding duration to date"]
fn issue_621() {
let _ = OffsetDateTime::UNIX_EPOCH + StdDuration::from_secs(18_157_382_926_370_278_155);
}
98 changes: 90 additions & 8 deletions time/src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,49 @@ impl Date {
}
}

/// Computes `self + duration`, returning `None` if an overflow occurred.
///
/// ```rust
/// # use time::{Date, ext::NumericalStdDuration};
/// # use time_macros::date;
/// assert_eq!(Date::MAX.checked_add_std(1.std_days()), None);
/// assert_eq!(
/// date!(2020 - 12 - 31).checked_add_std(2.std_days()),
/// Some(date!(2021 - 01 - 02))
/// );
/// ```
///
/// # Note
///
/// This function only takes whole days into account.
///
/// ```rust
/// # use time::{Date, ext::NumericalStdDuration};
/// # use time_macros::date;
/// assert_eq!(Date::MAX.checked_add_std(23.std_hours()), Some(Date::MAX));
/// assert_eq!(
/// date!(2020 - 12 - 31).checked_add_std(23.std_hours()),
/// Some(date!(2020 - 12 - 31))
/// );
/// assert_eq!(
/// date!(2020 - 12 - 31).checked_add_std(47.std_hours()),
/// Some(date!(2021 - 01 - 01))
/// );
/// ```
pub const fn checked_add_std(self, duration: StdDuration) -> Option<Self> {
let whole_days = duration.as_secs() / Second.per(Day) as u64;
if whole_days > i32::MAX as u64 {
return None;
}

let julian_day = const_try_opt!(self.to_julian_day().checked_add(whole_days as _));
if let Ok(date) = Self::from_julian_day(julian_day) {
Some(date)
} else {
None
}
}

/// Computes `self - duration`, returning `None` if an overflow occurred.
///
/// ```
Expand Down Expand Up @@ -786,6 +829,49 @@ impl Date {
}
}

/// Computes `self - duration`, returning `None` if an overflow occurred.
///
/// ```
/// # use time::{Date, ext::NumericalStdDuration};
/// # use time_macros::date;
/// assert_eq!(Date::MIN.checked_sub_std(1.std_days()), None);
/// assert_eq!(
/// date!(2020 - 12 - 31).checked_sub_std(2.std_days()),
/// Some(date!(2020 - 12 - 29))
/// );
/// ```
///
/// # Note
///
/// This function only takes whole days into account.
///
/// ```
/// # use time::{Date, ext::NumericalStdDuration};
/// # use time_macros::date;
/// assert_eq!(Date::MIN.checked_sub_std(23.std_hours()), Some(Date::MIN));
/// assert_eq!(
/// date!(2020 - 12 - 31).checked_sub_std(23.std_hours()),
/// Some(date!(2020 - 12 - 31))
/// );
/// assert_eq!(
/// date!(2020 - 12 - 31).checked_sub_std(47.std_hours()),
/// Some(date!(2020 - 12 - 30))
/// );
/// ```
pub const fn checked_sub_std(self, duration: StdDuration) -> Option<Self> {
let whole_days = duration.as_secs() / Second.per(Day) as u64;
if whole_days > i32::MAX as u64 {
return None;
}

let julian_day = const_try_opt!(self.to_julian_day().checked_sub(whole_days as _));
if let Ok(date) = Self::from_julian_day(julian_day) {
Some(date)
} else {
None
}
}

/// Calculates the first occurrence of a weekday that is strictly later than a given `Date`.
/// Returns `None` if an overflow occurred.
pub(crate) const fn checked_next_occurrence(self, weekday: Weekday) -> Option<Self> {
Expand Down Expand Up @@ -1225,10 +1311,8 @@ impl Add<StdDuration> for Date {
type Output = Self;

fn add(self, duration: StdDuration) -> Self::Output {
Self::from_julian_day(
self.to_julian_day() + (duration.as_secs() / Second.per(Day) as u64) as i32,
)
.expect("overflow adding duration to date")
self.checked_add_std(duration)
.expect("overflow adding duration to date")
}
}

Expand All @@ -1247,10 +1331,8 @@ impl Sub<StdDuration> for Date {
type Output = Self;

fn sub(self, duration: StdDuration) -> Self::Output {
Self::from_julian_day(
self.to_julian_day() - (duration.as_secs() / Second.per(Day) as u64) as i32,
)
.expect("overflow subtracting duration from date")
self.checked_sub_std(duration)
.expect("overflow subtracting duration from date")
}
}

Expand Down

0 comments on commit 5b48552

Please sign in to comment.