Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion components/calendar/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4"
arbitrary = { version = "1.4", features = ["derive"] }
icu_calendar = { path = "..", features = ["compiled_data"] }
icu_calendar = { path = "..", features = ["compiled_data", "unstable"] }

# Prevent this from interfering with workspaces
[workspace]
Expand All @@ -25,3 +25,17 @@ path = "fuzz_targets/construction.rs"
test = false
doc = false
bench = false

[[bin]]
name = "add"
path = "fuzz_targets/add.rs"
test = false
doc = false
bench = false

[[bin]]
name = "until"
path = "fuzz_targets/until.rs"
test = false
doc = false
bench = false
101 changes: 101 additions & 0 deletions components/calendar/fuzz/fuzz_targets/add.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

#![no_main]

mod common;

use common::{Ymd, AnyCalendarKind};

use arbitrary::Arbitrary;
use icu_calendar::options::*;
use libfuzzer_sys::fuzz_target;

#[derive(Arbitrary, Debug)]
struct FuzzInput {
ymd: Ymd,
duration: DateDuration,
overflow_constrain: bool,
cal: AnyCalendarKind,
}

#[derive(Arbitrary, Debug)]
pub struct DateDuration {
pub is_negative: bool,
pub years: u32,
pub months: u32,
pub weeks: u32,
pub days: u64,
}

impl From<DateDuration> for icu_calendar::types::DateDuration {
fn from(other: DateDuration) -> Self {
Self {
is_negative: other.is_negative,
years: other.years,
months: other.months,
weeks: other.weeks,
days: other.days,
}
}
}

impl DateDuration {
/// Temporal doesn't care about dates outside of -271821-04-20 to +275760-09-13
/// and will not let you attempt to add up to them even with overflow: constrain.
///
/// We should eventually be applying some limits to this code in ICU4X.
/// We currently do not and `Date::try_added()` will panic for large `days` values.
///
/// <https://github.com/unicode-org/icu4x/issues/3964>
///
/// For now, fuzz what we can for Temporal's needs.
///
/// This code is copied from <https://github.com/boa-dev/temporal/pull/615>
/// so that we are testing temporal_rs behavior.
fn is_valid_for_temporal(&self) -> bool {
// Temporal range is -271821-04-20 to +275760-09-13
// This is (roughly) the maximum year duration that can exist for ISO
const TEMPORAL_MAX_ISO_YEAR_DURATION: u32 = 275760 + 271821;
// Double it. No calendar has years that are half the size of ISO years.
const YEAR_DURATION: u32 = 2 * TEMPORAL_MAX_ISO_YEAR_DURATION;
// Assume every year is a leap year, calculate a month range
const MONTH_DURATION: u32 = YEAR_DURATION * 13;
// Our longest year is 390 days
const DAY_DURATION: u32 = YEAR_DURATION * 390;
const WEEK_DURATION: u32 = DAY_DURATION / 7;


if self.years > YEAR_DURATION {
return false;
}
if self.months > MONTH_DURATION {
return false;
}
if self.weeks > WEEK_DURATION {
return false;
}
if self.days > DAY_DURATION.into() {
return false;
}

true

}
}

fuzz_target!(|data: FuzzInput| {
let Some(date) = data.ymd.to_date(data.cal, true) else { return };

let mut options = DateAddOptions::default();
options.overflow = if data.overflow_constrain {
Some(Overflow::Constrain)
} else {
Some(Overflow::Reject)
};
if !data.duration.is_valid_for_temporal() {
return;
}
let _ = date.try_added_with_options(data.duration.into(), options);
});
109 changes: 109 additions & 0 deletions components/calendar/fuzz/fuzz_targets/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use arbitrary::Arbitrary;
use icu_calendar::{Date, AnyCalendar};
use icu_calendar::types::{DateFields, MonthCode};
use icu_calendar::options::*;


