Check a census row against the field before its writer runs

This commit is contained in:
Dennis Kobert
2026-09-08 13:14:33 +00:00
parent 324925aedb
commit ad03b5b0bf
2 changed files with 41 additions and 4 deletions

View File

@@ -151,6 +151,13 @@ pub struct AttributeInfo {
/// Writes a legacy stored value into a field of this marker, parking
/// droppable payloads. A wrong-typed stored value leaves the field
/// untouched; `None` reports arena exhaustion.
///
/// `dst` must address a live field of *this row's* value type: the glue
/// writes its own `Value`'s worth of bytes there, so a caller resolving the
/// field by [`name`](Self::name) checks [`size`](Self::size) and
/// [`value_type`](Self::value_type) against the field first. The fields
/// here are public and the struct is `Copy`, so a row is a claim about a
/// marker rather than a proof about a field.
pub write_stored: unsafe fn(&dyn AnyAttributeValue, *mut u8, &crate::arena::Arena) -> Option<()>,
}

View File

@@ -133,13 +133,23 @@ impl<'e> RunBuilder<'e> {
/// Writes a legacy stored value on an already pushed lane through the
/// census glue, parking droppable payloads. A marker outside the layout's
/// fields is dropped; a wrong-typed stored value leaves the field's
/// fields is dropped, as is one whose row and field agree on the name but
/// not on the value type; a wrong-typed stored value leaves the field's
/// default. `None` reports arena exhaustion.
pub fn attr_stored(&mut self, lane: usize, info: &crate::attribute::AttributeInfo, value: &dyn crate::list::AnyAttributeValue) -> Option<()> {
assert!(lane < self.pushed, "attributes write onto pushed lanes");
let Some(offset) = self.layout.offset_of(info.name, 0) else { return Some(()) };
// SAFETY: the offset comes from the builder's own layout, and the
// census writer verifies the stored type before touching the field.
let Some(field) = self.layout.fields.iter().find(|field| field.name == info.name && field.level == 0) else {
return Some(());
};
let (offset, size, type_id) = (field.offset, field.size, field.type_id);
// The row reaches the field by name alone, so the writer's own value type
// is checked against the field before its glue writes there, as `push`
// checks the default writer's.
if info.size != size || info.value_type != type_id {
return Some(());
}
// SAFETY: the offset comes from the builder's own layout, and the field
// was just checked to hold the writer's own value type at its size.
unsafe { (info.write_stored)(value, self.frames.add(lane * self.layout.lane_stride() + offset), self.arena) }
}
@@ -938,6 +948,26 @@ mod tests {
assert_eq!(run.attr::<crate::attribute::EditorLayerPath>(0), &[crate::uuid::NodeId(3)]);
}
#[test]
fn a_census_row_pointed_at_another_field_writes_nothing() {
use crate::attribute::{Attribute, Opacity, Transform};
use crate::lane::LaneSource;
let arena = crate::arena::Arena::new(1 << 16).unwrap();
let mut builder = RunBuilder::new(&arena, element_write_hashed::<f64>(), &[FieldWrite::of::<Opacity>(0)], 1).unwrap();
let lane = builder.push(0f64).unwrap();
// The transform writer renamed onto the 8-byte opacity field: a
// 48-byte write past the lane if the name were the only check.
let mut forged = crate::attribute::info(Transform::NAME).unwrap();
forged.name = Opacity::NAME;
builder.attr_stored(lane, &forged, &glam::DAffine2::from_translation((5., 6.).into())).unwrap();
let item = builder.finish();
let run = RunView::<f64>::new(&item).expect("the run holds f64 elements");
assert_eq!(run.attr::<Opacity>(0), 1., "the mistyped row leaves the census default");
}
#[test]
fn a_wrong_typed_or_undeclared_column_leaves_the_default() {
use crate::lane::LaneSource;