From c731763b180ba2ea50190fe565cfd24950fcb4ce Mon Sep 17 00:00:00 2001 From: Zander Milroy Date: Wed, 7 Oct 2026 21:41:35 +0000 Subject: [PATCH 1/3] test(gds21): cover invalid BGNLIB/BGNSTR datetimes Add a regression that zeroes library and struct date fields and expects geometry to survive parse. Co-authored-by: Cursor --- gds21/src/tests.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/gds21/src/tests.rs b/gds21/src/tests.rs index c0931ea..998d5d4 100644 --- a/gds21/src/tests.rs +++ b/gds21/src/tests.rs @@ -2,7 +2,10 @@ use super::*; /// Specified creation date for test cases fn test_dates() -> GdsDateTimes { - let test_date = NaiveDate::from_ymd(1970, 1, 1).and_hms(0, 0, 1); + let test_date = NaiveDate::from_ymd_opt(1970, 1, 1) + .unwrap() + .and_hms_opt(0, 0, 1) + .unwrap(); GdsDateTimes { modified: test_date, accessed: test_date, @@ -80,6 +83,50 @@ fn it_round_trips() -> GdsResult<()> { Ok(()) } #[test] +fn invalid_datetimes_preserve_geometry() -> GdsResult<()> { + let points = GdsPoint::vec(&[(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)]); + let mut strukt = GdsStruct::new("cell"); + strukt.elems.push( + GdsBoundary { + xy: points.clone(), + ..Default::default() + } + .into(), + ); + let mut lib = GdsLibrary::new("lib"); + lib.structs.push(strukt); + + let mut bytes = Vec::new(); + lib.write(&mut bytes)?; + let mut offset = 0; + while offset < bytes.len() { + let record_len = u16::from_be_bytes([bytes[offset], bytes[offset + 1]]) as usize; + let record_type = bytes[offset + 2]; + if record_type == GdsRecordType::BgnLib as u8 + || record_type == GdsRecordType::BgnStruct as u8 + { + bytes[offset + 4..offset + record_len].fill(0); + } + offset += record_len; + } + + let parsed = GdsLibrary::from_bytes(&bytes)?; + let epoch = NaiveDate::from_ymd_opt(1970, 1, 1) + .unwrap() + .and_hms_opt(0, 0, 0) + .unwrap(); + assert_eq!(parsed.dates.modified, epoch); + assert_eq!(parsed.dates.accessed, epoch); + assert_eq!(parsed.structs.len(), 1); + assert_eq!(parsed.structs[0].dates.modified, epoch); + assert_eq!(parsed.structs[0].dates.accessed, epoch); + match &parsed.structs[0].elems[0] { + GdsElement::GdsBoundary(boundary) => assert_eq!(boundary.xy, points), + element => panic!("expected boundary, got {:?}", element), + } + Ok(()) +} +#[test] fn it_has_gds_properties() -> GdsResult<()> { // Read a sample let lib = GdsLibrary::load(&resource("has_properties.gds"))?; From b93749d3bdbce1eb1b73106423f3a77769f1e818 Mon Sep 17 00:00:00 2001 From: Zander Milroy Date: Wed, 7 Oct 2026 21:41:35 +0000 Subject: [PATCH 2/3] fix(gds21): coerce invalid GDS datetimes to epoch Replace panicking chrono date constructors in parse_datetimes with fallible opts and fall back to 1970-01-01 so bad metadata cannot abort library load. Co-authored-by: Cursor --- gds21/src/read.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/gds21/src/read.rs b/gds21/src/read.rs index 7ba5d41..e16db17 100644 --- a/gds21/src/read.rs +++ b/gds21/src/read.rs @@ -835,13 +835,28 @@ where if d.len() != 12 { return self.fail("Invalid length GdsDateTimes"); } + let epoch = NaiveDate::from_ymd_opt(1970, 1, 1) + .unwrap() + .and_hms_opt(0, 0, 0) + .unwrap(); + let parse_datetime = |year, month, day, hour, minute, second| { + NaiveDate::from_ymd_opt(year, month, day) + .and_then(|date| date.and_hms_opt(hour, minute, second)) + .unwrap_or(epoch) + }; Ok(GdsDateTimes { - modified: NaiveDate::from_ymd(d[0] as i32, d[1] as u32, d[2] as u32).and_hms( + modified: parse_datetime( + d[0] as i32, + d[1] as u32, + d[2] as u32, d[3] as u32, d[4] as u32, d[5] as u32, ), - accessed: NaiveDate::from_ymd(d[6] as i32, d[7] as u32, d[8] as u32).and_hms( + accessed: parse_datetime( + d[6] as i32, + d[7] as u32, + d[8] as u32, d[9] as u32, d[10] as u32, d[11] as u32, From 5df28e24e34c5eab0cb2700de2c039886ee17e0c Mon Sep 17 00:00:00 2001 From: Zander Milroy Date: Wed, 7 Oct 2026 21:56:34 +0000 Subject: [PATCH 3/3] Use EPOCH constant --- gds21/src/read.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/gds21/src/read.rs b/gds21/src/read.rs index e16db17..2a7b7d8 100644 --- a/gds21/src/read.rs +++ b/gds21/src/read.rs @@ -835,14 +835,10 @@ where if d.len() != 12 { return self.fail("Invalid length GdsDateTimes"); } - let epoch = NaiveDate::from_ymd_opt(1970, 1, 1) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap(); let parse_datetime = |year, month, day, hour, minute, second| { NaiveDate::from_ymd_opt(year, month, day) .and_then(|date| date.and_hms_opt(hour, minute, second)) - .unwrap_or(epoch) + .unwrap_or(NaiveDateTime::UNIX_EPOCH) }; Ok(GdsDateTimes { modified: parse_datetime(