#[derive(Arbitrary, Debug, Copy, Clone)]
pub struct Ymd {
year: i32,
month: u8,
day: u8,
month_interpretation: MonthInterpretation,
}

impl Ymd {
pub fn to_date(self, kind: AnyCalendarKind, overflow_constrain: bool) -> Option<Date<AnyCalendar>> {
let calendar = AnyCalendar::new(kind.into());

let mut options = DateFromFieldsOptions::default();

options.overflow = if overflow_constrain {
Some(Overflow::Constrain)
} else {
Some(Overflow::Reject)
};

let code: MonthCode;

let mut fields = DateFields::default();
fields.extended_year = Some(self.year);
fields.day = Some(self.day);
match self.month_interpretation {
MonthInterpretation::Ordinal => {
fields.ordinal_month = Some(self.month);
}
MonthInterpretation::CodeNormal => {
code = MonthCode::new_normal(self.month)?;
fields.month_code = Some(code.0.as_bytes());
}
MonthInterpretation::CodeLeap => {
code = MonthCode::new_leap(self.month)?;
fields.month_code = Some(code.0.as_bytes());
}
};

Date::try_from_fields(fields, options, calendar).ok()
}
}

#[derive(Arbitrary, Debug, Copy, Clone)]
enum MonthInterpretation {
Ordinal,
CodeNormal,
CodeLeap,
}

#[derive(Arbitrary, Debug, Copy, Clone)]
pub enum AnyCalendarKind {
Buddhist,
Chinese,
Coptic,
Dangi,
Ethiopian,
EthiopianAmeteAlem,
Gregorian,
Hebrew,
Indian,
HijriTabularTypeIIFriday,
// Not needed by Temporal and has some bugs
// https://github.com/unicode-org/icu4x/issues/7049#issuecomment-3384358307
// HijriSimulatedMecca,
HijriTabularTypeIIThursday,
HijriUmmAlQura,
Iso,
Japanese,
JapaneseExtended,
Persian,
Roc,
// Note: This doesn't cover Julian, since it's not in AnyCalendar
}

impl From<AnyCalendarKind> for icu_calendar::AnyCalendarKind {
fn from(other: AnyCalendarKind) -> Self {
match other {
AnyCalendarKind::Buddhist => Self::Buddhist,
AnyCalendarKind::Chinese => Self::Chinese,
AnyCalendarKind::Coptic => Self::Coptic,
AnyCalendarKind::Dangi => Self::Dangi,
AnyCalendarKind::Ethiopian => Self::Ethiopian,
AnyCalendarKind::EthiopianAmeteAlem => Self::EthiopianAmeteAlem,
AnyCalendarKind::Gregorian => Self::Gregorian,
AnyCalendarKind::Hebrew => Self::Hebrew,
AnyCalendarKind::Indian => Self::Indian,
AnyCalendarKind::HijriTabularTypeIIFriday => Self::HijriTabularTypeIIFriday,
// AnyCalendarKind::HijriSimulatedMecca => Self::HijriSimulatedMecca,
AnyCalendarKind::HijriTabularTypeIIThursday => Self::HijriTabularTypeIIThursday,
AnyCalendarKind::HijriUmmAlQura => Self::HijriUmmAlQura,
AnyCalendarKind::Iso => Self::Iso,
AnyCalendarKind::Japanese => Self::Japanese,
AnyCalendarKind::JapaneseExtended => Self::JapaneseExtended,
AnyCalendarKind::Persian => Self::Persian,
AnyCalendarKind::Roc => Self::Roc,
}
}
}
106 changes: 6 additions & 100 deletions components/calendar/fuzz/fuzz_targets/construction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,116 +4,22 @@

#![no_main]

mod common;

use common::{Ymd, AnyCalendarKind};

use arbitrary::Arbitrary;
use icu_calendar::options::*;
use icu_calendar::types::{DateFields, MonthCode};
use icu_calendar::{AnyCalendar, Date};
use libfuzzer_sys::fuzz_target;

