Skip to content

Commit c7795ef

Browse files
authored
Constrain durations into a range that will produce valid Temporal dates before passing to ICU4X (#615)
We have some panics we should be avoiding here unicode-org/icu4x#7206
1 parent e91d5a9 commit c7795ef

File tree

2 files changed

+59
-1
lines changed

2 files changed

+59
-1
lines changed

src/builtins/compiled/duration/tests.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::{
22
duration::DateDuration,
33
options::{
4-
OffsetDisambiguation, RelativeTo, RoundingIncrement, RoundingMode, RoundingOptions, Unit,
4+
OffsetDisambiguation, Overflow, RelativeTo, RoundingIncrement, RoundingMode,
5+
RoundingOptions, Unit,
56
},
67
partial::PartialDuration,
78
Calendar, PlainDate, TimeZone, ZonedDateTime,
@@ -626,6 +627,24 @@ fn add_normalized_time_duration_out_of_range() {
626627
assert!(err.is_err())
627628
}
628629

630+
#[test]
631+
fn add_large_durations() {
632+
// Testcases found by fuzzing <https://github.com/unicode-org/icu4x/pull/7206>
633+
let base = PlainDate::new(2000, 1, 1, Calendar::from_str("dangi").unwrap()).unwrap();
634+
635+
let test_duration = Duration::from(DateDuration::new(4294901760, 256, 0, 0).unwrap());
636+
assert!(base.add(&test_duration, Some(Overflow::Constrain)).is_err());
637+
638+
let test_duration = Duration::from(DateDuration::new(0, 1281, 0, 8589934592).unwrap());
639+
assert!(base.add(&test_duration, Some(Overflow::Constrain)).is_err());
640+
641+
let test_duration = Duration::from(DateDuration::new(2046820352, 0, 0, 0).unwrap());
642+
assert!(base.add(&test_duration, Some(Overflow::Constrain)).is_err());
643+
644+
let test_duration = Duration::from(DateDuration::new(0, 0, 2516582400, 0).unwrap());
645+
assert!(base.add(&test_duration, Some(Overflow::Constrain)).is_err());
646+
}
647+
629648
#[test]
630649
fn test_rounding_boundaries() {
631650
let relative_to = PlainDate::new(2000, 1, 1, Calendar::default()).unwrap();

src/builtins/core/calendar.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,43 @@ impl<'a> TryFrom<&'a CalendarFields> for DateFields<'a> {
250250
Ok(this)
251251
}
252252
}
253+
254+
/// See <https://github.com/unicode-org/icu4x/issues/7207>
255+
///
256+
/// We don't want to trigger any pathological or panicky testcases in ICU4X.
257+
///
258+
/// To aid in that, we early constrain date durations to things that have no hope of producing a datetime
259+
/// that is within Temporal range.
260+
fn early_constrain_date_duration(duration: &IcuDateDuration) -> Result<(), TemporalError> {
261+
// Temporal range is -271821-04-20 to +275760-09-13
262+
// This is (roughly) the maximum year duration that can exist for ISO
263+
const TEMPORAL_MAX_ISO_YEAR_DURATION: u32 = 275760 + 271821;
264+
// Double it. No calendar has years that are half the size of ISO years.
265+
const YEAR_DURATION: u32 = 2 * TEMPORAL_MAX_ISO_YEAR_DURATION;
266+
// Assume every year is a leap year, calculate a month range
267+
const MONTH_DURATION: u32 = YEAR_DURATION * 13;
268+
// Our longest year is 390 days
269+
const DAY_DURATION: u32 = YEAR_DURATION * 390;
270+
const WEEK_DURATION: u32 = DAY_DURATION / 7;
271+
272+
let err = Err(TemporalError::range().with_enum(ErrorMessage::IntermediateDateTimeOutOfRange));
273+
274+
if duration.years > YEAR_DURATION {
275+
return err;
276+
}
277+
if duration.months > MONTH_DURATION {
278+
return err;
279+
}
280+
if duration.weeks > WEEK_DURATION {
281+
return err;
282+
}
283+
if duration.days > DAY_DURATION.into() {
284+
return err;
285+
}
286+
287+
Ok(())
288+
}
289+
253290
// ==== Public `CalendarSlot` methods ====
254291

255292
impl Calendar {
@@ -437,6 +474,8 @@ impl Calendar {
437474
weeks: u32::try_from(duration.weeks.abs()).map_err(|_| invalid)?,
438475
days: u64::try_from(duration.days.abs()).map_err(|_| invalid)?,
439476
};
477+
478+
early_constrain_date_duration(&duration)?;
440479
let mut options = DateAddOptions::default();
441480
options.overflow = Some(overflow.into());
442481
let calendar_date = self.0.from_iso(*date.to_icu4x().inner());

0 commit comments

Comments
 (0)