From 2e1c029fa82efeaae0b9c339a6b1ad3ea6d90f08 Mon Sep 17 00:00:00 2001 From: aiongg Date: Thu, 22 Oct 2026 15:18:26 +0000 Subject: [PATCH] fix(xfa): every flowed subform instance spends the layout budget Nested repeating subforms multiplied their instances (up to 50 per level over 64 levels), and an empty instance neither measured nor placed anything, so it spent none of the measurement or item budgets. Each instance now counts as a measurement, and layout stops with TooLarge once the budget is spent. --- crates/xfa/src/layout.rs | 3 +++ crates/xfa/src/tests.rs | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/crates/xfa/src/layout.rs b/crates/xfa/src/layout.rs index 326e7f741..b4d331255 100644 --- a/crates/xfa/src/layout.rs +++ b/crates/xfa/src/layout.rs @@ -769,6 +769,9 @@ impl Layouter<'_> { self.warn_truncated(node, &ctx.som, instances); let ctx_parent = ctx; for i in 0..instances { + // Every instance spends budget, so nested repeats that place nothing still stop. + self.measures.set(self.measures.get().saturating_add(1)); + self.check_measures()?; let ctx = ctx_parent.with_som(&sf.common, sib + i); let (rect, _, _) = self.cursor(); let (x, w) = avail.unwrap_or((rect.x, rect.w)); diff --git a/crates/xfa/src/tests.rs b/crates/xfa/src/tests.rs index 84c9fc00f..c5de7ad88 100644 --- a/crates/xfa/src/tests.rs +++ b/crates/xfa/src/tests.rs @@ -404,6 +404,18 @@ fn repeated_pictures_are_bounded_in_total_bytes() { assert!(layout_xml(&ok).is_ok(), "one small picture must lay out"); } +#[test] +fn empty_repeating_subforms_spend_the_layout_budget() { + // Four nested levels of 50 empty instances are 6.25 million instances that place nothing + // and measure nothing; each one counts against the layout budget. + let level = ""; + let tpl = format!("", level.repeat(4), "".repeat(4)); + assert!(matches!(layout_xml(&tpl), Err(XfaError::TooLarge(_)))); + // Three levels (125,000 instances) still lay out. + let tpl = format!("", level.repeat(3), "".repeat(3)); + assert!(layout_xml(&tpl).is_ok()); +} + #[test] fn the_packets_can_be_one_stream_or_named_parts() { // Named parts, with the template split from the rest.