From 07762956d2e61dfbb1d5b66c103fe0e8a24319fc Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sat, 24 Oct 2026 01:35:50 +0700 Subject: [PATCH] Cap path elements in DXF import across the whole file Squashed from #3800 onto main. --- crates/cad/src/import/build.rs | 12 ++++++++++++ crates/cad/src/import/tests.rs | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/crates/cad/src/import/build.rs b/crates/cad/src/import/build.rs index 1df9c0ea1..fcc87a71f 100644 --- a/crates/cad/src/import/build.rs +++ b/crates/cad/src/import/build.rs @@ -25,12 +25,15 @@ const THINNEST: f64 = 0.25; const MAX_NODES: usize = 2_000_000; /// The most block visits (one per insert copy) an import makes, whatever they produce. const MAX_VISITS: usize = 10_000_000; +/// The most path elements an import makes (blocks drawn as art are copied for every placement). +const MAX_ELEMENTS: usize = if cfg!(test) { 1_000_000 } else { 4_000_000 }; /// How far from the origin art may lie (points): the canvas. pub(crate) const MAX_EXTENT: f64 = 4.0e6; const NESTED: &str = "blocks nested more than 8 deep, or inside themselves, are left out"; const XREFS: &str = "blocks referencing other drawings (external references) are left out"; const TOO_MANY: &str = "the drawing has more than 2,000,000 objects: the rest are left out"; +const TOO_MANY_POINTS: &str = "the drawing has more than 4,000,000 path points: the rest are left out"; /// What a block's art takes from the insert that places it: its colour, lineweight, linetype /// and opacity where the art says "by block", and its layer for art on layer 0. @@ -66,6 +69,8 @@ pub(crate) struct Build<'d, 'a> { lw_scale: f64, nodes: usize, visits: usize, + /// Path elements made so far. + elements: usize, warnings: Vec, } @@ -122,6 +127,7 @@ impl<'d, 'a> Build<'d, 'a> { lw_scale: 1.0, nodes: 0, visits: 0, + elements: 0, warnings: vec![], } } @@ -451,6 +457,12 @@ impl<'d, 'a> Build<'d, 'a> { } fn path(&mut self, bp: &BezPath, fill: bool, width: f64, p: &Props, map: Affine, inh: &Inherit) -> Option { + let n = bp.elements().len(); + if self.elements.saturating_add(n) > MAX_ELEMENTS { + self.warn(TOO_MANY_POINTS); + return None; + } + self.elements += n; let mut bp = bp.clone(); bp.apply_affine(map); super::curve::bounds(&bp)?; diff --git a/crates/cad/src/import/tests.rs b/crates/cad/src/import/tests.rs index 61ba1dbf4..d0dde65c9 100644 --- a/crates/cad/src/import/tests.rs +++ b/crates/cad/src/import/tests.rs @@ -817,6 +817,30 @@ fn nested_array_inserts_of_nothing_stop_at_the_work_budget() { assert!(r.warnings.iter().any(|w| w.contains("more than")), "{:?}", r.warnings); } +#[test] +fn an_array_of_a_large_anonymous_block_stops_at_the_path_element_budget() { + // A 2,000-vertex polyline in *U1, placed in a 100 × 10 array: 2,000,000 path elements when + // every cell is copied, twice the budget tests import with. + let mut owned: Vec<(i32, String)> = vec![(0, "BLOCK".into()), (2, "*U1".into()), (70, "1".into()), (10, "0".into()), (20, "0".into())]; + owned.extend([(0, "LWPOLYLINE".into()), (90, "2000".into()), (70, "0".into())]); + for i in 0..2000 { + owned.extend([(10, i.to_string()), (20, (i % 2).to_string())]); + } + owned.push((0, "ENDBLK".into())); + let blocks: Vec<(i32, &str)> = owned.iter().map(|(c, v)| (*c, v.as_str())).collect(); + let inserts = [(0, "INSERT"), (2, "*U1"), (10, "0"), (20, "0"), (70, "100"), (71, "10"), (44, "2000"), (45, "2")]; + let r = open(&drawing(&[], &[], &blocks, &inserts), &points()); + let mut elements = 0; + r.document.walk(|n| { + if let NodeKind::Path { path, .. } = &n.kind { + elements += path.to_bezpath().elements().len(); + } + }); + assert!(elements > 0); + assert!(elements <= 1_000_000, "{elements} path elements"); + assert!(r.warnings.iter().any(|w| w.contains("path points")), "{:?}", r.warnings); +} + #[test] fn blocks_inside_themselves_are_left_out_without_hanging() { let blocks = [