Importers: nesting is capped, so a deeply nested file can't overflow the stack

This commit is contained in:
bflatastic
2026-10-14 07:37:52 +07:00
parent 8ab574b7de
commit c10da25ce4
36 changed files with 1481 additions and 38 deletions
+1
View File
@@ -32,6 +32,7 @@ pub(super) fn import(name: &str, bytes: &[u8]) -> Result<Imported> {
Ok(d) => {
let mut b = Builder { doc: Document::new(0.0, 0.0), warnings: Vec::new(), k: 72.0 / d.dpi, layer: 0 };
b.document(&d)?;
b.doc.limit_depth_noting(&mut b.warnings);
let mut warnings = d.warnings.iter().map(|w| format!("Affinity: not imported or approximated: {w}.")).collect::<Vec<_>>();
warnings.extend(b.warnings.iter().map(|w| format!("Affinity: {w}.")));
Ok(Imported { doc: b.doc, warnings, preview_only: false })
+2 -1
View File
@@ -139,7 +139,8 @@ fn restore_or_import(
/// (with a warning for each that can't be used).
pub(crate) fn native_file(bytes: &[u8]) -> Result<(Document, vectorcraft_format::FileInfo, Vec<String>)> {
let f = vectorcraft_format::load_file(bytes).map_err(err)?;
let warnings = super::native::install_profiles(f.profiles);
let mut warnings = super::native::install_profiles(f.profiles);
warnings.extend(f.depth_notes);
Ok((f.doc, f.info, warnings))
}
+12 -1
View File
@@ -583,6 +583,10 @@ pub(crate) fn is_guide_path(d: &Document, id: NodeId) -> bool {
d.node(id).is_some_and(|n| matches!(n.kind, NodeKind::Path { guide: true, .. }))
}
/// The error of an edit that would nest objects deeper than [`vectorcraft_doc::MAX_DEPTH`].
pub const TOO_DEEP: &str = "objects can't be nested more than 32 deep";
const _: () = assert!(vectorcraft_doc::MAX_DEPTH == 32, "TOO_DEEP (and its catalog rows) names the cap");
/// Cheap sanity check after an edit: artboards and the objects just touched (the selection) must
/// have finite, in-range geometry, so saved files always reload and renderers never see NaN/∞.
fn doc_sane(d: &Document, sel: &Selection) -> bool {
@@ -1245,6 +1249,9 @@ impl Session {
}
/// Add a document and make it active.
pub fn add_document(&mut self, mut doc: Document, path: Option<String>) -> usize {
// Every edit is checked against the document before it, so the first one is within the cap
// (importers and the native reader have seen to theirs already, with a warning).
doc.limit_depth();
Self::refresh_text_bounds(&mut doc);
// Guides from files saved before guides had layers go on a layer.
doc.adopt_guides();
@@ -1262,6 +1269,7 @@ impl Session {
if index >= self.docs.len() {
return false;
}
doc.limit_depth();
Self::refresh_text_bounds(&mut doc);
doc.adopt_guides();
if self.active == Some(index) {
@@ -1476,7 +1484,10 @@ impl Session {
if !st.doc.variables.bindings.is_empty() {
Arc::make_mut(&mut st.doc).prune_variable_bindings();
}
if doc_sane(&st.doc, &st.selection) {
if st.doc.too_deep(Some(&before)) {
// Deeper trees overflow the stack of what walks them.
Err(EngineError::Other(TOO_DEEP.into()))
} else if doc_sane(&st.doc, &st.selection) {
Ok(v)
} else {
Err(EngineError::Other("result would exceed the canvas (coordinates out of range)".into()))