From 42233b0c814d3a32d0f212ee6674d6285b6c6b15 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 31 Mar 2026 09:32:40 +0200 Subject: [PATCH] Add large-year coverage to fuzz target Add a dedicated fuzz_large_year target with structured inputs biased toward large years (near 9999 boundary, up to GNU_MAX_YEAR). Keep the original fuzz_parse_datetime target unchanged for raw-bytes fuzzing. --- fuzz/Cargo.lock | 16 +++++++++ fuzz/Cargo.toml | 8 +++++ fuzz/fuzz_targets/large_year.rs | 64 +++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 fuzz/fuzz_targets/large_year.rs diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index db1ae3d..de2c2c7 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -7,6 +7,9 @@ name = "arbitrary" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "autocfg" @@ -32,6 +35,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -42,6 +56,8 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" name = "fuzz_parse_datetime" version = "0.2.0" dependencies = [ + "arbitrary", + "jiff", "libfuzzer-sys", "parse_datetime", ] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 8923b11..69ebb92 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -8,6 +8,8 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4.7" +arbitrary = { version = "1", features = ["derive"] } +jiff = "0.2" [dependencies.parse_datetime] path = "../" @@ -17,3 +19,9 @@ name = "fuzz_parse_datetime" path = "fuzz_targets/parse_datetime.rs" test = false doc = false + +[[bin]] +name = "fuzz_large_year" +path = "fuzz_targets/large_year.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/large_year.rs b/fuzz/fuzz_targets/large_year.rs new file mode 100644 index 0000000..ace853f --- /dev/null +++ b/fuzz/fuzz_targets/large_year.rs @@ -0,0 +1,64 @@ +#![no_main] + +use arbitrary::Arbitrary; +use jiff::{civil::DateTime, tz::TimeZone}; +use libfuzzer_sys::fuzz_target; + +#[derive(Arbitrary, Debug)] +struct Input { + /// Year for the base date (biased toward boundary years). + base_year_selector: u8, + /// Year to embed in a constructed large-year input string. + input_year: u32, + month: u8, + day: u8, + /// Suffix appended after the constructed date (e.g. relative items). + suffix: String, + /// Whether to also call parse_datetime (no base). + try_no_base: bool, +} + +fn base_year(selector: u8) -> i16 { + match selector % 6 { + 0 => 2024, + 1 => 9998, + 2 => 9999, + 3 => 1, + 4 => 100, + _ => (selector as i16) * 40, + } +} + +fn clamp_year(y: u32) -> u32 { + // Focus on the interesting range: 9990..=100_000 and 0..=20_000 + match y % 4 { + 0 => 9990 + (y % 20), // near boundary + 1 => 10000 + (y % 90_000), // large years + 2 => y % 20_000, // general range + _ => 2_147_485_540 + (y % 10), // near GNU_MAX_YEAR + } +} + +fuzz_target!(|input: Input| { + let year = clamp_year(input.input_year); + let month = (input.month % 12) + 1; + let day = (input.day % 28) + 1; + let date_str = format!("{year:04}-{month:02}-{day:02} {}", input.suffix); + + // Test parse_datetime (uses current time as base). + if input.try_no_base { + let _ = parse_datetime::parse_datetime(&date_str); + } + + // Test parse_datetime_at_date with a controlled base. + let by = base_year(input.base_year_selector); + if let Ok(base) = DateTime::new(by, 1, 1, 0, 0, 0, 0) { + if let Ok(base) = base.to_zoned(TimeZone::UTC) { + let _ = parse_datetime::parse_datetime_at_date(base, &date_str); + } + } + + // Also try a bare large year as a pure number. + let bare = format!("{year}"); + let _ = parse_datetime::parse_datetime(&bare); +});