mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Justify the unsafe sites in the arena, record and graphic glue
This commit is contained in:
@@ -54,6 +54,9 @@ struct DropEntry {
|
||||
|
||||
/// The glue a tombstoned entry carries: its payload was moved to another arena,
|
||||
/// which now owns the obligation.
|
||||
///
|
||||
/// # Safety
|
||||
/// None: the payload is neither read nor dropped, so any pointer is accepted.
|
||||
unsafe fn inert(_: *mut u8) {}
|
||||
|
||||
/// The entry parking `offset`, `None` where the arena holds no obligation for
|
||||
@@ -237,11 +240,15 @@ impl Arena {
|
||||
// Built before the write so an unencodable offset drops `value` here
|
||||
// rather than stranding it in the arena without drop glue.
|
||||
let weak = ArenaWeak::new(self.generation(), offset)?;
|
||||
// SAFETY: `reserve` returned `offset`, so it is within the backbone.
|
||||
let ptr = unsafe { self.base().add(offset) }.cast::<T>();
|
||||
// SAFETY: freshly reserved, aligned, in-bounds, unaliased.
|
||||
unsafe { ptr.write(value) };
|
||||
if std::mem::needs_drop::<T>() {
|
||||
/// # Safety
|
||||
/// `p` must address the live `T` this entry was registered for.
|
||||
unsafe fn glue<T>(p: *mut u8) {
|
||||
// SAFETY: the caller's contract.
|
||||
unsafe { p.cast::<T>().drop_in_place() }
|
||||
}
|
||||
self.drops.lock().unwrap().push(DropEntry { offset, type_of, drop_fn: glue::<T>, retained });
|
||||
@@ -315,7 +322,10 @@ impl Arena {
|
||||
let parked = std::mem::replace(&mut entries[entry].retained, 0);
|
||||
entries[entry].drop_fn = inert;
|
||||
drop(entries);
|
||||
/// # Safety
|
||||
/// `p` must address the live `T` this entry was registered for.
|
||||
unsafe fn glue<T>(p: *mut u8) {
|
||||
// SAFETY: the caller's contract.
|
||||
unsafe { p.cast::<T>().drop_in_place() }
|
||||
}
|
||||
dst.drops.lock().unwrap().push(DropEntry {
|
||||
@@ -345,6 +355,7 @@ impl Arena {
|
||||
pub fn alloc_scratch<T: Send + Sync>(&self, len: usize) -> Option<&mut [MaybeUninit<T>]> {
|
||||
let size = size_of::<T>().checked_mul(len)?;
|
||||
let offset = self.reserve(size, align_of::<T>())?;
|
||||
// SAFETY: `reserve` returned `offset`, so it is within the backbone.
|
||||
let ptr = unsafe { self.base().add(offset) }.cast::<MaybeUninit<T>>();
|
||||
// SAFETY: exclusive region; lifetime tied to `&self`, and `reset` takes
|
||||
// `&mut self`, so the slice cannot outlive the generation.
|
||||
|
||||
@@ -199,12 +199,15 @@ where
|
||||
crate::record::FieldWrite::of::<A>(level)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `dst` must address a live field of `A`'s value type.
|
||||
unsafe fn write_stored<A: Attribute>(stored: &dyn AnyAttributeValue, dst: *mut u8, arena: &crate::arena::Arena) -> Option<()> {
|
||||
if A::from_stored(stored.as_any()).is_none() {
|
||||
// A wrong-typed stored value reads as absent, so the field keeps its default.
|
||||
return Some(());
|
||||
}
|
||||
match A::REPARK {
|
||||
// SAFETY: the caller's contract; the glue is this marker's own.
|
||||
Some(repark) => unsafe { repark(stored, dst, arena) },
|
||||
None => {
|
||||
let value = A::from_stored(stored.as_any()).expect("checked above");
|
||||
|
||||
@@ -375,6 +375,7 @@ impl CacheHash for AttributeDyn {
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: the type carries no lifetime, so it is its own static form.
|
||||
unsafe impl StaticType for AttributeDyn {
|
||||
type Static = Self;
|
||||
}
|
||||
@@ -418,6 +419,7 @@ impl CacheHash for AttributeValueDyn {
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: as for AttributeDyn.
|
||||
unsafe impl StaticType for AttributeValueDyn {
|
||||
type Static = Self;
|
||||
}
|
||||
@@ -501,6 +503,7 @@ impl CacheHash for ListDyn {
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: as for AttributeDyn.
|
||||
unsafe impl StaticType for ListDyn {
|
||||
type Static = Self;
|
||||
}
|
||||
@@ -1190,6 +1193,8 @@ impl<T> ApplyTransform for List<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: the list carries its lifetime only through T, so substituting T's
|
||||
// static form substitutes the list's and keeps the layout identical.
|
||||
unsafe impl<T: StaticTypeSized> StaticType for List<T> {
|
||||
type Static = List<T::Static>;
|
||||
}
|
||||
|
||||
@@ -214,6 +214,7 @@ impl<'a, 'e, N> RecordInput<'a, 'e, N> {
|
||||
/// `rec` must be a record of the layout the offsets were resolved against
|
||||
/// and `El` its element type; both are proven at wiring.
|
||||
unsafe fn element_only<El: Clone>(rec: Rec<'_>, _reads: &[Option<usize>]) -> El {
|
||||
// SAFETY: the caller's contract.
|
||||
unsafe { read_element::<El>(rec) }
|
||||
}
|
||||
|
||||
@@ -516,6 +517,8 @@ impl<El: Clone + 'static, N> RecordExtract<El, N> {
|
||||
// The element copies out by value, so the input's claim dies with
|
||||
// the scope.
|
||||
let scope = frames.scope();
|
||||
// SAFETY: the served value is a record of `self.layout`, whose element is
|
||||
// `El` by the wiring that built this extract.
|
||||
serve_input(&self.edge, input, &scope).map(|value| unsafe { read_element::<El>(self.layout.rec(&value)) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,17 @@ impl FieldWrite {
|
||||
where
|
||||
A::Value<'static>: graphene_hash::CacheHash + PartialEq + 'static,
|
||||
{
|
||||
/// # Safety
|
||||
/// `ptr` must address a live `V`.
|
||||
unsafe fn content_hash<V: graphene_hash::CacheHash>(ptr: *const u8, state: &mut dyn core::hash::Hasher) {
|
||||
let mut state = state;
|
||||
// SAFETY: the caller's contract.
|
||||
unsafe { &*ptr.cast::<V>() }.cache_hash(&mut state);
|
||||
}
|
||||
/// # Safety
|
||||
/// `a` and `b` must both address a live `V`.
|
||||
unsafe fn content_eq<V: PartialEq>(a: *const u8, b: *const u8) -> bool {
|
||||
// SAFETY: the caller's contract.
|
||||
unsafe { *a.cast::<V>() == *b.cast::<V>() }
|
||||
}
|
||||
Self {
|
||||
@@ -132,12 +138,18 @@ impl Eq for ElementWrite {}
|
||||
|
||||
impl Default for ElementWrite {
|
||||
fn default() -> Self {
|
||||
/// # Safety
|
||||
/// None: the empty element has no bytes, so nothing is read.
|
||||
unsafe fn clone_out(_ptr: *const u8) -> Box<dyn std::any::Any + Send + Sync> {
|
||||
Box::new(())
|
||||
}
|
||||
/// # Safety
|
||||
/// None: nothing is written, the empty element having no slot.
|
||||
unsafe fn repark(_value: &(dyn std::any::Any + Send + Sync), _dst: *mut u8, _arena: &crate::arena::Arena) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
/// # Safety
|
||||
/// None: the move always declines, so `parked` is never read.
|
||||
unsafe fn park_move(_parked: *const u8, _promotion: &Promotion<'_>) -> Option<*const u8> {
|
||||
None
|
||||
}
|
||||
@@ -508,11 +520,14 @@ pub fn element_write<T: Clone + Send + Sync + dyn_any::StaticTypeSized>() -> Ele
|
||||
where
|
||||
T::Static: Clone + Send + Sync,
|
||||
{
|
||||
/// # Safety
|
||||
/// `ptr` must address a live element of `T` in the form [`element_parked`] picks.
|
||||
unsafe fn clone_out<T: Clone + Send + Sync + dyn_any::StaticTypeSized>(ptr: *const u8) -> Box<dyn std::any::Any + Send + Sync>
|
||||
where
|
||||
T::Static: Clone + Send + Sync,
|
||||
{
|
||||
if let Some(deep) = deep_element_glue(std::any::TypeId::of::<T::Static>()) {
|
||||
// SAFETY: the caller's contract; the glue is registered for this element type.
|
||||
return unsafe { (deep.clone_out)(ptr) };
|
||||
}
|
||||
// SAFETY: a lifetime-carrying element type registers deep glue, so this
|
||||
@@ -522,11 +537,15 @@ where
|
||||
// which is where a missed wasm registration export is caught.
|
||||
Box::new(unsafe { erase_static(read_element::<T>(Rec::new(ptr))) })
|
||||
}
|
||||
/// # Safety
|
||||
/// `dst` must be fresh element storage of a record whose element is `T`, and
|
||||
/// `value` must be the clone-out this glue produced for that element type.
|
||||
unsafe fn repark<T: Clone + Send + Sync + dyn_any::StaticTypeSized>(value: &(dyn std::any::Any + Send + Sync), dst: *mut u8, arena: &crate::arena::Arena) -> Option<()>
|
||||
where
|
||||
T::Static: Clone + Send + Sync,
|
||||
{
|
||||
if let Some(deep) = deep_element_glue(std::any::TypeId::of::<T::Static>()) {
|
||||
// SAFETY: the caller's contract; the glue is registered for this element type.
|
||||
return unsafe { (deep.repark)(value, dst, arena) };
|
||||
}
|
||||
let retained = retained_measure(std::any::TypeId::of::<T::Static>()).map_or(0, |measure| measure(value));
|
||||
@@ -571,11 +590,17 @@ pub fn element_write_hashed<T: Clone + Send + Sync + graphene_hash::CacheHash +
|
||||
where
|
||||
T::Static: Clone + Send + Sync,
|
||||
{
|
||||
/// # Safety
|
||||
/// `ptr` must address a live element of `T` in the form [`element_parked`] picks.
|
||||
unsafe fn content_hash<T: graphene_hash::CacheHash>(ptr: *const u8, state: &mut dyn core::hash::Hasher) {
|
||||
let mut state = state;
|
||||
// SAFETY: the caller's contract.
|
||||
unsafe { borrow_element::<T>(Rec::new(ptr)) }.cache_hash(&mut state);
|
||||
}
|
||||
/// # Safety
|
||||
/// `a` and `b` must each address a live element of `T` in that same form.
|
||||
unsafe fn content_eq<T: PartialEq>(a: *const u8, b: *const u8) -> bool {
|
||||
// SAFETY: the caller's contract.
|
||||
unsafe { borrow_element::<T>(Rec::new(a)) == borrow_element::<T>(Rec::new(b)) }
|
||||
}
|
||||
ElementWrite {
|
||||
|
||||
@@ -113,17 +113,21 @@ impl OwnedRecord {
|
||||
// The element-to-field seam is never written, so the copy stays untyped:
|
||||
// a `&[u8]` over the frame would read those bytes.
|
||||
let mut staged = Vec::<u8>::with_capacity(layout.size);
|
||||
// SAFETY: the caller's contract sizes the record at `layout.size`, which
|
||||
// is the capacity just reserved, so the copy fills exactly the staging.
|
||||
let bytes = unsafe {
|
||||
std::ptr::copy_nonoverlapping(rec.ptr(), staged.as_mut_ptr(), layout.size);
|
||||
staged.set_len(layout.size);
|
||||
staged.into_boxed_slice()
|
||||
};
|
||||
// SAFETY: the caller's contract; a parked element sits at offset 0.
|
||||
let element = layout.element.parked.then(|| unsafe { (layout.element.clone_out)(rec.ptr()) });
|
||||
let fields = layout
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, field)| field.repark.is_some())
|
||||
// SAFETY: the caller's contract; each field reads its own descriptor's 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 }
|
||||
@@ -146,15 +150,21 @@ impl OwnedRecord {
|
||||
self.write_into(layout, slot.dst(), arena)
|
||||
}
|
||||
|
||||
/// `dst` is a claimed frame of `layout`, and `replay_into`'s asserts have
|
||||
/// established that `layout` is the one the copy was taken at.
|
||||
fn write_into(&self, layout: &Layout, dst: *mut u8, arena: &crate::arena::Arena) -> Option<()> {
|
||||
// SAFETY: the copy is `layout.size` bytes and the claim is a frame of it.
|
||||
unsafe { std::ptr::copy_nonoverlapping(self.bytes.as_ptr(), dst, self.bytes.len()) };
|
||||
if let Some(element) = &self.element {
|
||||
// SAFETY: the element was cloned out of this layout's own slot at offset 0.
|
||||
unsafe { (layout.element.repark)(&**element, dst, arena) }?;
|
||||
}
|
||||
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)?;
|
||||
// SAFETY: the asserts matched this index's field type, so the glue and
|
||||
// the value agree; the write lands in that field's own region.
|
||||
unsafe { repark(resident.as_deref().unwrap_or(&**value), dst.add(field.offset), arena) }?;
|
||||
}
|
||||
Some(())
|
||||
|
||||
@@ -99,6 +99,7 @@ pub(in crate::record) unsafe fn promote_record(layout: &Layout, dst: *mut u8, pr
|
||||
None => {
|
||||
// SAFETY: as above, and the clone owns its content.
|
||||
let owned = unsafe { (layout.element.clone_out)(dst.cast_const()) };
|
||||
// SAFETY: the clone is this element's own type, back into its slot.
|
||||
unsafe { (layout.element.repark)(&*owned, dst, promotion.persistent) }?;
|
||||
}
|
||||
},
|
||||
@@ -243,6 +244,7 @@ impl MaterializedSpan {
|
||||
// SAFETY: the caller's contract on the lane, into the lane's own
|
||||
// region of the freshly reserved slab.
|
||||
let dst = unsafe { base.add(lane * stride) };
|
||||
// SAFETY: as above; `layout.size` bytes of a lane fit its own stride.
|
||||
unsafe { std::ptr::copy_nonoverlapping(batch.get(lane).rec().ptr(), dst, layout.size) };
|
||||
// SAFETY: the copy images a record of this layout.
|
||||
unsafe { promote_record(layout, dst, promotion) }?;
|
||||
|
||||
@@ -51,6 +51,8 @@ impl SourcePlan {
|
||||
/// buffer of the plan's union layout that does not overlap `src`. The
|
||||
/// returned view borrows `dst`, so `'d` must not outlive it.
|
||||
pub unsafe fn translate<'d>(&self, src: Rec<'_>, dst: *mut u8) -> Rec<'d> {
|
||||
// SAFETY: the caller's contract; the plan's moves and fills name only
|
||||
// offsets of the layouts it was built from.
|
||||
unsafe {
|
||||
apply_plan(src, dst, &self.moves);
|
||||
for (offset, bytes) in &self.fills {
|
||||
|
||||
@@ -96,6 +96,8 @@ impl<'e> RunBuilder<'e> {
|
||||
// `lane` is below `len`; the element slot and each field's region are
|
||||
// disjoint parts of this lane.
|
||||
let base = unsafe { self.frames.add(lane * stride) };
|
||||
// SAFETY: the element slot is fresh, and the assert above matched `T` to
|
||||
// the layout's element type.
|
||||
unsafe { write_element(base, element, self.arena) }?;
|
||||
for field in &self.layout.fields {
|
||||
// SAFETY: as above; the field region is within the lane.
|
||||
@@ -240,6 +242,7 @@ impl<'e> GroupItem<'e> {
|
||||
for lane in 0..batch.len() {
|
||||
// SAFETY: both sides hold `len` lanes at the shared layout's stride.
|
||||
let dst = unsafe { frames.add(lane * stride) };
|
||||
// SAFETY: as above; the scratch was reserved for exactly these lanes.
|
||||
unsafe { std::ptr::copy_nonoverlapping(batch.get(lane).rec().ptr(), dst, stride) };
|
||||
if parked {
|
||||
// SAFETY: the copy images a live record of this layout.
|
||||
@@ -355,6 +358,7 @@ impl<'e> GroupItem<'e> {
|
||||
.layout
|
||||
.element
|
||||
.parked
|
||||
// SAFETY: as above; a parked element sits at each lane's offset 0.
|
||||
.then(|| (0..self.len).map(|lane| unsafe { (self.layout.element.clone_out)(frames.add(lane * stride)) }).collect());
|
||||
let fields = self
|
||||
.layout
|
||||
@@ -363,6 +367,7 @@ impl<'e> GroupItem<'e> {
|
||||
.enumerate()
|
||||
.filter(|(_, field)| field.repark.is_some())
|
||||
.map(|(index, field)| {
|
||||
// SAFETY: as above; each lane reads this descriptor's own offset.
|
||||
let mut values: Vec<_> = (0..self.len).map(|lane| unsafe { (field.read_erased)(frames.add(lane * stride + field.offset)) }).collect();
|
||||
if let Some(glue) = values.first().and_then(|value| deep_field_glue(value.as_any().type_id())) {
|
||||
for value in &mut values {
|
||||
@@ -400,6 +405,7 @@ impl<'e> GroupItem<'e> {
|
||||
unsafe { std::ptr::copy_nonoverlapping(owned.bytes.as_ptr(), frames, owned.bytes.len()) };
|
||||
if let Some(elements) = &owned.elements {
|
||||
for (lane, element) in elements.iter().enumerate() {
|
||||
// SAFETY: as above; the clone is this element's own type, into its lane's slot.
|
||||
unsafe { (self.layout.element.repark)(&**element, frames.add(lane * stride), arena) }?;
|
||||
}
|
||||
}
|
||||
@@ -408,6 +414,8 @@ impl<'e> GroupItem<'e> {
|
||||
let repark = field.repark.expect("copied fields carry re-park glue");
|
||||
let glue = values.first().and_then(|value| deep_field_glue(value.as_any().type_id()));
|
||||
for (lane, value) in values.iter().enumerate() {
|
||||
// SAFETY: the copy took these values through this field's own glue,
|
||||
// so each writes its own type into its lane's region of the field.
|
||||
match glue {
|
||||
Some(glue) => match (glue.replay)(&**value, arena)? {
|
||||
Some(resident) => unsafe { repark(&*resident, frames.add(lane * stride + field.offset), arena) }?,
|
||||
@@ -600,14 +608,20 @@ impl<'e> Group<'e> {
|
||||
/// touches. The element always carries glue (`assert_element_glue`) and
|
||||
/// `FieldWrite::of` always installs field glue, so a padded value's own
|
||||
/// padding never reaches the byte fallback either.
|
||||
///
|
||||
/// # Safety
|
||||
/// `a` and `b` must both be live records of `layout`.
|
||||
unsafe fn record_content_eq(layout: &Layout, a: *const u8, b: *const u8) -> bool {
|
||||
// SAFETY: the caller's contract; each call reads a written span of the layout.
|
||||
let bytes_eq = |offset: usize, size: usize| unsafe { std::slice::from_raw_parts(a.add(offset), size) == std::slice::from_raw_parts(b.add(offset), size) };
|
||||
let element = match layout.element.content_eq {
|
||||
// SAFETY: the caller's contract; the glue is the element's own.
|
||||
Some(eq) => unsafe { eq(a, b) },
|
||||
None => bytes_eq(0, layout.element.size),
|
||||
};
|
||||
element
|
||||
&& layout.fields.iter().all(|field| match field.content_eq {
|
||||
// SAFETY: as above, at this descriptor's own offset.
|
||||
Some(eq) => unsafe { eq(a.add(field.offset), b.add(field.offset)) },
|
||||
None => bytes_eq(field.offset, field.size),
|
||||
})
|
||||
@@ -615,14 +629,21 @@ unsafe fn record_content_eq(layout: &Layout, a: *const u8, b: *const u8) -> bool
|
||||
|
||||
/// Hashes one record region of `layout` by content, with the byte fallback
|
||||
/// of [`record_content_eq`].
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must be a live record of `layout`.
|
||||
unsafe fn record_content_hash(layout: &Layout, ptr: *const u8, state: &mut dyn core::hash::Hasher) {
|
||||
match layout.element.content_hash {
|
||||
// SAFETY: the caller's contract; the glue is the element's own.
|
||||
Some(hash) => unsafe { hash(ptr, state) },
|
||||
// SAFETY: as above; the element's own bytes are written.
|
||||
None => state.write(unsafe { std::slice::from_raw_parts(ptr, layout.element.size) }),
|
||||
}
|
||||
for field in &layout.fields {
|
||||
match field.content_hash {
|
||||
// SAFETY: as above, at this descriptor's own offset.
|
||||
Some(hash) => unsafe { hash(ptr.add(field.offset), state) },
|
||||
// SAFETY: as above; the field's own bytes are written.
|
||||
None => state.write(unsafe { std::slice::from_raw_parts(ptr.add(field.offset), field.size) }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ impl<'e> Artboard<'e> {
|
||||
/// # Safety
|
||||
/// `ptr` must point at a live parked `Artboard` element field.
|
||||
unsafe fn deep_clone_artboard(ptr: *const u8) -> Box<dyn std::any::Any + Send + Sync> {
|
||||
// SAFETY: the caller's contract.
|
||||
let artboard = unsafe { core_types::record::borrow_element::<Artboard>(core_types::record::Rec::new(ptr)) };
|
||||
let mut content = artboard.0.clone();
|
||||
for element in content.iter_element_values_mut() {
|
||||
@@ -72,6 +73,7 @@ unsafe fn deep_repark_artboard(value: &(dyn std::any::Any + Send + Sync), dst: *
|
||||
*element = crate::graphic::map_groups_to_resident(element, arena)?;
|
||||
}
|
||||
crate::graphic::map_attribute_groups_to_resident(&mut content, arena)?;
|
||||
// SAFETY: the caller's contract on `dst`; the replayed content is an `Artboard`.
|
||||
unsafe { core_types::record::write_element(dst, Artboard(content), arena) }
|
||||
}
|
||||
|
||||
@@ -83,12 +85,14 @@ unsafe fn deep_repark_artboard(value: &(dyn std::any::Any + Send + Sync), dst: *
|
||||
/// `src` must point at a live parked `Artboard` element field, and `dst` at
|
||||
/// the element field the promoted reference is written to.
|
||||
unsafe fn promote_artboard(src: *const u8, dst: *mut u8, promotion: &core_types::record::Promotion<'_>) -> Option<()> {
|
||||
// SAFETY: the caller's contract on `src`.
|
||||
let artboard = unsafe { core_types::record::borrow_element::<Artboard>(core_types::record::Rec::new(src)) };
|
||||
if !crate::graphic::list_contains_groups(&artboard.0) {
|
||||
// SAFETY: a parked element slot holds one reference at offset 0, and
|
||||
// content no group is reachable from, elements and item attribute values
|
||||
// alike, owns all of itself.
|
||||
let header = unsafe { src.cast::<*const u8>().read() };
|
||||
// SAFETY: as above; the group-free check establishes the own-all-content half.
|
||||
if let Some(moved) = unsafe { promotion.move_park::<Artboard>(header, 0) } {
|
||||
// SAFETY: as above, into the promoted image's own element slot.
|
||||
unsafe { dst.cast::<*const Artboard>().write(moved) };
|
||||
@@ -101,6 +105,7 @@ unsafe fn promote_artboard(src: *const u8, dst: *mut u8, promotion: &core_types:
|
||||
content.push(core_types::list::Item::from_parts(crate::graphic::map_groups_to_persistent(&element, promotion)?, attributes));
|
||||
}
|
||||
crate::graphic::map_attribute_groups_to_persistent(&mut content, promotion)?;
|
||||
// SAFETY: the caller's contract on `dst`; the promoted content is an `Artboard`.
|
||||
unsafe { core_types::record::write_element(dst, Artboard(content), promotion.persistent()) }
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ pub fn map_groups_to_resident<'a>(graphic: &Graphic<'a>, arena: &'a core_types::
|
||||
/// # Safety
|
||||
/// `ptr` must point at a live parked `Graphic` element field.
|
||||
unsafe fn deep_clone_graphic(ptr: *const u8) -> Box<dyn std::any::Any + Send + Sync> {
|
||||
// SAFETY: the caller's contract.
|
||||
let graphic = unsafe { core_types::record::borrow_element::<Graphic>(core_types::record::Rec::new(ptr)) };
|
||||
Box::new(map_groups_to_owned(graphic))
|
||||
}
|
||||
@@ -68,6 +69,7 @@ unsafe fn deep_repark_graphic(value: &(dyn std::any::Any + Send + Sync), dst: *m
|
||||
let graphic = value.downcast_ref::<Graphic>().expect("an element replays at its own type");
|
||||
let resident = map_groups_to_resident(graphic, arena)?;
|
||||
let retained = graphic_retained_heap(&resident);
|
||||
// SAFETY: the caller's contract; the resident form is a `Graphic` for `dst`.
|
||||
unsafe { core_types::record::write_element_sized(dst, resident, arena, retained) }
|
||||
}
|
||||
|
||||
@@ -165,12 +167,14 @@ pub(crate) fn map_attribute_groups_to_persistent(list: &mut List<Graphic<'_>>, p
|
||||
/// `src` must point at a live parked `Graphic` element field, and `dst` at the
|
||||
/// element field the promoted reference is written to.
|
||||
unsafe fn promote_graphic(src: *const u8, dst: *mut u8, promotion: &core_types::record::Promotion<'_>) -> Option<()> {
|
||||
// SAFETY: the caller's contract on `src`.
|
||||
let graphic = unsafe { core_types::record::borrow_element::<Graphic>(core_types::record::Rec::new(src)) };
|
||||
if !graphic_contains_groups(graphic) {
|
||||
// SAFETY: a parked element slot holds one reference at offset 0, and a
|
||||
// graphic no group is reachable from, elements and item attribute values
|
||||
// alike, owns all of its content.
|
||||
let header = unsafe { src.cast::<*const u8>().read() };
|
||||
// SAFETY: as above; the group-free check establishes the own-all-content half.
|
||||
if let Some(moved) = unsafe { promotion.move_park::<Graphic<'static>>(header, graphic_retained_heap(graphic)) } {
|
||||
// SAFETY: as above, into the promoted image's own element slot.
|
||||
unsafe { dst.cast::<*const Graphic<'static>>().write(moved) };
|
||||
@@ -179,6 +183,7 @@ unsafe fn promote_graphic(src: *const u8, dst: *mut u8, promotion: &core_types::
|
||||
}
|
||||
let promoted = map_groups_to_persistent(graphic, promotion)?;
|
||||
let retained = graphic_retained_heap(&promoted);
|
||||
// SAFETY: the caller's contract on `dst`; the promoted form is a `Graphic`.
|
||||
unsafe { core_types::record::write_element_sized(dst, promoted, promotion.persistent(), retained) }
|
||||
}
|
||||
|
||||
@@ -270,6 +275,8 @@ fn deep_repark_graphic_list(value: &dyn core_types::list::AnyAttributeValue, are
|
||||
*element = map_groups_to_resident(element, arena)?;
|
||||
}
|
||||
map_attribute_groups_to_resident(&mut list, arena)?;
|
||||
// SAFETY: every group the clone carried, in an element or an item attribute
|
||||
// value, now names `arena`, whose borrow the replayed field is read at.
|
||||
let list = unsafe { core_types::record::erase_static(list) };
|
||||
Some(Some(Box::new(Some(list))))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user