From 5455e9be071a0ba281c181d8adea8710b4461a26 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sat, 24 Oct 2026 01:35:09 +0700 Subject: [PATCH] Cap the total array insert copies in a DXF import Squashed from #3789 onto main. --- crates/cad/src/import/entity.rs | 14 ++++++++++++++ crates/cad/src/import/tests.rs | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/crates/cad/src/import/entity.rs b/crates/cad/src/import/entity.rs index f24f05596..59e1283cc 100644 --- a/crates/cad/src/import/entity.rs +++ b/crates/cad/src/import/entity.rs @@ -17,6 +17,8 @@ const DESCENT: f64 = 0.3; const LINE_SPACING: f64 = 5.0 / 3.0; /// The most copies one array insert (MINSERT) makes. const MAX_CELLS: i64 = 10_000; +/// The most copies all the array inserts of a file make together. +const MAX_TOTAL_CELLS: i64 = 2_000_000; /// Art farther than this from the origin (drawing units) is damaged data, left out. const MAX_COORD: f64 = 1e12; /// How far fit and aligned text may be squeezed or stretched to span their two points: at width @@ -184,12 +186,15 @@ pub(crate) struct Converter { /// Entity types left out, with how many. pub skipped: BTreeMap, pub warnings: Vec, + /// Array insert copies made so far, against [`MAX_TOTAL_CELLS`]. + cells: i64, } const PATTERN_HATCH: &str = "pattern hatches come in as their boundaries"; const GRADIENT_HATCH: &str = "gradient hatches are filled with their first colour"; const MESHES: &str = "3D polygon meshes and polyface meshes are left out"; const BIG_ARRAY: &str = "array inserts are limited to 10,000 copies each"; +const MANY_ARRAYS: &str = "array inserts are limited to 2,000,000 copies in all"; const FAR_AWAY: &str = "entities with coordinates out of range are left out"; impl Converter { @@ -294,6 +299,15 @@ impl Converter { } else { (cols, rows) }; + // Past the file-wide budget, each insert keeps only its first copy. + let left = (MAX_TOTAL_CELLS - self.cells).max(1); + let (cols, rows) = if cols * rows > left { + self.warn(MANY_ARRAYS); + (cols.min(left), (left / cols.min(left)).max(1).min(rows)) + } else { + (cols, rows) + }; + self.cells = self.cells.saturating_add(cols * rows); let cells = (0..rows) .flat_map(|r| (0..cols).map(move |c| (r, c))) .map(|(r, c)| place * Affine::translate((c as f64 * cs, r as f64 * rs)) * Affine::scale_non_uniform(sx, sy)) diff --git a/crates/cad/src/import/tests.rs b/crates/cad/src/import/tests.rs index a7bc39ceb..9e8780513 100644 --- a/crates/cad/src/import/tests.rs +++ b/crates/cad/src/import/tests.rs @@ -1090,3 +1090,20 @@ fn paper_layout_names_are_unique_ignoring_case() { let info = info(&with_layouts(&names)).unwrap(); assert_eq!(info.layouts, ["Model", "Layout1", "Other"]); } + +#[test] +fn many_array_inserts_of_a_missing_block_stay_bounded() { + let one = [(0, "INSERT"), (8, "0"), (2, "NOPE"), (10, "0"), (20, "0"), (70, "100"), (71, "100"), (44, "1"), (45, "1")]; + let e: Vec<(i32, &str)> = (0..3000).flat_map(|_| one).collect(); + let bytes = entities(&e); + let text = reader::decode(&bytes); + let (pairs, _) = reader::pairs(&text).unwrap(); + let d = reader::Drawing::read(&pairs); + let model = d.layout("Model").or(d.layouts.first()).unwrap(); + let mut conv = entity::Converter::default(); + let items = conv.items(&d.layout_records(model)); + assert_eq!(items.len(), 3000); + let total: usize = items.iter().map(|i| if let entity::Geom::Insert(ins) = &i.geom { ins.cells.len() } else { 0 }).sum(); + assert!(total <= 2_000_000 + 3000, "total cells materialised: {total}"); + assert!(conv.warnings.iter().any(|w| w.contains("array inserts")), "{:?}", conv.warnings); +}