Browse Source

Fix out-of-range panics of Date (#1075)

* Add overflow check when fixing year, month, and day

* Add timeclip

* Add timeclip to `Date.UTC()`

* Minor fix

* Fix comment

* Add more range check

* Add timeclip to all date creation

* Remove redundant overflow check

* Fix clippy
pull/1091/head
Jevan Chan 4 years ago committed by GitHub
parent
commit
db68c125d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 139
      boa/src/builtins/date/mod.rs

139
boa/src/builtins/date/mod.rs

@ -12,7 +12,14 @@ use crate::{
use chrono::{prelude::*, Duration, LocalResult};
use std::fmt::Display;
const NANOS_IN_MS: f64 = 1_000_000f64;
/// The number of nanoseconds in a millisecond.
const NANOS_PER_MS: i64 = 1_000_000;
/// The number of milliseconds in an hour.
const MILLIS_PER_HOUR: i64 = 3_600_000;
/// The number of milliseconds in a minute.
const MILLIS_PER_MINUTE: i64 = 60_000;
/// The number of milliseconds in a second.
const MILLIS_PER_SECOND: i64 = 1000;
#[inline]
fn is_zero_or_normal_opt(value: Option<f64>) -> bool {
@ -191,6 +198,22 @@ impl Date {
/// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 7;
/// Check if the time (number of miliseconds) is in the expected range.
/// Returns None if the time is not in the range, otherwise returns the time itself in option.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-timeclip
#[inline]
pub fn time_clip(time: f64) -> Option<f64> {
if time.abs() > 8.64e15 {
None
} else {
Some(time)
}
}
/// Converts the `Date` to a local `DateTime`.
///
/// If the `Date` is invalid (i.e. NAN), this function will return `None`.
@ -224,53 +247,64 @@ impl Date {
millisecond: Option<f64>,
) {
#[inline]
fn num_days_in(year: i32, month: u32) -> i32 {
fn num_days_in(year: i32, month: u32) -> Option<u32> {
let month = month + 1; // zero-based for calculations
NaiveDate::from_ymd(
match month {
12 => year + 1,
_ => year,
},
match month {
12 => 1,
_ => month + 1,
},
1,
Some(
NaiveDate::from_ymd_opt(
match month {
12 => year.checked_add(1)?,
_ => year,
},
match month {
12 => 1,
_ => month + 1,
},
1,
)?
.signed_duration_since(NaiveDate::from_ymd_opt(year, month, 1)?)
.num_days() as u32,
)
.signed_duration_since(NaiveDate::from_ymd(year, month, 1))
.num_days() as i32
}
#[inline]
fn fix_month(year: &mut i32, month: &mut i32) {
*year += *month / 12;
*month = if *month < 0 {
*year -= 1;
11 + (*month + 1) % 12
fn fix_month(year: i32, month: i32) -> Option<(i32, u32)> {
let year = year.checked_add(month / 12)?;
if month < 0 {
let year = year.checked_sub(1)?;
let month = (11 + (month + 1) % 12) as u32;
Some((year, month))
} else {
*month % 12
let month = (month % 12) as u32;
Some((year, month))
}
}
#[inline]
fn fix_day(year: &mut i32, month: &mut i32, day: &mut i32) {
fix_month(year, month);
fn fix_day(mut year: i32, mut month: i32, mut day: i32) -> Option<(i32, u32, u32)> {
loop {
if *day < 0 {
*month -= 1;
fix_month(year, month);
*day += num_days_in(*year, *month as u32);
if day < 0 {
let (fixed_year, fixed_month) = fix_month(year, month.checked_sub(1)?)?;
year = fixed_year;
month = fixed_month as i32;
day += num_days_in(fixed_year, fixed_month)? as i32;
} else {
let num_days = num_days_in(*year, *month as u32);
if *day >= num_days {
*day -= num_days_in(*year, *month as u32);
*month += 1;
fix_month(year, month);
let (fixed_year, fixed_month) = fix_month(year, month)?;
let num_days = num_days_in(fixed_year, fixed_month)? as i32;
if day >= num_days {
day -= num_days;
month = month.checked_add(1)?;
} else {
break;
}
}
}
let (fixed_year, fixed_month) = fix_month(year, month)?;
Some((fixed_year, fixed_month, day as u32))
}
// If any of the args are infinity or NaN, return an invalid date.
@ -286,22 +320,29 @@ impl Date {
};
self.0 = naive.and_then(|naive| {
let mut year = year.unwrap_or_else(|| naive.year() as f64) as i32;
let mut month = month.unwrap_or_else(|| naive.month0() as f64) as i32;
let mut day = day.unwrap_or_else(|| naive.day() as f64) as i32 - 1;
let year = year.unwrap_or_else(|| naive.year() as f64) as i32;
let month = month.unwrap_or_else(|| naive.month0() as f64) as i32;
let day = (day.unwrap_or_else(|| naive.day() as f64) as i32).checked_sub(1)?;
let hour = hour.unwrap_or_else(|| naive.hour() as f64) as i64;
let minute = minute.unwrap_or_else(|| naive.minute() as f64) as i64;
let second = second.unwrap_or_else(|| naive.second() as f64) as i64;
let millisecond =
millisecond.unwrap_or_else(|| naive.nanosecond() as f64 / NANOS_IN_MS) as i64;
let millisecond = millisecond
.unwrap_or_else(|| naive.nanosecond() as f64 / NANOS_PER_MS as f64)
as i64;
let (year, month, day) = fix_day(year, month, day)?;
let duration_hour = Duration::milliseconds(hour.checked_mul(MILLIS_PER_HOUR)?);
let duration_minute = Duration::milliseconds(minute.checked_mul(MILLIS_PER_MINUTE)?);
let duration_second = Duration::milliseconds(second.checked_mul(MILLIS_PER_SECOND)?);
let duration_milisecond = Duration::milliseconds(millisecond);
fix_day(&mut year, &mut month, &mut day);
let duration = duration_hour
.checked_add(&duration_minute)?
.checked_add(&duration_second)?
.checked_add(&duration_milisecond)?;
let duration = Duration::hours(hour)
+ Duration::minutes(minute)
+ Duration::seconds(second)
+ Duration::milliseconds(millisecond);
NaiveDate::from_ymd_opt(year, month as u32 + 1, day as u32 + 1)
NaiveDate::from_ymd_opt(year, month + 1, day + 1)
.and_then(|dt| dt.and_hms(0, 0, 0).checked_add_signed(duration))
.and_then(|dt| {
if utc {
@ -310,6 +351,7 @@ impl Date {
ignore_ambiguity(Local.from_local_datetime(&dt)).map(|dt| dt.naive_utc())
}
})
.filter(|dt| Self::time_clip(dt.timestamp_millis() as f64).is_some())
});
}
@ -415,6 +457,7 @@ impl Date {
},
};
let tv = tv.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some());
let date = Date(tv);
this.set_data(ObjectData::Date(date));
Ok(this.clone())
@ -477,7 +520,8 @@ impl Date {
let final_date = NaiveDate::from_ymd_opt(year, month + 1, day)
.and_then(|naive_date| naive_date.and_hms_milli_opt(hour, min, sec, milli))
.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local)))
.map(|local| local.naive_utc());
.map(|local| local.naive_utc())
.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some());
let date = Date(final_date);
this.set_data(ObjectData::Date(date));
@ -557,7 +601,7 @@ impl Date {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getMilliseconds
pub fn get_milliseconds(&self) -> f64 {
self.to_local()
.map_or(f64::NAN, |dt| dt.nanosecond() as f64 / NANOS_IN_MS)
.map_or(f64::NAN, |dt| dt.nanosecond() as f64 / NANOS_PER_MS as f64)
}
/// `Date.prototype.getMinutes()`
@ -724,7 +768,7 @@ impl Date {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCMilliseconds
pub fn get_utc_milliseconds(&self) -> f64 {
self.to_utc()
.map_or(f64::NAN, |dt| dt.nanosecond() as f64 / NANOS_IN_MS)
.map_or(f64::NAN, |dt| dt.nanosecond() as f64 / NANOS_PER_MS as f64)
}
/// `Date.prototype.getUTCMinutes()`
@ -1352,9 +1396,8 @@ impl Date {
NaiveDate::from_ymd_opt(year, month + 1, day)
.and_then(|f| f.and_hms_milli_opt(hour, min, sec, milli))
.map_or(Ok(Value::number(f64::NAN)), |f| {
Ok(Value::number(f.timestamp_millis() as f64))
})
.and_then(|f| Self::time_clip(f.timestamp_millis() as f64))
.map_or(Ok(Value::number(f64::NAN)), |time| Ok(Value::number(time)))
}
}

Loading…
Cancel
Save