#[derive(Arbitrary, Debug)]
struct FuzzInput {
year: i32,
month: u8,
day: u8,
month_interpretation: MonthInterpretation,
ymd: Ymd,
overflow_constrain: bool,
cal: AnyCalendarKind,
}

#[derive(Arbitrary, Debug)]
enum MonthInterpretation {
Ordinal,
CodeNormal,
CodeLeap,
}

#[derive(Arbitrary, Debug)]
pub enum AnyCalendarKind {
Buddhist,
Chinese,
Coptic,
Dangi,
Ethiopian,
EthiopianAmeteAlem,
Gregorian,
Hebrew,
Indian,
HijriTabularTypeIIFriday,
// Not needed by Temporal and has some bugs
// https://github.com/unicode-org/icu4x/issues/7049#issuecomment-3384358307
// HijriSimulatedMecca,
HijriTabularTypeIIThursday,
HijriUmmAlQura,
Iso,
Japanese,
JapaneseExtended,
Persian,
Roc,
// Note: This doesn't cover Julian, since it's not in AnyCalendar
}

impl From<AnyCalendarKind> for icu_calendar::AnyCalendarKind {
fn from(other: AnyCalendarKind) -> Self {
match other {
AnyCalendarKind::Buddhist => Self::Buddhist,
AnyCalendarKind::Chinese => Self::Chinese,
AnyCalendarKind::Coptic => Self::Coptic,
AnyCalendarKind::Dangi => Self::Dangi,
AnyCalendarKind::Ethiopian => Self::Ethiopian,
AnyCalendarKind::EthiopianAmeteAlem => Self::EthiopianAmeteAlem,
AnyCalendarKind::Gregorian => Self::Gregorian,
AnyCalendarKind::Hebrew => Self::Hebrew,
AnyCalendarKind::Indian => Self::Indian,
AnyCalendarKind::HijriTabularTypeIIFriday => Self::HijriTabularTypeIIFriday,
// AnyCalendarKind::HijriSimulatedMecca => Self::HijriSimulatedMecca,
AnyCalendarKind::HijriTabularTypeIIThursday => Self::HijriTabularTypeIIThursday,
AnyCalendarKind::HijriUmmAlQura => Self::HijriUmmAlQura,
AnyCalendarKind::Iso => Self::Iso,
AnyCalendarKind::Japanese => Self::Japanese,
AnyCalendarKind::JapaneseExtended => Self::JapaneseExtended,
AnyCalendarKind::Persian => Self::Persian,
AnyCalendarKind::Roc => Self::Roc,
}
}
}

macro_rules! unwrap_or_return(
($e:expr) => {
{
let Some(r) = $e else {
return;
};
r
}
}
);

fuzz_target!(|data: FuzzInput| {
let calendar = AnyCalendar::new(data.cal.into());

let mut options = DateFromFieldsOptions::default();

options.overflow = if data.overflow_constrain {
Some(Overflow::Constrain)
} else {
Some(Overflow::Reject)
};

let mut fields = DateFields::default();
fields.extended_year = Some(data.year);
fields.day = Some(data.day);
match data.month_interpretation {
MonthInterpretation::Ordinal => {
fields.ordinal_month = Some(data.month);
}
MonthInterpretation::CodeNormal => {
fields.month_code = Some(unwrap_or_return!(MonthCode::new_normal(data.month)));
}
MonthInterpretation::CodeLeap => {
fields.month_code = Some(unwrap_or_return!(MonthCode::new_leap(data.month)));
}
};
if let Ok(date) = Date::try_from_fields(fields, options, calendar) {
if let Some(date) = data.ymd.to_date(data.cal, data.overflow_constrain) {
let _ = date.day_of_month();
let _ = date.day_of_week();
let _ = date.day_of_year();
Expand Down
Loading