From 11aedd8196ac3efa93914c8affb8e08aca042e50 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Mon, 7 Sep 2026 16:03:10 +0000 Subject: [PATCH] Refuse record layouts wider than the frame tier's alignment --- .../libraries/core-types/src/record/layout.rs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/node-graph/libraries/core-types/src/record/layout.rs b/node-graph/libraries/core-types/src/record/layout.rs index dfa2c7327c..67edd1c973 100644 --- a/node-graph/libraries/core-types/src/record/layout.rs +++ b/node-graph/libraries/core-types/src/record/layout.rs @@ -155,6 +155,11 @@ impl Default for ElementWrite { } } +/// The widest alignment a record may need. Frames are a `Vec`, lanes +/// stride at a multiple of 8, and run slabs come from `alloc_scratch::`, +/// so nothing below a record can promise more. +pub const MAX_ALIGN: usize = 8; + /// A record layout: the element at offset 0, then the written attributes in /// canonical order (descending alignment, then size, then name, then level). /// Layouts are derived data, a pure function of the upstream write set. @@ -198,8 +203,9 @@ impl Layout { /// The union of this layout's fields and `writes` over `element` at /// `depth`, in canonical order. A (name, level) written at a different - /// size is a type conflict and panics; the census keeps declared names to - /// one type, so this only fires on wiring bugs. + /// size is a type conflict and panics, as does an element or attribute + /// wider than [`MAX_ALIGN`]; the census keeps declared names to one type, + /// so these only fire on wiring bugs. pub fn with_writes(&self, depth: u8, element: ElementWrite, writes: &[FieldWrite]) -> Layout { let mut merged: Vec = self.fields.iter().map(FieldDesc::as_write).collect(); for &write in writes { @@ -208,6 +214,10 @@ impl Layout { None => merged.push(write), } } + assert!(element.align <= MAX_ALIGN, "a record element aligns to at most {MAX_ALIGN} bytes, but this one needs {}", element.align); + for write in &merged { + assert!(write.align <= MAX_ALIGN, "attribute `{}` aligns to at most {MAX_ALIGN} bytes, but needs {}", write.name, write.align); + } merged.sort_by(|a, b| b.align.cmp(&a.align).then(b.size.cmp(&a.size)).then(a.name.cmp(b.name)).then(a.level.cmp(&b.level))); let mut offset = element.size; let mut align = element.align.max(1); @@ -619,6 +629,18 @@ mod tests { assert_eq!(layout.align, 8); } + #[test] + #[should_panic(expected = "a record element aligns to at most 8 bytes")] + fn an_over_aligned_element_is_refused_at_wiring() { + Layout::default().with_writes(0, element_write::(), &[]); + } + + #[test] + #[should_panic(expected = "attribute `wide` aligns to at most 8 bytes")] + fn an_over_aligned_attribute_is_refused_at_wiring() { + Layout::default().with_writes(0, element_write::(), &[sized_field("wide", 16, 16)]); + } + #[test] #[should_panic(expected = "two different types")] fn type_conflicts_panic() {