From f4250899838bacef99fdbb479df2e86a0165d641 Mon Sep 17 00:00:00 2001 From: Yuu <206304251+nekomario28@users.noreply.github.com> Date: Thu, 15 Oct 2026 02:40:25 +0900 Subject: [PATCH] io: hostile-input tests for the SVG and DXF readers (#469) (#1083) Adds tests that feed truncated, malformed and deeply nested SVG and DXF input to the readers and check that none of it panics. --- crates/io/src/dxf.rs | 33 +++++++++++++++++++++++++++++++++ crates/io/src/svg.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/crates/io/src/dxf.rs b/crates/io/src/dxf.rs index 5e1cb362..5577a265 100644 --- a/crates/io/src/dxf.rs +++ b/crates/io/src/dxf.rs @@ -455,6 +455,39 @@ pub fn write_dxf(geom: &[Geom2]) -> String { mod tests { use super::*; + /// A DXF is group codes and values the user did not write, and blocks recurse. None of these + /// may panic (solidcraft#469). + #[test] + fn hostile_dxf_never_panics() { + let good = write_dxf(&[Geom2::Line(Vec2::ZERO, Vec2::new(10.0, 0.0)), Geom2::Circle(Vec2::new(5.0, 5.0), 2.0)]); + let mut cases: Vec = vec![ + String::new(), + "0".into(), + "0\n".into(), + "garbage".into(), + "0\nSECTION\n2\n".into(), + "0\nSECTION\n2\nENTITIES\n0\nEOF\n".into(), + "0\nSECTION\n2\nENTITIES\n0\nLWPOLYLINE\n90\n4\n10\n0\n20\n0\n0\nENDSEC\n0\nEOF\n".into(), + "0\nSECTION\n2\nENTITIES\n0\nLWPOLYLINE\n90\n-1\n0\nENDSEC\n0\nEOF\n".into(), + "0\nSECTION\n2\nENTITIES\n0\nLWPOLYLINE\n90\n99999999\n0\nENDSEC\n0\nEOF\n".into(), + "0\nSECTION\n2\nENTITIES\n0\nCIRCLE\n10\nnan\n20\ninf\n40\n-1\n0\nENDSEC\n0\nEOF\n".into(), + "0\nSECTION\n2\nENTITIES\n0\nCIRCLE\n10\n1e400\n20\n1e400\n40\n1e400\n0\nENDSEC\n0\nEOF\n".into(), + format!("0\nSECTION\n2\nBLOCKS\n{}0\nENDSEC\n0\nEOF\n", "0\nBLOCK\n2\nb\n0\nENDBLK\n".repeat(300)), + "0\nSECTION\n2\nBLOCKS\n0\nBLOCK\n2\na\n1\nINSERT\n2\na\n0\nENDBLK\n0\nENDSEC\n0\nEOF\n".into(), + format!("0\nSECTION\n2\nENTITIES\n{}0\nENDSEC\n0\nEOF\n", "0\nLINE\n10\n0\n20\n0\n11\n1\n21\n1\n".repeat(5000)), + ]; + for cut in (0..good.len()).step_by(11) { + cases.push(good.get(..cut).unwrap_or("").to_string()); + } + for (from, to) in [("0\n", "1\n"), ("SECTION", "SECTI0N"), ("EOF", "EOFF")] { + cases.push(good.replacen(from, to, 3)); + } + for c in &cases { + let r = std::panic::catch_unwind(|| read_dxf(c)); + assert!(r.is_ok(), "panicked on {:?}", c.get(..c.len().min(200))); + } + } + #[test] fn round_trip_and_polyline_bulges() { let g = vec![ diff --git a/crates/io/src/svg.rs b/crates/io/src/svg.rs index e00d0574..903a5cec 100644 --- a/crates/io/src/svg.rs +++ b/crates/io/src/svg.rs @@ -498,6 +498,42 @@ pub fn read_svg(text: &str) -> Result> { mod tests { use super::*; + /// The parser recurses over groups and reads numbers out of a document the user did not write, + /// so none of these may panic: `catch_unwind` turns one into a failed test, the way + /// `hostile_files_never_panic` does for STEP (solidcraft#469). + #[test] + fn hostile_svg_never_panics() { + let good = r#" + + "#; + let mut cases: Vec = vec![ + String::new(), + "<".into(), + "".into(), + "".into(), + "".into(), + "".into(), + "".into(), + format!("{}{}", "".repeat(300), "".repeat(300)), + format!("{}", "".repeat(300)), + "<g>".into(), + "]>&x;".into(), + format!("", "L 1 1 ".repeat(20_000)), + ]; + for cut in (0..good.len()).step_by(13) { + cases.push(good.get(..cut).unwrap_or("").to_string()); + } + for (from, to) in [("10mm", "-10mm"), ("0 0 100 50", "0 0 0 0"), ("