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. 123
      boa/src/builtins/date/mod.rs

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

@ -12,7 +12,14 @@ use crate::{
use chrono::{prelude::*, Duration, LocalResult}; use chrono::{prelude::*, Duration, LocalResult};
use std::fmt::Display; 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] #[inline]
fn is_zero_or_normal_opt(value: Option<f64>) -> bool { fn is_zero_or_normal_opt(value: Option<f64>) -> bool {
@ -191,6 +198,22 @@ impl Date {
/// The amount of arguments this function object takes. /// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 7; 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`. /// Converts the `Date` to a local `DateTime`.
/// ///
/// If the `Date` is invalid (i.e. NAN), this function will return `None`. /// If the `Date` is invalid (i.e. NAN), this function will return `None`.
@ -224,11 +247,13 @@ impl Date {
millisecond: Option<f64>, millisecond: Option<f64>,
) { ) {
#[inline] #[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 let month = month + 1; // zero-based for calculations
NaiveDate::from_ymd(
Some(
NaiveDate::from_ymd_opt(
match month { match month {
12 => year + 1, 12 => year.checked_add(1)?,
_ => year, _ => year,
}, },
match month { match month {
@ -236,41 +261,50 @@ impl Date {
_ => month + 1, _ => month + 1,
}, },
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] #[inline]
fn fix_month(year: &mut i32, month: &mut i32) { fn fix_month(year: i32, month: i32) -> Option<(i32, u32)> {
*year += *month / 12; let year = year.checked_add(month / 12)?;
*month = if *month < 0 {
*year -= 1; if month < 0 {
11 + (*month + 1) % 12 let year = year.checked_sub(1)?;
let month = (11 + (month + 1) % 12) as u32;
Some((year, month))
} else { } else {
*month % 12 let month = (month % 12) as u32;
Some((year, month))
} }
} }
#[inline] #[inline]
fn fix_day(year: &mut i32, month: &mut i32, day: &mut i32) { fn fix_day(mut year: i32, mut month: i32, mut day: i32) -> Option<(i32, u32, u32)> {
fix_month(year, month);
loop { loop {
if *day < 0 { if day < 0 {
*month -= 1; let (fixed_year, fixed_month) = fix_month(year, month.checked_sub(1)?)?;
fix_month(year, month);
*day += num_days_in(*year, *month as u32); year = fixed_year;
month = fixed_month as i32;
day += num_days_in(fixed_year, fixed_month)? as i32;
} else { } else {
let num_days = num_days_in(*year, *month as u32); let (fixed_year, fixed_month) = fix_month(year, month)?;
if *day >= num_days { let num_days = num_days_in(fixed_year, fixed_month)? as i32;
*day -= num_days_in(*year, *month as u32);
*month += 1; if day >= num_days {
fix_month(year, month); day -= num_days;
month = month.checked_add(1)?;
} else { } else {
break; 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. // 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| { self.0 = naive.and_then(|naive| {
let mut year = year.unwrap_or_else(|| naive.year() as f64) as i32; let 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 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 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 hour = hour.unwrap_or_else(|| naive.hour() as f64) as i64;
let minute = minute.unwrap_or_else(|| naive.minute() 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 second = second.unwrap_or_else(|| naive.second() as f64) as i64;
let millisecond = let millisecond = millisecond
millisecond.unwrap_or_else(|| naive.nanosecond() as f64 / NANOS_IN_MS) as i64; .unwrap_or_else(|| naive.nanosecond() as f64 / NANOS_PER_MS as f64)
as i64;
let (year, month, day) = fix_day(year, month, day)?;
fix_day(&mut year, &mut month, &mut 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);
let duration = Duration::hours(hour) let duration = duration_hour
+ Duration::minutes(minute) .checked_add(&duration_minute)?
+ Duration::seconds(second) .checked_add(&duration_second)?
+ Duration::milliseconds(millisecond); .checked_add(&duration_milisecond)?;
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| dt.and_hms(0, 0, 0).checked_add_signed(duration))
.and_then(|dt| { .and_then(|dt| {
if utc { if utc {
@ -310,6 +351,7 @@ impl Date {
ignore_ambiguity(Local.from_local_datetime(&dt)).map(|dt| dt.naive_utc()) 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); let date = Date(tv);
this.set_data(ObjectData::Date(date)); this.set_data(ObjectData::Date(date));
Ok(this.clone()) Ok(this.clone())
@ -477,7 +520,8 @@ impl Date {
let final_date = NaiveDate::from_ymd_opt(year, month + 1, day) 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(|naive_date| naive_date.and_hms_milli_opt(hour, min, sec, milli))
.and_then(|local| ignore_ambiguity(Local.from_local_datetime(&local))) .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); let date = Date(final_date);
this.set_data(ObjectData::Date(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 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getMilliseconds
pub fn get_milliseconds(&self) -> f64 { pub fn get_milliseconds(&self) -> f64 {
self.to_local() 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()` /// `Date.prototype.getMinutes()`
@ -724,7 +768,7 @@ impl Date {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCMilliseconds /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getUTCMilliseconds
pub fn get_utc_milliseconds(&self) -> f64 { pub fn get_utc_milliseconds(&self) -> f64 {
self.to_utc() 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()` /// `Date.prototype.getUTCMinutes()`
@ -1352,9 +1396,8 @@ impl Date {
NaiveDate::from_ymd_opt(year, month + 1, day) NaiveDate::from_ymd_opt(year, month + 1, day)
.and_then(|f| f.and_hms_milli_opt(hour, min, sec, milli)) .and_then(|f| f.and_hms_milli_opt(hour, min, sec, milli))
.map_or(Ok(Value::number(f64::NAN)), |f| { .and_then(|f| Self::time_clip(f.timestamp_millis() as f64))
Ok(Value::number(f.timestamp_millis() as f64)) .map_or(Ok(Value::number(f64::NAN)), |time| Ok(Value::number(time)))
})
} }
} }

Loading…
Cancel
Save