From 548cbfbf4e6c07cce596eeb75628b9470612d8b0 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Mon, 7 Sep 2026 16:11:32 +0000 Subject: [PATCH] Check the claim's layout when replaying an owned record --- .../libraries/core-types/src/record/owned.rs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/node-graph/libraries/core-types/src/record/owned.rs b/node-graph/libraries/core-types/src/record/owned.rs index da32fbfbca..1147535f24 100644 --- a/node-graph/libraries/core-types/src/record/owned.rs +++ b/node-graph/libraries/core-types/src/record/owned.rs @@ -87,7 +87,10 @@ pub fn replay_field_value(value: &dyn crate::list::AnyAttributeValue, arena: &cr pub struct OwnedRecord { pub(in crate::record) bytes: Box<[u8]>, pub(in crate::record) element: Option>, - fields: Vec<(usize, Box)>, + /// Each copied field's index in the copy's layout, that field's declared + /// value type, and the owned value. The type rides along so a replay can + /// re-check the field it resolves rather than trusting the caller's claim. + fields: Vec<(usize, std::any::TypeId, Box)>, } impl std::fmt::Debug for OwnedRecord { @@ -114,16 +117,25 @@ impl OwnedRecord { .iter() .enumerate() .filter(|(_, field)| field.repark.is_some()) - .map(|(index, field)| (index, deepen_field_value(unsafe { (field.read_erased)(rec.ptr().add(field.offset)) }))) + .map(|(index, field)| (index, field.type_id, deepen_field_value(unsafe { (field.read_erased)(rec.ptr().add(field.offset)) }))) .collect(); OwnedRecord { bytes, element, fields } } /// Replays the copy into a caller's claim, re-parking droppable payloads - /// against `arena`; the claim's layout is the one the copy was taken at, - /// and `None` reports arena exhaustion. + /// against `arena`; `None` reports arena exhaustion. The claim's layout must + /// be the one the copy was taken at, which the checks below establish rather + /// than assume: the copy carries no layout of its own, so a mismatched claim + /// would otherwise write past the frame or re-park a field through another + /// field's glue. pub fn replay_into(&self, slot: &mut FrameClaim<'_, '_>, arena: &crate::arena::Arena) -> Option<()> { let layout = slot.layout; + assert_eq!(self.bytes.len(), layout.size, "a replay lands in the layout the copy was taken at"); + assert_eq!(self.element.is_some(), layout.element.parked, "a replay lands in the layout the copy was taken at"); + for &(index, type_id, _) in &self.fields { + let field = layout.fields.get(index).expect("a replay lands in the layout the copy was taken at"); + assert_eq!(field.type_id, type_id, "a replay lands in the layout the copy was taken at"); + } self.write_into(layout, slot.dst(), arena) } @@ -132,7 +144,7 @@ impl OwnedRecord { if let Some(element) = &self.element { unsafe { (layout.element.repark)(&**element, dst, arena) }?; } - for (index, value) in &self.fields { + for (index, _, value) in &self.fields { let field = &layout.fields[*index]; let repark = field.repark.expect("copied fields carry re-park glue"); let resident = replay_field_value(&**value, arena)?;