Skip to main content

azul_core/
refany.rs

1//! Type-erased, reference-counted smart pointer with runtime borrow checking.
2//!
3//! # Safety
4//!
5//! This module provides `RefAny`, a type-erased container similar to `Arc<RefCell<dyn Any>>`,
6//! but designed for FFI compatibility and cross-language interoperability.
7//!
8//! ## Memory Safety Guarantees
9//!
10//! 1. **Proper Alignment**: Fixed in commit addressing Miri UB - memory is allocated with correct
11//!    alignment for the stored type using `Layout::from_size_align()`.
12//!
13//! 2. **Atomic Reference Counting**: All reference counts use `AtomicUsize` with `SeqCst` ordering,
14//!    ensuring thread-safe access and preventing use-after-free.
15//!
16//! 3. **Runtime Type Safety**: Type IDs are checked before downcasting, preventing invalid pointer
17//!    casts that would cause undefined behavior.
18//!
19//! 4. **Runtime Borrow Checking**: Shared and mutable borrows are tracked at runtime, enforcing
20//!    Rust's borrowing rules dynamically (similar to `RefCell`).
21//!
22//! ## Thread Safety
23//!
24//! - `RefAny` is `Send`: Can be transferred between threads (data is heap-allocated)
25//! - `RefAny` is `Sync`: Can be shared between threads (atomic operations + `&mut self` for
26//!   borrows)
27//!
28//! The `SeqCst` (Sequentially Consistent) memory ordering provides the strongest guarantees:
29//! all atomic operations appear in a single global order visible to all threads, preventing
30//! race conditions where one thread doesn't see another's reference count updates.
31
32use alloc::boxed::Box;
33use alloc::string::String;
34use core::{
35    alloc::Layout,
36    ffi::c_void,
37    fmt,
38    sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
39};
40
41use azul_css::AzString;
42
43/// C-compatible destructor function type for `RefAny`.
44/// Called when the last reference to a `RefAny` is dropped.
45pub type RefAnyDestructorType = extern "C" fn(*mut c_void);
46
47// NOTE: JSON serialization/deserialization callback types are defined in azul_layout::json
48// The actual types are:
49//   RefAnySerializeFnType = extern "C" fn(RefAny) -> Json
50//   RefAnyDeserializeFnType = extern "C" fn(Json) -> ResultRefAnyString
51// In azul_core, we only store function pointers as usize (0 = not set).
52
53/// Internal reference counting metadata for `RefAny`.
54///
55/// This struct tracks:
56///
57/// - How many `RefAny` clones exist (`num_copies`)
58/// - How many shared borrows are active (`num_refs`)
59/// - How many mutable borrows are active (`num_mutable_refs`)
60/// - Memory layout information for correct deallocation
61/// - Type information for runtime type checking
62///
63/// # Thread Safety
64///
65/// All counters are `AtomicUsize` with `SeqCst` ordering, making them safe to access
66/// from multiple threads simultaneously. The strong ordering ensures no thread can
67/// observe inconsistent states (e.g., both seeing count=1 during final drop).
68#[derive(Debug)]
69#[repr(C)]
70// `_internal_*` are C-ABI field names exposed in api.json; the `_` prefix is the
71// intentional "internal" convention and cannot be renamed without breaking the ABI.
72#[allow(clippy::pub_underscore_fields)]
73pub struct RefCountInner {
74    /// Type-erased pointer to heap-allocated data.
75    ///
76    /// SAFETY: Must be properly aligned for the stored type (guaranteed by
77    /// `Layout::from_size_align` in `new_c`). Never null for non-ZST types.
78    ///
79    /// This pointer is shared by all `RefAny` clones, so `replace_contents`
80    /// updates are visible to all clones.
81    pub _internal_ptr: *const c_void,
82
83    /// Number of `RefAny` instances sharing the same data.
84    /// When this reaches 0, the data is deallocated.
85    pub num_copies: AtomicUsize,
86
87    /// Number of active shared borrows (`Ref<T>`).
88    /// While > 0, mutable borrows are forbidden.
89    pub num_refs: AtomicUsize,
90
91    /// Number of active mutable borrows (`RefMut<T>`).
92    /// While > 0, all other borrows are forbidden.
93    pub num_mutable_refs: AtomicUsize,
94
95    /// Size of the stored type in bytes (from `size_of::<T>()`).
96    pub _internal_len: usize,
97
98    /// Layout size for deallocation (from `Layout::size()`).
99    pub _internal_layout_size: usize,
100
101    /// Required alignment for the stored type (from `align_of::<T>()`).
102    /// CRITICAL: Must match the alignment used during allocation to prevent UB.
103    pub _internal_layout_align: usize,
104
105    /// Runtime type identifier computed from `TypeId::of::<T>()`.
106    /// Used to prevent invalid downcasts.
107    pub type_id: u64,
108
109    /// Human-readable type name (e.g., "`MyStruct`") for debugging.
110    pub type_name: AzString,
111
112    /// Function pointer to correctly drop the type-erased data.
113    /// SAFETY: Must be called with a pointer to data of the correct type.
114    pub custom_destructor: extern "C" fn(*mut c_void),
115
116    /// Function pointer to serialize `RefAny` to JSON (0 = not set).
117    /// Cast to `RefAnySerializeFnType` (defined in `azul_layout::json`) when called.
118    /// Type: extern "C" fn(RefAny) -> Json
119    pub serialize_fn: usize,
120
121    /// Function pointer to deserialize JSON to new `RefAny` (0 = not set).
122    /// Cast to `RefAnyDeserializeFnType` (defined in `azul_layout::json`) when called.
123    /// Type: extern "C" fn(Json) -> `ResultRefAnyString`
124    pub deserialize_fn: usize,
125
126    /// Function pointer to an on-update observer (0 = not set).
127    /// Cast to `extern "C" fn(*const c_void, usize)` — the (data ptr, byte len)
128    /// of the *pre-mutation* data — and fired from `downcast_mut` BEFORE the
129    /// mutable borrow is handed out. This is the foundation for undo/redo
130    /// snapshots and client/server state sync. Set via `RefAny::set_update_fn`.
131    pub update_fn: usize,
132}
133
134/// Wrapper around a heap-allocated `RefCountInner`.
135///
136/// This is the shared metadata that all `RefAny` clones point to.
137/// The `RefCount` is responsible for all memory management:
138///
139/// - `RefCount::clone()` increments `num_copies` in `RefCountInner`
140/// - `RefCount::drop()` decrements `num_copies` and, if it reaches 0:
141///   1. Frees the `RefCountInner`
142///   2. Calls the custom destructor on the data
143///   3. Deallocates the data memory
144///
145/// # Why `run_destructor: bool`
146///
147/// This flag tracks whether this `RefCount` instance should decrement
148/// `num_copies` when dropped. Set to `true` for all clones (including
149/// those created by `RefAny::clone()` and `AZ_REFLECT` macros).
150/// Set to `false` after the decrement has been performed to prevent
151/// double-decrement.
152#[derive(Hash, PartialEq, PartialOrd, Ord, Eq)]
153#[repr(C)]
154pub struct RefCount {
155    pub ptr: *const RefCountInner,
156    pub run_destructor: bool,
157}
158
159impl fmt::Debug for RefCount {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        self.downcast().fmt(f)
162    }
163}
164
165impl Clone for RefCount {
166    /// Clones the `RefCount` and increments the reference count.
167    ///
168    /// # Safety
169    ///
170    /// This is safe because:
171    /// - The ptr is valid (created from `Box::into_raw`)
172    /// - `num_copies` is atomically incremented with `SeqCst` ordering
173    /// - This ensures the `RefCountInner` is not freed while clones exist
174    fn clone(&self) -> Self {
175        // CRITICAL: Must increment num_copies so the RefCountInner is not freed
176        // while this clone exists. The C macros (AZ_REFLECT) use AzRefCount_clone
177        // to create Ref/RefMut guards, and those guards must keep the data alive.
178        if !self.ptr.is_null() {
179            // SAFETY: `ptr` is non-null (checked) and came from `Box::into_raw`
180            // in `RefCount::new`; it stays alive as long as any clone exists
181            // because every clone increments `num_copies` here.
182            unsafe {
183                (*self.ptr).num_copies.fetch_add(1, AtomicOrdering::SeqCst);
184            }
185        }
186        Self {
187            ptr: self.ptr,
188            run_destructor: true,
189        }
190    }
191}
192
193impl Drop for RefCount {
194    /// Decrements the reference count when a `RefCount` clone is dropped.
195    ///
196    /// If this was the last reference (`num_copies` reaches 0), this will also
197    /// free the `RefCountInner` and call the custom destructor.
198    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
199    fn drop(&mut self) {
200        // Only decrement if run_destructor is true (meaning this is a clone)
201        // and the pointer is valid
202        if !self.run_destructor || self.ptr.is_null() {
203            return;
204        }
205        self.run_destructor = false;
206
207        // Take the inner pointer and NULL the field before doing anything
208        // else. The C ABI reaches this drop via `AzRefCount_delete` →
209        // `drop_in_place` on C-owned struct memory, and writes through
210        // `&mut self` persist in that memory. Nulling the pointer here
211        // (mirroring the `ptr = 0` convention the AZ_REFLECT C macros use
212        // for their downcast guards) makes a SECOND delete of the same
213        // struct — easy to hit in C example failure paths, and unguarded
214        // in pre-0.2.1 copies of azul.h — a safe no-op via the null check
215        // above, instead of a double-free of the RefCountInner allocation
216        // or a read through a dangling pointer.
217        let inner = self.ptr;
218        self.ptr = core::ptr::null();
219
220        // Atomically decrement and get the PREVIOUS value. `checked_sub`
221        // refuses to underflow: an unmatched decrement (e.g. a C caller
222        // deleting a byte-copied Ref struct twice) becomes a no-op instead
223        // of wrapping `num_copies` to `usize::MAX` and corrupting the
224        // reference count for the rest of the process.
225        // SAFETY: `inner` is non-null (guarded above) and points to the live
226        // `RefCountInner` from `Box::into_raw`; only the atomic field is touched.
227        let current_copies = unsafe {
228            match (*inner).num_copies.fetch_update(
229                AtomicOrdering::SeqCst,
230                AtomicOrdering::SeqCst,
231                |n| n.checked_sub(1),
232            ) {
233                Ok(prev) => prev,
234                Err(_zero) => return,
235            }
236        };
237
238        // If previous value wasn't 1, other references still exist
239        if current_copies != 1 {
240            return;
241        }
242
243        // We're the last reference! Clean up.
244        // SAFETY: ptr came from Box::into_raw, and we're the last reference
245        let sharing_info = unsafe { Box::from_raw(inner.cast_mut()) };
246        let sharing_info = *sharing_info; // Box deallocates RefCountInner here
247
248        // Get the data pointer
249        let data_ptr = sharing_info._internal_ptr;
250
251        // Handle zero-sized types specially
252        if sharing_info._internal_len == 0
253            || sharing_info._internal_layout_size == 0
254            || data_ptr.is_null()
255        {
256            let mut _dummy: [u8; 0] = [];
257            // Call destructor even for ZSTs (may have side effects)
258            (sharing_info.custom_destructor)(_dummy.as_mut_ptr().cast::<c_void>());
259        } else {
260            // Reconstruct the layout used during allocation. Removed the
261            // `unsafe { Layout::from_size_align_unchecked(..) }`: these size/align
262            // were produced by a valid `Layout` in `new_c` (`layout.size()` /
263            // `layout.align()`), so the safe checked constructor always succeeds
264            // and is behaviorally identical here — no unsafe needed.
265            let layout = Layout::from_size_align(
266                sharing_info._internal_layout_size,
267                sharing_info._internal_layout_align,
268            )
269            .expect("RefCount::drop: stored layout was invalid");
270
271            // Phase 1: Run the custom destructor
272            (sharing_info.custom_destructor)(data_ptr.cast_mut());
273
274            // Phase 2: Deallocate the memory
275            // SAFETY: `data_ptr` was allocated in `new_c` (or `replace_contents`)
276            // with exactly this `layout`, and we are the last reference, so no
277            // other clone can observe the freed block.
278            unsafe {
279                alloc::alloc::dealloc(data_ptr as *mut u8, layout);
280            }
281        }
282    }
283}
284
285/// Debug-friendly snapshot of `RefCountInner` with non-atomic values.
286#[derive(Debug, Clone)]
287pub(crate) struct RefCountInnerDebug {
288    pub(crate) num_copies: usize,
289    pub(crate) num_refs: usize,
290    pub(crate) num_mutable_refs: usize,
291    pub(crate) _internal_len: usize,
292    pub(crate) _internal_layout_size: usize,
293    pub(crate) _internal_layout_align: usize,
294    pub(crate) type_id: u64,
295    pub(crate) type_name: AzString,
296    pub(crate) custom_destructor: usize,
297    /// Serialization function pointer (0 = not set)
298    pub(crate) serialize_fn: usize,
299    /// Deserialization function pointer (0 = not set)
300    pub(crate) deserialize_fn: usize,
301}
302
303impl RefCount {
304    /// Creates a new `RefCount` by boxing the metadata on the heap.
305    ///
306    /// # Safety
307    ///
308    /// Safe because we're creating a new allocation with `Box::new`,
309    /// then immediately leaking it with `into_raw` to get a stable pointer.
310    fn new(ref_count: RefCountInner) -> Self {
311        Self {
312            ptr: Box::into_raw(Box::new(ref_count)),
313            run_destructor: true,
314        }
315    }
316
317    /// Dereferences the raw pointer to access the metadata.
318    ///
319    /// # Safety
320    ///
321    /// Safe because:
322    /// - The pointer is created from `Box::into_raw`, so it's valid and properly aligned
323    /// - The lifetime is tied to `&self`, ensuring the pointer is still alive
324    /// - Reference counting ensures the data isn't freed while references exist
325    fn downcast(&self) -> &RefCountInner {
326        assert!(!self.ptr.is_null(), "[RefCount::downcast] FATAL: self.ptr is null!");
327        // SAFETY: `ptr` is non-null (asserted) and came from `Box::into_raw`; the
328        // returned reference is bounded by `&self`, and refcounting keeps the
329        // `RefCountInner` alive for at least that long.
330        unsafe { &*self.ptr }
331    }
332
333    /// Creates a debug snapshot of the current reference counts.
334    ///
335    /// Loads all atomic values with `SeqCst` ordering to get a consistent view.
336    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
337    pub(crate) fn debug_get_refcount_copied(&self) -> RefCountInnerDebug {
338        let dc = self.downcast();
339        RefCountInnerDebug {
340            num_copies: dc.num_copies.load(AtomicOrdering::SeqCst),
341            num_refs: dc.num_refs.load(AtomicOrdering::SeqCst),
342            num_mutable_refs: dc.num_mutable_refs.load(AtomicOrdering::SeqCst),
343            _internal_len: dc._internal_len,
344            _internal_layout_size: dc._internal_layout_size,
345            _internal_layout_align: dc._internal_layout_align,
346            type_id: dc.type_id,
347            type_name: dc.type_name.clone(),
348            custom_destructor: dc.custom_destructor as usize,
349            serialize_fn: dc.serialize_fn,
350            deserialize_fn: dc.deserialize_fn,
351        }
352    }
353
354    /// Runtime check: can we create a shared borrow?
355    ///
356    /// Returns `true` if there are no active mutable borrows.
357    /// Multiple shared borrows can coexist (like `&T` in Rust).
358    ///
359    /// # Memory Ordering
360    ///
361    /// Uses `SeqCst` to ensure we see the most recent state from all threads.
362    /// If another thread just released a mutable borrow, we'll see it.
363    #[must_use] pub fn can_be_shared(&self) -> bool {
364        self.downcast()
365            .num_mutable_refs
366            .load(AtomicOrdering::SeqCst)
367            == 0
368    }
369
370    /// Runtime check: can we create a mutable borrow?
371    ///
372    /// Returns `true` only if there are ZERO active borrows of any kind.
373    /// This enforces Rust's exclusive mutability rule (like `&mut T`).
374    ///
375    /// # Memory Ordering
376    ///
377    /// Uses `SeqCst` to ensure we see all recent borrows from all threads.
378    /// Both counters must be checked atomically to prevent races.
379    #[must_use] pub fn can_be_shared_mut(&self) -> bool {
380        let info = self.downcast();
381        info.num_mutable_refs.load(AtomicOrdering::SeqCst) == 0
382            && info.num_refs.load(AtomicOrdering::SeqCst) == 0
383    }
384
385    /// Increments the shared borrow counter.
386    ///
387    /// Called when a `Ref<T>` is created. The `Ref::drop` will decrement it.
388    ///
389    /// # Memory Ordering
390    ///
391    /// `SeqCst` ensures this increment is visible to all threads before they
392    /// try to acquire a mutable borrow (which checks this counter).
393    pub fn increase_ref(&self) {
394        self.downcast()
395            .num_refs
396            .fetch_add(1, AtomicOrdering::SeqCst);
397    }
398
399    /// Decrements the shared borrow counter.
400    ///
401    /// Called when a `Ref<T>` is dropped, indicating the borrow is released.
402    ///
403    /// # Underflow guard
404    ///
405    /// Saturates at 0: an unmatched decrement — e.g. a C caller running
406    /// `FooRef_delete` after a FAILED downcast with a pre-0.2.1 copy of
407    /// `azul.h` (whose macro did not skip the decrease), or a plain
408    /// double-delete — must not wrap `num_refs` to `usize::MAX`, which
409    /// would make `can_be_shared_mut()` return `false` for the rest of
410    /// the process (callbacks silently stop mutating state).
411    ///
412    /// # Memory Ordering
413    ///
414    /// `SeqCst` ensures this decrement is immediately visible to other threads
415    /// waiting to acquire a mutable borrow.
416    pub fn decrease_ref(&self) {
417        let _ = self.downcast().num_refs.fetch_update(
418            AtomicOrdering::SeqCst,
419            AtomicOrdering::SeqCst,
420            |n| n.checked_sub(1),
421        );
422    }
423
424    /// Increments the mutable borrow counter.
425    ///
426    /// Called when a `RefMut<T>` is created. Should only succeed when this
427    /// counter and `num_refs` are both 0.
428    ///
429    /// # Memory Ordering
430    ///
431    /// `SeqCst` ensures this increment is visible to all other threads,
432    /// blocking them from acquiring any borrow (shared or mutable).
433    pub fn increase_refmut(&self) {
434        self.downcast()
435            .num_mutable_refs
436            .fetch_add(1, AtomicOrdering::SeqCst);
437    }
438
439    /// Decrements the mutable borrow counter.
440    ///
441    /// Called when a `RefMut<T>` is dropped, releasing exclusive access.
442    ///
443    /// # Underflow guard
444    ///
445    /// Saturates at 0 (see [`Self::decrease_ref`]): a double
446    /// `FooRefMut_delete` from C must not wrap `num_mutable_refs`, which
447    /// would corrupt the runtime borrow checker and let a second thread
448    /// or timer callback obtain an aliasing mutable borrow.
449    ///
450    /// # Memory Ordering
451    ///
452    /// `SeqCst` ensures this decrement is immediately visible, allowing
453    /// other threads to acquire borrows.
454    pub fn decrease_refmut(&self) {
455        let _ = self.downcast().num_mutable_refs.fetch_update(
456            AtomicOrdering::SeqCst,
457            AtomicOrdering::SeqCst,
458            |n| n.checked_sub(1),
459        );
460    }
461}
462
463/// RAII guard for a shared borrow of type `T` from a `RefAny`.
464///
465/// Similar to `std::cell::Ref`, this automatically decrements the borrow
466/// counter when dropped, ensuring borrows are properly released.
467///
468/// # Deref
469///
470/// Implements `Deref<Target = T>` so you can use it like `&T`.
471#[derive(Debug)]
472#[repr(C)]
473pub struct Ref<'a, T> {
474    ptr: &'a T,
475    sharing_info: RefCount,
476}
477
478impl<T> Drop for Ref<'_, T> {
479    /// Automatically releases the shared borrow when the guard goes out of scope.
480    ///
481    /// # Safety
482    ///
483    /// Safe because `decrease_ref` uses atomic operations and is designed to be
484    /// called exactly once per `Ref` instance.
485    fn drop(&mut self) {
486        self.sharing_info.decrease_ref();
487    }
488}
489
490impl<T> core::ops::Deref for Ref<'_, T> {
491    type Target = T;
492
493    fn deref(&self) -> &Self::Target {
494        self.ptr
495    }
496}
497
498/// RAII guard for a mutable borrow of type `T` from a `RefAny`.
499///
500/// Similar to `std::cell::RefMut`, this automatically decrements the mutable
501/// borrow counter when dropped, releasing exclusive access.
502///
503/// # Deref / `DerefMut`
504///
505/// Implements both `Deref` and `DerefMut` so you can use it like `&mut T`.
506#[derive(Debug)]
507#[repr(C)]
508pub struct RefMut<'a, T> {
509    ptr: &'a mut T,
510    sharing_info: RefCount,
511}
512
513impl<T> Drop for RefMut<'_, T> {
514    /// Automatically releases the mutable borrow when the guard goes out of scope.
515    ///
516    /// # Safety
517    ///
518    /// Safe because `decrease_refmut` uses atomic operations and is designed to be
519    /// called exactly once per `RefMut` instance.
520    fn drop(&mut self) {
521        self.sharing_info.decrease_refmut();
522    }
523}
524
525impl<T> core::ops::Deref for RefMut<'_, T> {
526    type Target = T;
527
528    fn deref(&self) -> &Self::Target {
529        &*self.ptr
530    }
531}
532
533impl<T> core::ops::DerefMut for RefMut<'_, T> {
534    fn deref_mut(&mut self) -> &mut Self::Target {
535        self.ptr
536    }
537}
538
539/// Type-erased, reference-counted smart pointer with runtime borrow checking.
540///
541/// `RefAny` is similar to `Arc<RefCell<dyn Any>>`, providing:
542/// - Type erasure (stores any `'static` type)
543/// - Reference counting (clones share the same data)
544/// - Runtime borrow checking (enforces Rust's borrowing rules at runtime)
545/// - FFI compatibility (`#[repr(C)]` and C-compatible API)
546///
547/// # Thread Safety
548///
549/// - `Send`: Can be moved between threads (heap-allocated data, atomic counters)
550/// - `Sync`: Can be shared between threads (`downcast_ref/mut` require `&mut self`)
551///
552/// # Memory Safety
553///
554/// Fixed critical UB bugs in alignment, copy count, and pointer provenance.
555/// All operations are verified with Miri to ensure absence of undefined behavior.
556///
557/// # Usage
558///
559/// ```rust
560/// # use azul_core::refany::RefAny;
561/// let data = RefAny::new(42i32);
562/// let mut data_clone = data.clone(); // shares the same heap allocation
563///
564/// // Runtime-checked downcasting with type safety
565/// if let Some(value_ref) = data_clone.downcast_ref::<i32>() {
566///     assert_eq!(*value_ref, 42);
567/// };
568///
569/// // Runtime-checked mutable borrowing
570/// if let Some(mut value_mut) = data_clone.downcast_mut::<i32>() {
571///     *value_mut = 100;
572/// };
573/// ```
574#[derive(Debug)]
575#[repr(C)]
576pub struct RefAny {
577    /// Shared metadata: reference counts, type info, destructor, AND data pointer.
578    ///
579    /// All `RefAny` clones point to the same `RefCountInner` via this field.
580    /// The data pointer is stored in `RefCountInner` so all clones see the same
581    /// pointer, even after `replace_contents()` is called.
582    ///
583    /// The `run_destructor` flag on `RefCount` controls whether dropping this
584    /// `RefAny` should decrement the reference count and potentially free memory.
585    pub sharing_info: RefCount,
586
587    /// Unique ID for this specific clone (root = 0, subsequent clones increment).
588    ///
589    /// Used to distinguish between the original and clones for debugging.
590    pub instance_id: u64,
591}
592
593// The comparison traits below are hand-written, NOT derived, and key on
594// `sharing_info` ALONE. `instance_id` is deliberately omitted:
595//
596//     // self.instance_id == other.instance_id   <-- NEVER compare this
597//
598// `instance_id` is a debug-only counter that `clone()` increments (original = 0,
599// first clone = 1, ...). Deriving equality folded it in, so a `RefAny` never
600// equaled its own clone even though both point at the same `RefCountInner` — the
601// same heap data, same refcount. Equality here means "same data", not "same
602// handle"; `sharing_info` (a pointer + flag) already distinguishes unrelated
603// instances.
604//
605// Hash/Ord must key on exactly the same fields as PartialEq or they break their
606// own contracts (equal values must hash equally; `cmp() == Equal` must imply
607// `==`), so all five delegate to `sharing_info`.
608impl PartialEq for RefAny {
609    fn eq(&self, other: &Self) -> bool {
610        self.sharing_info == other.sharing_info
611    }
612}
613
614impl Eq for RefAny {}
615
616impl core::hash::Hash for RefAny {
617    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
618        core::hash::Hash::hash(&self.sharing_info, state);
619    }
620}
621
622impl PartialOrd for RefAny {
623    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
624        Some(self.cmp(other))
625    }
626}
627
628impl Ord for RefAny {
629    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
630        self.sharing_info.cmp(&other.sharing_info)
631    }
632}
633
634impl_option!(
635    RefAny,
636    OptionRefAny,
637    copy = false,
638    [Debug, Hash, Clone, PartialEq, PartialOrd, Ord, Eq]
639);
640
641// AUDIT: unsound-but-required. These `Send`/`Sync` impls are unconditional in
642// `T`: a `!Send`/`!Sync` payload moved or shared cross-thread races its own
643// internals. This is an INTENTIONAL FFI design constraint — `RefAny` is a
644// type-erased C-ABI handle with no way to carry `T: Send + Sync` bounds across
645// the boundary, and the framework's threading model keeps a given payload on
646// one thread in practice. Left as-is per the audit; do not "fix" by adding
647// bounds (it would break the erased FFI type).
648//
649// SAFETY: RefAny is Send because:
650// - The data pointer points to heap memory (can be sent between threads)
651// - All shared state (RefCountInner) uses atomic operations
652// - No thread-local storage is used
653#[allow(clippy::non_send_fields_in_send_ty)] // see SAFETY note above: atomic refcount, no TLS, no cross-thread deref
654unsafe impl Send for RefAny {}
655
656// SAFETY: RefAny is Sync because:
657// - Methods on `&RefAny` (like `clone`, `get_type_id`) only use atomic operations or
658//   read immutable data, which is inherently thread-safe
659// - The runtime borrow checker (via `can_be_shared/shared_mut`) uses SeqCst atomics
660//
661// AUDIT: unsound-but-required (same intentional FFI constraint as `Send` above).
662//
663// The check-then-increment race that this note described in `downcast_ref/mut`
664// is now FIXED (both use atomic `fetch_add`+validate / `compare_exchange`
665// acquisition — see those methods). The remaining unsoundness is only the
666// unconditional-in-`T` `Sync`, which is required by the erased C-ABI type.
667unsafe impl Sync for RefAny {}
668
669impl RefAny {
670    /// Creates a new type-erased `RefAny` containing the given value.
671    ///
672    /// This is the primary way to construct a `RefAny` from Rust code.
673    ///
674    /// # Type Safety
675    ///
676    /// Stores the `TypeId` of `T` for runtime type checking during downcasts.
677    ///
678    /// # Memory Layout
679    ///
680    /// - Allocates memory on the heap with correct size (`size_of::<T>()`) and alignment
681    ///   (`align_of::<T>()`)
682    /// - Copies the value into the heap allocation
683    /// - Forgets the original value to prevent double-drop
684    ///
685    /// # Custom Destructor
686    ///
687    /// Creates a type-specific destructor that:
688    /// 1. Copies the data from heap back to stack
689    /// 2. Calls `mem::drop` to run `T`'s destructor
690    /// 3. The heap memory is freed separately in `RefAny::drop`
691    ///
692    /// This two-phase destruction ensures proper cleanup even for complex types.
693    ///
694    /// # Safety
695    ///
696    /// Safe because:
697    /// - `mem::forget` prevents double-drop of the original value
698    /// - Type `T` and destructor `<U>` are matched at compile time
699    /// - `ptr::copy_nonoverlapping` with count=1 copies exactly one `T`
700    ///
701    /// # Example
702    ///
703    /// ```rust
704    /// # use azul_core::refany::RefAny;
705    /// let mut data = RefAny::new(42i32);
706    /// let value = data.downcast_ref::<i32>().unwrap();
707    /// assert_eq!(*value, 42);
708    /// ```
709    pub fn new<T: 'static>(value: T) -> Self {
710        /// Type-specific destructor that properly drops the inner value.
711        ///
712        /// # Safety
713        ///
714        /// Safe to call ONLY with a pointer that was created by `RefAny::new<U>`.
715        /// The type `U` must match the original type `T`.
716        ///
717        /// # Why Copy to Stack?
718        ///
719        /// Rust's drop glue expects a value, not a pointer. We copy the data
720        /// to the stack so `mem::drop` can run the destructor properly.
721        ///
722        /// # Critical Fix
723        ///
724        /// The third argument to `copy_nonoverlapping` is the COUNT (1 element),
725        /// not the SIZE in bytes. Using `size_of::<U>()` here would copy
726        /// `size_of::<U>()` elements, causing buffer overflow.
727        extern "C" fn default_custom_destructor<U: 'static>(ptr: *mut c_void) {
728            use core::{mem, ptr};
729
730            // The actual drop glue. `U::drop` is arbitrary user code and this
731            // function is `extern "C"` (called across the FFI boundary from the
732            // C ABI teardown), so a panic escaping here would unwind across that
733            // boundary = UB.
734            // SAFETY: this fn is only installed by `RefAny::new::<U>`, so `ptr`
735            // points to an initialized, properly aligned `U` that no other code
736            // still references (we are in the final drop). We move it out exactly
737            // once (`count = 1`) and run its drop glue.
738            let run = || unsafe {
739                // A ZST has no bytes to move, and `ptr` is not a real pointer to one:
740                // `RefAny::new` never allocates for a ZST, and `RefCount::drop`
741                // substitutes a 1-byte-aligned dummy. Feeding that to
742                // `copy_nonoverlapping` violates its "aligned and non-null"
743                // precondition (`[u64; 0]` demands align 8) — UB, and Rust's debug
744                // check turns it into a NON-UNWINDING abort that kills the process.
745                //
746                // A ZST has exactly one value, so conjure it directly and run its drop
747                // glue without touching `ptr` at all.
748                if size_of::<U>() == 0 {
749                    // Sound for a ZST (exactly one value, touches no memory); the
750                    // size_of == 0 guard is what makes assume_init well-defined here.
751                    #[allow(clippy::uninit_assumed_init)]
752                    drop(mem::MaybeUninit::<U>::uninit().assume_init());
753                    return;
754                }
755
756                // Allocate uninitialized stack space for one `U`
757                let mut stack_mem = mem::MaybeUninit::<U>::uninit();
758
759                // Copy 1 element of type U from heap to stack
760                ptr::copy_nonoverlapping(
761                    ptr as *const U,
762                    stack_mem.as_mut_ptr(),
763                    1, // CRITICAL: This is element count, not byte count!
764                );
765
766                // Take ownership and run the destructor
767                let stack_mem = stack_mem.assume_init();
768                drop(stack_mem); // Runs U's Drop implementation
769            };
770
771            // AUDIT: contain any panic from `U::drop` so it can't unwind across
772            // the `extern "C"` boundary. `catch_unwind` needs `std`; `no_std`
773            // builds use `panic = "abort"`, where unwinding cannot occur.
774            #[cfg(feature = "std")]
775            {
776                drop(std::panic::catch_unwind(std::panic::AssertUnwindSafe(run)));
777            }
778            #[cfg(not(feature = "std"))]
779            {
780                run();
781            }
782        }
783
784        let type_name = ::core::any::type_name::<T>();
785        let type_id = Self::get_type_id_static::<T>();
786
787        let st = AzString::from_const_str(type_name);
788        let s = Self::new_c(
789            (&raw const value) as *const c_void,
790            ::core::mem::size_of::<T>(),
791            ::core::mem::align_of::<T>(), // CRITICAL: Pass alignment to prevent UB
792            type_id,
793            st,
794            default_custom_destructor::<T>,
795            0, // serialize_fn: not set for Rust types by default
796            0, // deserialize_fn: not set for Rust types by default
797        );
798        ::core::mem::forget(value); // Prevent double-drop
799        s
800    }
801
802    /// C-ABI compatible function to create a `RefAny` from raw components.
803    ///
804    /// This is the low-level constructor used by FFI bindings (C, Python, etc.).
805    ///
806    /// # Parameters
807    ///
808    /// - `ptr`: Pointer to the value to store (will be copied)
809    /// - `len`: Size of the value in bytes (`size_of::<T>()`)
810    /// - `align`: Required alignment in bytes (`align_of::<T>()`)
811    /// - `type_id`: Unique identifier for the type (for downcast safety)
812    /// - `type_name`: Human-readable type name (for debugging)
813    /// - `custom_destructor`: Function to call when the last reference is dropped
814    /// - `serialize_fn`: Function pointer for JSON serialization (0 = not set)
815    /// - `deserialize_fn`: Function pointer for JSON deserialization (0 = not set)
816    ///
817    /// # Safety
818    ///
819    /// Caller must ensure:
820    /// - `ptr` points to valid data of size `len` with alignment `align`
821    /// - `type_id` uniquely identifies the type
822    /// - `custom_destructor` correctly drops the type at `ptr`
823    /// - `len` and `align` match the actual type's layout
824    /// - If `serialize_fn != 0`, it must be a valid function pointer of type
825    ///   `extern "C" fn(RefAny) -> Json`
826    /// - If `deserialize_fn != 0`, it must be a valid function pointer of type
827    ///   `extern "C" fn(Json) -> ResultRefAnyString`
828    ///
829    /// # Zero-Sized Types
830    ///
831    /// Special case: ZSTs use a null pointer but still track the type info
832    /// and call the destructor (which may have side effects even for ZSTs).
833    ///
834    /// # Panics
835    ///
836    /// Panics if `ptr` is null while `len > 0` (a non-empty value must have a
837    /// valid backing pointer).
838    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
839    pub fn new_c(
840        // *const T
841        ptr: *const c_void,
842        // sizeof(T)
843        len: usize,
844        // alignof(T)
845        align: usize,
846        // unique ID of the type (used for type comparison when downcasting)
847        type_id: u64,
848        // name of the class such as "app::MyData", usually compiler- or macro-generated
849        type_name: AzString,
850        custom_destructor: extern "C" fn(*mut c_void),
851        // function pointer for JSON serialization (0 = not set)
852        serialize_fn: usize,
853        // function pointer for JSON deserialization (0 = not set)
854        deserialize_fn: usize,
855    ) -> Self {
856        use core::ptr;
857
858        // CRITICAL: Validate input pointer for non-ZST types
859        // A NULL pointer for a non-zero-sized type would cause UB when copying
860        assert!(!(len > 0 && ptr.is_null()), 
861                "RefAny::new_c: NULL pointer passed for non-ZST type (size={}). \
862                This would cause undefined behavior. Type: {:?}",
863                len,
864                type_name.as_str()
865            );
866
867        // Special case: Zero-sized types
868        //
869        // Calling `alloc(Layout { size: 0, .. })` is UB, so we use a null pointer.
870        // The destructor is still called (it may have side effects even for ZSTs).
871        let (_internal_ptr, layout) = if len == 0 {
872            let _dummy: [u8; 0] = [];
873            (ptr::null_mut(), Layout::for_value(&_dummy))
874        } else {
875            // CRITICAL FIX: Use the caller-provided alignment, not alignment of [u8]
876            //
877            // Previous bug: `Layout::for_value(&[u8])` created align=1
878            // This caused unaligned references when downcasting to types like i32 (align=4)
879            //
880            // Fixed: `Layout::from_size_align(len, align)` respects the type's alignment
881            let layout = Layout::from_size_align(len, align).expect("Failed to create layout");
882
883            // Allocate heap memory with correct alignment
884            // SAFETY: `layout` has non-zero size (this branch is `len != 0`), the
885            // required precondition for `alloc`; null return is handled below.
886            let heap_struct_as_bytes = unsafe { alloc::alloc::alloc(layout) };
887
888            // Handle allocation failure (aborts the program)
889            if heap_struct_as_bytes.is_null() {
890                alloc::alloc::handle_alloc_error(layout);
891            }
892
893            // Copy the data byte-by-byte to the heap
894            // SAFETY: Both pointers are valid, non-overlapping, and properly aligned
895            unsafe { ptr::copy_nonoverlapping(ptr as *const u8, heap_struct_as_bytes, len) };
896
897            (heap_struct_as_bytes, layout)
898        };
899
900        let ref_count_inner = RefCountInner {
901            _internal_ptr: _internal_ptr as *const c_void,
902            num_copies: AtomicUsize::new(1),       // This is the first instance
903            num_refs: AtomicUsize::new(0),         // No borrows yet
904            num_mutable_refs: AtomicUsize::new(0), // No mutable borrows yet
905            _internal_len: len,
906            _internal_layout_size: layout.size(),
907            _internal_layout_align: layout.align(),
908            type_id,
909            type_name,
910            custom_destructor,
911            serialize_fn,
912            deserialize_fn,
913            update_fn: 0, // on-update observer not set by default; see set_update_fn
914        };
915
916        let sharing_info = RefCount::new(ref_count_inner);
917
918        Self {
919            sharing_info,
920            instance_id: 0, // Root instance
921        }
922    }
923
924    /// Returns the raw data pointer for FFI downcasting.
925    ///
926    /// This is used by the `AZ_REFLECT` macros in C/C++ to access the
927    /// type-erased data pointer for downcasting operations.
928    ///
929    /// # Safety
930    ///
931    /// The returned pointer must only be dereferenced after verifying
932    /// the type ID matches the expected type. Callers are responsible
933    /// for proper type safety checks.
934    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
935    #[must_use] pub fn get_data_ptr(&self) -> *const c_void {
936        self.sharing_info.downcast()._internal_ptr
937    }
938
939    /// Returns the byte length of the type-erased payload behind
940    /// [`Self::get_data_ptr`] (`size_of::<T>()` of the stored type;
941    /// `0` for ZSTs).
942    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
943    #[must_use] pub fn get_data_len(&self) -> usize {
944        self.sharing_info.downcast()._internal_len
945    }
946
947    /// Checks if this is the only `RefAny` instance with no active borrows.
948    ///
949    /// Returns `true` only if:
950    /// - `num_copies == 1` (no clones exist)
951    /// - `num_refs == 0` (no shared borrows active)
952    /// - `num_mutable_refs == 0` (no mutable borrows active)
953    ///
954    /// Useful for checking if you have exclusive ownership.
955    ///
956    /// # Memory Ordering
957    ///
958    /// Uses `SeqCst` to ensure a consistent view across all three counters.
959    pub(crate) fn has_no_copies(&self) -> bool {
960        self.sharing_info
961            .downcast()
962            .num_copies
963            .load(AtomicOrdering::SeqCst)
964            == 1
965            && self
966                .sharing_info
967                .downcast()
968                .num_refs
969                .load(AtomicOrdering::SeqCst)
970                == 0
971            && self
972                .sharing_info
973                .downcast()
974                .num_mutable_refs
975                .load(AtomicOrdering::SeqCst)
976                == 0
977    }
978
979    /// Attempts to downcast to a shared reference of type `U`.
980    ///
981    /// Returns `None` if:
982    /// - The stored type doesn't match `U` (type safety)
983    /// - A mutable borrow is already active (borrow checking)
984    /// - The pointer is null (ZST or uninitialized)
985    ///
986    /// # Type Safety
987    ///
988    /// Compares `type_id` at runtime before casting. This prevents casting
989    /// `*const c_void` to the wrong type, which would be immediate UB.
990    ///
991    /// # Borrow Checking
992    ///
993    /// Checks `can_be_shared()` to enforce Rust's borrowing rules:
994    /// - Multiple shared borrows are allowed
995    /// - Shared and mutable borrows cannot coexist
996    ///
997    /// # Safety
998    ///
999    /// The `unsafe` cast is safe because:
1000    /// - Type ID check ensures `U` matches the stored type
1001    /// - Memory was allocated with correct alignment for `U`
1002    /// - Lifetime `'a` is tied to `&'a mut self`, preventing use-after-free
1003    /// - Reference count is incremented atomically before returning
1004    ///
1005    /// # Why `&mut self`?
1006    ///
1007    /// Requires `&mut self` to prevent multiple threads from calling this
1008    /// simultaneously on the same `RefAny`. The borrow checker enforces this.
1009    /// Clones of the `RefAny` can call this independently (they share data
1010    /// but have separate runtime borrow tracking).
1011    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
1012    #[inline]
1013    pub fn downcast_ref<U: 'static>(&mut self) -> Option<Ref<'_, U>> {
1014        // Runtime type check: prevent downcasting to wrong type
1015        let stored_type_id = self.get_type_id();
1016        let target_type_id = Self::get_type_id_static::<U>();
1017        let is_same_type = stored_type_id == target_type_id;
1018
1019        if !is_same_type {
1020            return None;
1021        }
1022
1023        // AUDIT: ATOMIC shared-borrow acquisition.
1024        //
1025        // `RefAny` is `Sync` and clones share one `RefCountInner`, so the old
1026        // check-then-increment (`can_be_shared()` then `increase_ref()`) raced a
1027        // concurrent `downcast_mut` on another clone: both could pass their
1028        // pre-checks and hand out aliasing `&`/`&mut` to the same memory (UB).
1029        //
1030        // Fix (mirrors the `compare_exchange` discipline in `replace_contents`):
1031        // increment `num_refs` FIRST, then validate that no mutable borrow is
1032        // live. `SeqCst` imposes a single total order, so a writer (which CASes
1033        // `num_mutable_refs` 0->1 then reads `num_refs`) and this reader (which
1034        // adds to `num_refs` then reads `num_mutable_refs`) can never both
1035        // succeed — at least one observes the other's write. Back the increment
1036        // out on any failure path.
1037        self.sharing_info.increase_ref();
1038
1039        if !self.sharing_info.can_be_shared() {
1040            // A mutable borrow is (being) acquired — release and fail.
1041            self.sharing_info.decrease_ref();
1042            return None;
1043        }
1044
1045        // Get data pointer from shared RefCountInner (stable while we hold the
1046        // shared borrow: `replace_contents` needs `num_refs == 0` to proceed).
1047        let data_ptr = self.sharing_info.downcast()._internal_ptr;
1048
1049        // Null check: ZSTs or uninitialized
1050        if data_ptr.is_null() {
1051            self.sharing_info.decrease_ref();
1052            return None;
1053        }
1054
1055        Some(Ref {
1056            // SAFETY: Type check passed, pointer is non-null and properly aligned
1057            ptr: unsafe { &*(data_ptr as *const U) },
1058            sharing_info: self.sharing_info.clone(),
1059        })
1060    }
1061
1062    /// Attempts to downcast to a mutable reference of type `U`.
1063    ///
1064    /// Returns `None` if:
1065    /// - The stored type doesn't match `U` (type safety)
1066    /// - Any borrow is already active (borrow checking)
1067    /// - The pointer is null (ZST or uninitialized)
1068    ///
1069    /// # Type Safety
1070    ///
1071    /// Compares `type_id` at runtime before casting, preventing UB.
1072    ///
1073    /// # Borrow Checking
1074    ///
1075    /// Checks `can_be_shared_mut()` to enforce exclusive mutability:
1076    /// - No other borrows (shared or mutable) can be active
1077    /// - This is Rust's `&mut T` rule, enforced at runtime
1078    ///
1079    /// # Safety
1080    ///
1081    /// The `unsafe` cast is safe because:
1082    ///
1083    /// - Type ID check ensures `U` matches the stored type
1084    /// - Memory was allocated with correct alignment for `U`
1085    /// - Borrow check ensures no other references exist
1086    /// - Lifetime `'a` is tied to `&'a mut self`, preventing aliasing
1087    /// - Mutable reference count is incremented atomically
1088    ///
1089    /// # Memory Ordering
1090    ///
1091    /// The `increase_refmut()` uses `SeqCst`, ensuring other threads see
1092    /// this mutable borrow before they try to acquire any borrow.
1093    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
1094    #[inline]
1095    pub fn downcast_mut<U: 'static>(&mut self) -> Option<RefMut<'_, U>> {
1096        // Runtime type check
1097        let is_same_type = self.get_type_id() == Self::get_type_id_static::<U>();
1098        if !is_same_type {
1099            return None;
1100        }
1101
1102        // AUDIT: ATOMIC exclusive-borrow acquisition (mirror `replace_contents`).
1103        //
1104        // The old check-then-increment (`can_be_shared_mut()` then
1105        // `increase_refmut()`) raced concurrent borrows on sibling clones and
1106        // could hand out an aliasing `&mut` (UB). Instead, `compare_exchange`
1107        // `num_mutable_refs` 0->1 to atomically take the exclusive slot, THEN
1108        // verify no shared borrow is live; release + fail otherwise. The CAS
1109        // both acquires and rejects a second mutable borrow in one step.
1110        let inner = self.sharing_info.downcast();
1111        if inner
1112            .num_mutable_refs
1113            .compare_exchange(0, 1, AtomicOrdering::SeqCst, AtomicOrdering::SeqCst)
1114            .is_err()
1115        {
1116            return None;
1117        }
1118        if inner.num_refs.load(AtomicOrdering::SeqCst) != 0 {
1119            // A shared borrow is live — release the exclusive slot and fail.
1120            inner.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
1121            return None;
1122        }
1123
1124        // Get data pointer from shared RefCountInner
1125        let data_ptr = inner._internal_ptr;
1126
1127        // Null check
1128        if data_ptr.is_null() {
1129            inner.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
1130            return None;
1131        }
1132
1133        // Fire the on-update observer (if registered) BEFORE handing out the
1134        // mutable borrow: the callback sees the pre-mutation data + its byte
1135        // length, enabling undo/redo snapshots and client/server state sync.
1136        let update_fn = inner.update_fn;
1137        if update_fn != 0 {
1138            // SAFETY: `update_fn` is non-zero (checked) and, per `set_update_fn`'s
1139            // contract, is a valid `extern "C" fn(*const c_void, usize)`. The
1140            // round-trip goes through an int-to-pointer CAST (not a direct
1141            // usize->fn transmute): a transmuted integer carries no provenance,
1142            // which is UB to call (Miri rejects it); the cast re-acquires it.
1143            let cb: extern "C" fn(*const c_void, usize) =
1144                unsafe { core::mem::transmute(update_fn as *const ()) };
1145            let len = inner._internal_len;
1146            // AUDIT: the observer is a host-provided `extern "C"` fn. A Rust
1147            // panic escaping it would unwind across the FFI boundary (UB), so
1148            // contain it. `catch_unwind` needs `std`; `no_std` builds use
1149            // `panic = "abort"` where no unwinding can occur.
1150            #[cfg(feature = "std")]
1151            {
1152                drop(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1153                    cb(data_ptr, len);
1154                })));
1155            }
1156            #[cfg(not(feature = "std"))]
1157            {
1158                cb(data_ptr, len);
1159            }
1160        }
1161
1162        Some(RefMut {
1163            // SAFETY: Type and borrow checks passed, exclusive access guaranteed
1164            ptr: unsafe { &mut *(data_ptr as *mut U) },
1165            sharing_info: self.sharing_info.clone(),
1166        })
1167    }
1168
1169    /// Computes a runtime type ID from Rust's `TypeId`.
1170    ///
1171    /// Rust's `TypeId` is not `#[repr(C)]` and can't cross FFI boundaries.
1172    /// This function converts it to a `u64` by treating it as a byte array.
1173    ///
1174    /// # Safety
1175    ///
1176    /// Safe because:
1177    /// - `TypeId` is a valid type with a stable layout
1178    /// - We only read from it, never write
1179    /// - The slice lifetime is bounded by the function scope
1180    ///
1181    /// # Implementation
1182    ///
1183    /// Treats the `TypeId` as bytes and sums them with bit shifts to create
1184    /// a unique (but not cryptographically secure) hash.
1185    #[inline]
1186    fn get_type_id_static<T: 'static>() -> u64 {
1187        use core::{any::TypeId, mem};
1188
1189        let t_id = TypeId::of::<T>();
1190
1191        // SAFETY: TypeId is a valid type, we're only reading it
1192        let struct_as_bytes = unsafe {
1193            core::slice::from_raw_parts(
1194                (&raw const t_id) as *const u8,
1195                size_of::<TypeId>(),
1196            )
1197        };
1198
1199        // AUDIT: fold ALL bytes of the `TypeId` (16 on current toolchains),
1200        // not just the first 8. This u64 is the ONLY runtime type guard used by
1201        // `downcast_*`; dropping the high 8 bytes let two distinct types whose
1202        // `TypeId`s differ only in their upper half collide, permitting a
1203        // wrong-type downcast (UB). An FxHash-style rotate+multiply mixes every
1204        // byte into the result and is deterministic within a process run (which
1205        // is all `TypeId` itself guarantees).
1206        struct_as_bytes.iter().fold(0u64, |hash, &b| {
1207            (hash.rotate_left(5) ^ u64::from(b)).wrapping_mul(0x51_7c_c1_b7_27_22_0a_95)
1208        })
1209    }
1210
1211    /// Checks if the stored type matches the given type ID.
1212    #[must_use] pub fn is_type(&self, type_id: u64) -> bool {
1213        self.sharing_info.downcast().type_id == type_id
1214    }
1215
1216    /// Returns the stored type ID.
1217    #[must_use] pub fn get_type_id(&self) -> u64 {
1218        self.sharing_info.downcast().type_id
1219    }
1220
1221    /// Returns the human-readable type name for debugging.
1222    #[must_use] pub fn get_type_name(&self) -> AzString {
1223        self.sharing_info.downcast().type_name.clone()
1224    }
1225
1226    /// Returns the current reference count (number of `RefAny` clones sharing this data).
1227    ///
1228    /// This is useful for debugging and metadata purposes.
1229    #[must_use] pub fn get_ref_count(&self) -> usize {
1230        self.sharing_info
1231            .downcast()
1232            .num_copies
1233            .load(AtomicOrdering::SeqCst)
1234    }
1235
1236    /// Returns the serialize function pointer (0 = not set).
1237    /// 
1238    /// This is used for JSON serialization of `RefAny` contents.
1239    #[must_use] pub fn get_serialize_fn(&self) -> usize {
1240        self.sharing_info.downcast().serialize_fn
1241    }
1242
1243    /// Returns the deserialize function pointer (0 = not set).
1244    /// 
1245    /// This is used for JSON deserialization to create a new `RefAny`.
1246    #[must_use] pub fn get_deserialize_fn(&self) -> usize {
1247        self.sharing_info.downcast().deserialize_fn
1248    }
1249
1250    /// Sets the serialize function pointer.
1251    ///
1252    /// # Safety
1253    ///
1254    /// The caller must ensure the function pointer is valid and has the correct
1255    /// signature: `extern "C" fn(RefAny) -> Json`
1256    ///
1257    /// **Known issue:** `&mut self` is exclusive to this clone, not to the shared
1258    /// `RefCountInner`. Concurrent calls via different clones are a data race
1259    /// because `serialize_fn` is a plain `usize`, not atomic.
1260    pub fn set_serialize_fn(&mut self, serialize_fn: usize) {
1261        // FIXME: &mut self is exclusive to this clone only, not to the shared
1262        // RefCountInner — concurrent calls via different clones are a data race.
1263        let inner = self.sharing_info.ptr.cast_mut();
1264        // SAFETY: `inner` came from `Box::into_raw` and is live (we hold `self`).
1265        unsafe {
1266            (*inner).serialize_fn = serialize_fn;
1267        }
1268    }
1269
1270    /// Sets the deserialize function pointer.
1271    ///
1272    /// # Safety
1273    ///
1274    /// The caller must ensure the function pointer is valid and has the correct
1275    /// signature: `extern "C" fn(Json) -> ResultRefAnyString`
1276    ///
1277    /// **Known issue:** `&mut self` is exclusive to this clone, not to the shared
1278    /// `RefCountInner`. Concurrent calls via different clones are a data race
1279    /// because `deserialize_fn` is a plain `usize`, not atomic.
1280    pub fn set_deserialize_fn(&mut self, deserialize_fn: usize) {
1281        // FIXME: &mut self is exclusive to this clone only, not to the shared
1282        // RefCountInner — concurrent calls via different clones are a data race.
1283        let inner = self.sharing_info.ptr.cast_mut();
1284        // SAFETY: `inner` came from `Box::into_raw` and is live (we hold `self`).
1285        unsafe {
1286            (*inner).deserialize_fn = deserialize_fn;
1287        }
1288    }
1289
1290    /// Registers an on-update observer (`0` = unset). It is fired from
1291    /// [`Self::downcast_mut`] with the (data ptr, byte len) of the *pre-mutation*
1292    /// data, just before the mutable borrow is handed out — the foundation for
1293    /// undo/redo snapshots and client/server state sync.
1294    ///
1295    /// # Safety
1296    ///
1297    /// If `update_fn != 0` it must be a valid `extern "C" fn(*const c_void, usize)`.
1298    /// Same shared-`RefCountInner` caveat as [`Self::set_serialize_fn`]: `&mut self`
1299    /// is exclusive to this clone, not to the shared inner.
1300    pub fn set_update_fn(&mut self, update_fn: usize) {
1301        let inner = self.sharing_info.ptr.cast_mut();
1302        // SAFETY: `inner` came from `Box::into_raw` and is live (we hold `self`).
1303        unsafe {
1304            (*inner).update_fn = update_fn;
1305        }
1306    }
1307
1308    /// Returns the registered on-update observer fn pointer (`0` = unset).
1309    #[must_use] pub fn get_update_fn(&self) -> usize {
1310        self.sharing_info.downcast().update_fn
1311    }
1312
1313    /// Returns true if this `RefAny` supports JSON serialization.
1314    #[must_use] pub fn can_serialize(&self) -> bool {
1315        self.get_serialize_fn() != 0
1316    }
1317
1318    /// Returns true if this `RefAny` type supports JSON deserialization.
1319    #[must_use] pub fn can_deserialize(&self) -> bool {
1320        self.get_deserialize_fn() != 0
1321    }
1322
1323    /// Replaces the contents of this `RefAny` with a new value from another `RefAny`.
1324    ///
1325    /// This method:
1326    /// 1. Atomically acquires a mutable "lock" via `compare_exchange`
1327    /// 2. Calls the destructor on the old value
1328    /// 3. Deallocates the old memory
1329    /// 4. Copies the new value's memory
1330    /// 5. Updates metadata (`type_id`, `type_name`, destructor, serialize/deserialize fns)
1331    /// 6. Updates the shared _`internal_ptr` so ALL clones see the new data
1332    /// 7. Releases the lock
1333    ///
1334    /// Since all clones of a `RefAny` share the same `RefCountInner`, this change
1335    /// will be visible to ALL clones of this `RefAny`.
1336    ///
1337    /// # Returns
1338    ///
1339    /// - `true` if the replacement was successful
1340    /// - `false` if there are active borrows (would cause UB)
1341    ///
1342    /// # Thread Safety
1343    ///
1344    /// Uses `compare_exchange` to atomically acquire exclusive access, preventing
1345    /// any race condition between checking for borrows and modifying the data.
1346    ///
1347    /// # Safety
1348    ///
1349    /// Safe because:
1350    /// - We atomically acquire exclusive access before modifying
1351    /// - The old destructor is called before deallocation
1352    /// - Memory is properly allocated with correct alignment
1353    /// - All metadata is updated while holding the lock
1354    ///
1355    /// # Panics
1356    ///
1357    /// Panics if a memory `Layout` for the replacement value cannot be
1358    /// constructed (its size overflows `isize::MAX`).
1359    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
1360    pub fn replace_contents(&mut self, new_value: Self) -> bool {
1361        use core::ptr;
1362
1363        let inner = self.sharing_info.ptr.cast_mut();
1364        
1365        // Atomically acquire exclusive access by setting num_mutable_refs to 1.
1366        // This uses compare_exchange to ensure no race condition:
1367        // - If num_mutable_refs is 0, set it to 1 (success)
1368        // - If num_mutable_refs is not 0, someone else has it (fail)
1369        // We also need to check num_refs == 0 atomically.
1370        let inner_ref = self.sharing_info.downcast();
1371        
1372        // First, try to acquire the mutable lock
1373        let mutable_lock_result = inner_ref.num_mutable_refs.compare_exchange(
1374            0,  // expected: no mutable refs
1375            1,  // desired: we take the mutable ref
1376            AtomicOrdering::SeqCst,
1377            AtomicOrdering::SeqCst,
1378        );
1379        
1380        if mutable_lock_result.is_err() {
1381            // Someone else has a mutable reference
1382            return false;
1383        }
1384        
1385        // Now check that there are no shared references
1386        // Note: We hold the mutable lock, so no new shared refs can be acquired
1387        if inner_ref.num_refs.load(AtomicOrdering::SeqCst) != 0 {
1388            // Release the lock and fail
1389            inner_ref.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
1390            return false;
1391        }
1392        
1393        // We now have exclusive access - perform the replacement
1394        // SAFETY: we hold the exclusive lock (num_mutable_refs==1, num_refs==0),
1395        // so no live `Ref`/`RefMut` aliases the data; `inner` is the live
1396        // `RefCountInner` from `Box::into_raw`. Old data is destructed+freed with
1397        // its own stored layout before the pointer is overwritten, and the new
1398        // data is freshly allocated and byte-copied.
1399        unsafe {
1400            // Get old layout info before we overwrite it
1401            let old_ptr = (*inner)._internal_ptr;
1402            let old_len = (*inner)._internal_len;
1403            let old_layout_size = (*inner)._internal_layout_size;
1404            let old_layout_align = (*inner)._internal_layout_align;
1405            let old_destructor = (*inner).custom_destructor;
1406
1407            // Step 1: Call destructor on old value (if non-ZST)
1408            if old_len > 0 && !old_ptr.is_null() {
1409                old_destructor(old_ptr.cast_mut());
1410            }
1411
1412            // Step 2: Deallocate old memory (if non-ZST). Use the *checked*
1413            // `Layout::from_size_align` (not `_unchecked`): the stored
1414            // size/align came from a valid `Layout`, so it always succeeds, and
1415            // this shrinks the unchecked surface inside this unsafe block.
1416            if old_layout_size > 0 && !old_ptr.is_null() {
1417                let old_layout = Layout::from_size_align(old_layout_size, old_layout_align)
1418                    .expect("replace_contents: stored old layout was invalid");
1419                alloc::alloc::dealloc(old_ptr as *mut u8, old_layout);
1420            }
1421
1422            // Get new value's metadata
1423            let new_inner = new_value.sharing_info.downcast();
1424            let new_ptr = new_inner._internal_ptr;
1425            let new_len = new_inner._internal_len;
1426            let new_layout_size = new_inner._internal_layout_size;
1427            let new_layout_align = new_inner._internal_layout_align;
1428
1429            // Step 3: Allocate new memory and copy data
1430            let allocated_ptr = if new_len == 0 {
1431                ptr::null_mut()
1432            } else {
1433                let new_layout = Layout::from_size_align(new_len, new_layout_align)
1434                    .expect("Failed to create layout");
1435                let heap_ptr = alloc::alloc::alloc(new_layout);
1436                if heap_ptr.is_null() {
1437                    alloc::alloc::handle_alloc_error(new_layout);
1438                }
1439                // Copy data from new_value
1440                ptr::copy_nonoverlapping(
1441                    new_ptr as *const u8,
1442                    heap_ptr,
1443                    new_len,
1444                );
1445                heap_ptr
1446            };
1447
1448            // Step 4: Update the shared internal pointer in RefCountInner
1449            // All clones will see this new pointer!
1450            (*inner)._internal_ptr = allocated_ptr as *const c_void;
1451
1452            // Step 5: Update metadata in RefCountInner
1453            (*inner)._internal_len = new_len;
1454            (*inner)._internal_layout_size = new_layout_size;
1455            (*inner)._internal_layout_align = new_layout_align;
1456            (*inner).type_id = new_inner.type_id;
1457            (*inner).type_name = new_inner.type_name.clone();
1458            (*inner).custom_destructor = new_inner.custom_destructor;
1459            (*inner).serialize_fn = new_inner.serialize_fn;
1460            (*inner).deserialize_fn = new_inner.deserialize_fn;
1461            (*inner).update_fn = new_inner.update_fn;
1462        }
1463
1464        // Release the mutable lock
1465        self.sharing_info.downcast().num_mutable_refs.store(0, AtomicOrdering::SeqCst);
1466
1467        // AUDIT: reclaim `new_value` instead of leaking it.
1468        //
1469        // The old code `mem::forget(new_value)` to stop `RefAny::drop` from
1470        // running the T-destructor a SECOND time on the bytes we just copied
1471        // into our own allocation — but that leaked `new_value`'s entire
1472        // `RefCountInner` box AND its heap data block on every single call.
1473        //
1474        // Instead, neutralize `new_value`'s destructor to a no-op and let the
1475        // normal refcount teardown run: it frees BOTH allocations (data block +
1476        // inner box) when this was the last reference, without re-running the
1477        // real T-destructor (which now lives on OUR inner, to run exactly once
1478        // when `self` is finally dropped). If `new_value` still had clones, the
1479        // no-op keeps them from double-dropping the shared T while their own
1480        // last drop still reclaims the shared block — no double free, no leak.
1481        #[allow(clippy::items_after_statements)]
1482        const extern "C" fn noop_destructor(_: *mut c_void) {}
1483        let new_inner = new_value.sharing_info.ptr.cast_mut();
1484        if !new_inner.is_null() {
1485            // SAFETY: `new_inner` came from `Box::into_raw` in `RefCount::new`
1486            // and is still alive (we hold `new_value`).
1487            unsafe {
1488                (*new_inner).custom_destructor = noop_destructor;
1489            }
1490        }
1491        drop(new_value);
1492
1493        true
1494    }
1495}
1496
1497impl Clone for RefAny {
1498    /// Creates a new `RefAny` sharing the same heap-allocated data.
1499    ///
1500    /// This is cheap (just increments a counter) and is how multiple parts
1501    /// of the code can hold references to the same data.
1502    ///
1503    /// # Reference Counting
1504    ///
1505    /// Atomically increments `num_copies` with `SeqCst` ordering before
1506    /// creating the clone. This ensures all threads see the updated count
1507    /// before the clone can be used.
1508    ///
1509    /// # Instance ID
1510    ///
1511    /// Each clone gets a unique `instance_id` based on the current copy count.
1512    /// The original has `instance_id=0`, the first clone gets `1`, etc.
1513    ///
1514    /// # Memory Ordering
1515    ///
1516    /// The `fetch_add` followed by `load` both use `SeqCst`:
1517    /// - `fetch_add`: Ensures the increment is visible to all threads
1518    /// - `load`: Gets the updated value for the `instance_id`
1519    ///
1520    /// This prevents race conditions where two threads clone simultaneously
1521    /// and both see the same `instance_id`.
1522    ///
1523    /// # Safety
1524    ///
1525    /// Safe because:
1526    ///
1527    /// - Atomic operations prevent data races
1528    /// - The heap allocation remains valid (only freed when count reaches 0)
1529    /// - `run_destructor` is set to `true` for all clones
1530    fn clone(&self) -> Self {
1531        // Atomically increment the reference count
1532        let inner = self.sharing_info.downcast();
1533        let prev = inner.num_copies.fetch_add(1, AtomicOrdering::SeqCst);
1534
1535        let new_instance_id = (prev + 1) as u64;
1536
1537        Self {
1538            // Data pointer is now in RefCountInner, shared automatically
1539            sharing_info: RefCount {
1540                ptr: self.sharing_info.ptr, // Share the same metadata (and data pointer)
1541                run_destructor: true,       // This clone should decrement num_copies on drop
1542            },
1543            // Give this clone a unique ID based on the updated count
1544            instance_id: new_instance_id,
1545        }
1546    }
1547}
1548
1549impl Drop for RefAny {
1550    /// Empty drop implementation - all cleanup is handled by `RefCount::drop`.
1551    ///
1552    /// When a `RefAny` is dropped, its `sharing_info: RefCount` field is automatically
1553    /// dropped by Rust. The `RefCount::drop` implementation handles all cleanup:
1554    ///
1555    /// 1. Atomically decrements `num_copies` with `fetch_sub`
1556    /// 2. If the previous value was 1 (we're the last reference):
1557    ///    - Reclaims the `RefCountInner` via `Box::from_raw`
1558    ///    - Calls the custom destructor to run `T::drop()`
1559    ///    - Deallocates the heap memory with the stored layout
1560    ///
1561    /// # Why No Code Here?
1562    ///
1563    /// Previously, `RefAny::drop` handled cleanup, but this caused issues with the
1564    /// C API where `Ref<T>` and `RefMut<T>` guards (which clone the `RefCount`) need
1565    /// to keep the data alive even after the original `RefAny` is dropped.
1566    ///
1567    /// By moving all cleanup to `RefCount::drop`, we ensure that:
1568    /// - `RefAny::clone()` creates a `RefCount` with `run_destructor = true`
1569    /// - `AZ_REFLECT` macros create `Ref`/`RefMut` guards that clone `RefCount`
1570    /// - Each `RefCount` drop decrements the counter
1571    /// - Only the LAST drop (when `num_copies` was 1) cleans up memory
1572    ///
1573    /// See `RefCount::drop` for the full algorithm and safety documentation.
1574    fn drop(&mut self) {
1575        // RefCount::drop handles everything automatically.
1576        // The sharing_info field is dropped by Rust, triggering RefCount::drop.
1577    }
1578}
1579
1580#[cfg(test)]
1581#[allow(clippy::items_after_statements, clippy::redundant_clone, clippy::cast_possible_truncation, clippy::cast_sign_loss, trivial_casts, clippy::borrow_as_ptr, clippy::cast_ptr_alignment, clippy::unused_self, unused_qualifications, unreachable_pub, private_interfaces)] // pedantic lints are noise in unsafe-exercising test code
1582mod audit_tests {
1583    use super::*;
1584    use core::sync::atomic::{AtomicUsize, Ordering};
1585
1586    static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
1587
1588    // The tests below share the single `DROP_COUNT` static: each resets it to 0
1589    // and then asserts an exact drop count. Under the default multi-threaded
1590    // test runner they would otherwise interleave and corrupt each other's
1591    // counts (a real, if test-only, isolation bug). Every `DROP_COUNT`-using
1592    // test takes this lock first to serialize; it is poison-tolerant so one
1593    // failing test does not cascade `.unwrap()` panics into the rest.
1594    static DROP_COUNT_SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
1595    fn serialize_drop_count() -> std::sync::MutexGuard<'static, ()> {
1596        DROP_COUNT_SERIAL
1597            .lock()
1598            .unwrap_or_else(std::sync::PoisonError::into_inner)
1599    }
1600
1601    struct DropCounter(#[allow(dead_code)] u32);
1602    impl Drop for DropCounter {
1603        fn drop(&mut self) {
1604            DROP_COUNT.fetch_add(1, Ordering::SeqCst);
1605        }
1606    }
1607
1608    // AUDIT: exclusive borrow must be denied while a shared borrow is live and
1609    // vice-versa (runtime borrow checker), and must be recoverable after the
1610    // guard drops. Exercises the atomic acquire/release added to downcast_*.
1611    #[test]
1612    fn borrow_exclusion_and_recovery() {
1613        // The runtime borrow guard lives in the *shared* refcount inner, so it
1614        // is only observable across two clones (a single `RefAny` can't hold two
1615        // guards at once — the methods take `&mut self`). `b` shares `a`'s inner.
1616        let mut a = RefAny::new(7i32);
1617        let mut b = a.clone();
1618
1619        {
1620            let r = a.downcast_ref::<i32>().unwrap();
1621            assert_eq!(*r, 7);
1622            // shared borrow live -> no mutable borrow via the shared inner
1623            assert!(b.downcast_mut::<i32>().is_none());
1624            // another shared borrow is fine
1625            assert!(b.downcast_ref::<i32>().is_some());
1626        }
1627
1628        {
1629            let mut m = a.downcast_mut::<i32>().unwrap();
1630            *m = 42;
1631            // mutable borrow live -> no shared borrow via the shared inner
1632            assert!(b.downcast_ref::<i32>().is_none());
1633        }
1634
1635        assert_eq!(*a.downcast_ref::<i32>().unwrap(), 42);
1636    }
1637
1638    // AUDIT: wrong-type downcast must be rejected. Same type -> same id.
1639    #[test]
1640    fn type_id_guard() {
1641        let mut a = RefAny::new(1u64);
1642        assert!(a.downcast_ref::<i32>().is_none());
1643        assert!(a.downcast_ref::<u64>().is_some());
1644
1645        assert_eq!(
1646            RefAny::get_type_id_static::<u64>(),
1647            RefAny::get_type_id_static::<u64>()
1648        );
1649        assert_ne!(
1650            RefAny::get_type_id_static::<u64>(),
1651            RefAny::get_type_id_static::<i64>()
1652        );
1653    }
1654
1655    // AUDIT: replace_contents must run each stored value's destructor exactly
1656    // once (old value on replace, new value on final drop) and must not leak.
1657    #[test]
1658    fn replace_contents_drops_exactly_once() {
1659        let _serial = serialize_drop_count();
1660        DROP_COUNT.store(0, Ordering::SeqCst);
1661        {
1662            let mut a = RefAny::new(DropCounter(1));
1663            let b = RefAny::new(DropCounter(2));
1664            assert!(a.replace_contents(b));
1665            // The original `a` value was dropped during replacement.
1666            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
1667            // `a` now holds the (copied) `b` value; dropped at end of scope.
1668        }
1669        // Two DropCounter values were constructed; both must be dropped once.
1670        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 2);
1671    }
1672
1673    // AUDIT: replace_contents must fail (return false) while a borrow is live.
1674    #[test]
1675    fn replace_contents_denied_while_borrowed() {
1676        let mut a = RefAny::new(1i32);
1677        // Clone first: `r` will exclusively borrow `a`, so the sibling clone
1678        // must exist beforehand. Both share the same inner RefCountInner.
1679        let mut a2 = a.clone();
1680        let r = a.downcast_ref::<i32>().unwrap();
1681        // A live shared borrow (num_refs != 0) on the shared inner must block
1682        // replace_contents via the sibling clone.
1683        assert!(!a2.replace_contents(RefAny::new(2i32)));
1684        drop(r);
1685        assert!(a2.replace_contents(RefAny::new(2i32)));
1686    }
1687
1688    // ---- Miri-focused unit tests -------------------------------------------
1689    // These exercise the pure-Rust memory behavior of each unsafe path so Miri
1690    // can detect UB (bad provenance, misalignment, use-after-free, leaks,
1691    // refcount corruption). No FFI, no threads, no OS calls; tiny allocations.
1692
1693    // MIRI: covers RefAny::new + new_c alloc/copy_nonoverlapping + downcast_ref
1694    // (&*(ptr as *const U)) + the final Drop path (Box::from_raw + dealloc +
1695    // custom destructor). A non-Copy heap type checks the destructor runs.
1696    #[test]
1697    fn miri_new_downcast_drop_roundtrip() {
1698        let _serial = serialize_drop_count();
1699        DROP_COUNT.store(0, Ordering::SeqCst);
1700        {
1701            let mut a = RefAny::new(DropCounter(9));
1702            // downcast_ref exercises the type-id guard + aligned pointer cast.
1703            assert!(a.downcast_ref::<DropCounter>().is_some());
1704            assert!(a.downcast_ref::<u8>().is_none());
1705        }
1706        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
1707    }
1708
1709    // MIRI: alignment correctness of new_c's Layout::from_size_align path. An
1710    // over-aligned payload downcast to a misaligned pointer would be UB.
1711    #[test]
1712    fn miri_alignment_preserved() {
1713        #[repr(align(16))]
1714        #[derive(Debug)]
1715        struct Over(u64);
1716        let mut a = RefAny::new(Over(0xABCD));
1717        let r = a.downcast_ref::<Over>().unwrap();
1718        assert_eq!(r.0, 0xABCD);
1719        assert_eq!((&raw const *r) as usize % 16, 0);
1720    }
1721
1722    // MIRI: clone shares one RefCountInner; num_copies increments on clone and
1723    // decrements on drop (RefCount::clone / RefCount::drop fetch paths). Data
1724    // must survive while any clone lives and be freed exactly once at the end.
1725    #[test]
1726    fn miri_clone_refcount_increment_decrement() {
1727        let _serial = serialize_drop_count();
1728        DROP_COUNT.store(0, Ordering::SeqCst);
1729        {
1730            let a = RefAny::new(DropCounter(1));
1731            assert_eq!(a.get_ref_count(), 1);
1732            let b = a.clone();
1733            assert_eq!(a.get_ref_count(), 2);
1734            assert_eq!(b.get_ref_count(), 2);
1735            {
1736                let c = b.clone();
1737                assert_eq!(c.get_ref_count(), 3);
1738            }
1739            // c dropped -> back to 2, nothing freed yet.
1740            assert_eq!(a.get_ref_count(), 2);
1741            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
1742        }
1743        // all clones dropped -> data destructed exactly once.
1744        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
1745    }
1746
1747    // MIRI: downcast_mut hands out &mut *(ptr as *mut U); mutation must be
1748    // visible through a shared clone (shared RefCountInner data pointer).
1749    #[test]
1750    fn miri_downcast_mut_mutation_visible_across_clones() {
1751        let mut a = RefAny::new(10u32);
1752        let mut b = a.clone();
1753        {
1754            let mut m = a.downcast_mut::<u32>().unwrap();
1755            *m += 5;
1756        }
1757        assert_eq!(*b.downcast_ref::<u32>().unwrap(), 15);
1758    }
1759
1760    // MIRI: the runtime borrow refcount on the shared inner. Exercises
1761    // increase_ref/decrease_ref/increase_refmut/decrease_refmut and the
1762    // can_be_shared / can_be_shared_mut predicates directly, plus the
1763    // checked_sub underflow guard (decrement at zero must saturate, not wrap).
1764    #[test]
1765    fn miri_borrow_counter_transitions_and_underflow_guard() {
1766        let a = RefAny::new(0i32);
1767        let rc = &a.sharing_info;
1768
1769        assert!(rc.can_be_shared());
1770        assert!(rc.can_be_shared_mut());
1771
1772        rc.increase_ref();
1773        assert!(rc.can_be_shared()); // shared borrows coexist
1774        assert!(!rc.can_be_shared_mut()); // but block a mutable borrow
1775        rc.decrease_ref();
1776        assert!(rc.can_be_shared_mut());
1777
1778        rc.increase_refmut();
1779        assert!(!rc.can_be_shared()); // mutable borrow blocks shared
1780        assert!(!rc.can_be_shared_mut());
1781        rc.decrease_refmut();
1782        assert!(rc.can_be_shared_mut());
1783
1784        // Underflow guard: extra decrements must saturate at 0, never wrap to
1785        // usize::MAX (which would permanently break the borrow checker).
1786        rc.decrease_ref();
1787        rc.decrease_refmut();
1788        assert!(rc.can_be_shared());
1789        assert!(rc.can_be_shared_mut());
1790    }
1791
1792    // MIRI: get_type_id_static reads TypeId via from_raw_parts and folds ALL
1793    // bytes. Same type -> same id (stable within a run); distinct types differ.
1794    #[test]
1795    fn miri_type_id_static_stable_and_distinct() {
1796        assert_eq!(
1797            RefAny::get_type_id_static::<(u8, u64)>(),
1798            RefAny::get_type_id_static::<(u8, u64)>()
1799        );
1800        assert_ne!(
1801            RefAny::get_type_id_static::<u32>(),
1802            RefAny::get_type_id_static::<[u32; 2]>()
1803        );
1804    }
1805
1806    // MIRI: ZST payload uses a null data pointer but must still construct,
1807    // clone, run its destructor once, and reject downcasts (null ptr path).
1808    #[test]
1809    fn miri_zst_roundtrip_and_destructor() {
1810        let _serial = serialize_drop_count();
1811        DROP_COUNT.store(0, Ordering::SeqCst);
1812        struct ZstDrop;
1813        impl Drop for ZstDrop {
1814            fn drop(&mut self) {
1815                DROP_COUNT.fetch_add(1, Ordering::SeqCst);
1816            }
1817        }
1818        {
1819            let mut a = RefAny::new(ZstDrop);
1820            assert_eq!(a.get_data_len(), 0);
1821            // downcast_ref bails on the null data pointer for a ZST.
1822            assert!(a.downcast_ref::<ZstDrop>().is_none());
1823            let _b = a.clone();
1824        }
1825        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
1826    }
1827
1828    // MIRI: replace_contents alloc/dealloc/copy path plus the neutralized
1829    // new_value destructor. Old value destructed once, new value destructed
1830    // once at final drop, with no leak/double-free of either heap block.
1831    #[test]
1832    fn miri_replace_contents_alloc_paths() {
1833        let _serial = serialize_drop_count();
1834        DROP_COUNT.store(0, Ordering::SeqCst);
1835        {
1836            let mut a = RefAny::new(DropCounter(1));
1837            assert!(a.replace_contents(RefAny::new(DropCounter(2))));
1838            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1); // old value gone
1839            assert_eq!(a.downcast_ref::<DropCounter>().unwrap().0, 2u32);
1840        }
1841        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 2);
1842    }
1843
1844    // MIRI: replacing across differing sizes/alignments (u8 -> u64) reallocates
1845    // correctly and keeps the shared pointer aligned for the new type.
1846    #[test]
1847    fn miri_replace_contents_changes_layout() {
1848        let mut a = RefAny::new(7u8);
1849        assert!(a.replace_contents(RefAny::new(0x1122_3344_5566_7788u64)));
1850        {
1851            // downcast_ref takes &mut self, so scope the guard before the next call.
1852            let r = a.downcast_ref::<u64>().unwrap();
1853            assert_eq!(*r, 0x1122_3344_5566_7788u64);
1854            assert_eq!((&raw const *r) as usize % core::mem::align_of::<u64>(), 0);
1855        }
1856        // old u8 type must no longer downcast.
1857        assert!(a.downcast_ref::<u8>().is_none());
1858    }
1859
1860    // MIRI: RefCount clone/drop in isolation keeps the inner alive until the
1861    // last handle drops (Box::into_raw / Box::from_raw balance).
1862    #[test]
1863    fn miri_refcount_clone_keeps_inner_alive() {
1864        let a = RefAny::new(5usize);
1865        let rc0 = a.sharing_info.clone(); // +1 copy
1866        let rc1 = rc0.clone(); // +1 copy
1867        assert_eq!(a.get_ref_count(), 3);
1868        drop(rc1);
1869        drop(rc0);
1870        assert_eq!(a.get_ref_count(), 1);
1871        // `a` still usable -> inner not freed.
1872        assert_eq!(*a.clone().downcast_ref::<usize>().unwrap(), 5);
1873    }
1874}
1875
1876#[cfg(test)]
1877#[allow(
1878    clippy::items_after_statements,
1879    clippy::redundant_clone,
1880    clippy::needless_pass_by_value,
1881    clippy::needless_range_loop,
1882    clippy::cast_possible_truncation,
1883    clippy::cast_sign_loss,
1884    clippy::cast_lossless,
1885    clippy::float_cmp,
1886    clippy::unreadable_literal,
1887    clippy::unusual_byte_groupings,
1888    clippy::many_single_char_names,
1889    clippy::used_underscore_binding,
1890    clippy::borrow_as_ptr,
1891    clippy::cast_ptr_alignment,
1892    clippy::fn_to_numeric_cast_any,
1893    trivial_casts,
1894    unused_qualifications,
1895    unreachable_pub,
1896    private_interfaces,
1897    missing_debug_implementations,
1898    missing_copy_implementations
1899)] // pedantic lints are noise in unsafe-exercising test code
1900mod autotest_generated {
1901    use alloc::{string::String, vec::Vec};
1902    use core::{
1903        ffi::c_void,
1904        sync::atomic::{AtomicUsize, Ordering},
1905    };
1906
1907    use super::*;
1908
1909    /// Destructor for payloads that need no drop glue (`Copy` types built via
1910    /// the raw C-ABI `new_c` path).
1911    extern "C" fn noop_destructor(_: *mut c_void) {}
1912
1913    /// Store `value` in a `RefAny` and read it back out: the byte-copy into the
1914    /// heap allocation and the type-checked pointer cast must be lossless.
1915    fn round_trip<T: 'static + Clone + PartialEq + core::fmt::Debug>(value: T) {
1916        let mut a = RefAny::new(value.clone());
1917        let r = a
1918            .downcast_ref::<T>()
1919            .expect("downcast to the stored type must succeed");
1920        assert_eq!(*r, value);
1921    }
1922
1923    // ---- RefAny::new_c — raw C-ABI constructor, malformed/boundary inputs ----
1924
1925    // A NULL pointer with a non-zero length is the classic FFI mistake: copying
1926    // from it would be UB, so `new_c` must panic instead of reading it.
1927    #[test]
1928    #[should_panic(expected = "NULL pointer passed for non-ZST type")]
1929    fn new_c_null_ptr_with_nonzero_len_panics() {
1930        drop(RefAny::new_c(
1931            core::ptr::null(),
1932            4,
1933            4,
1934            RefAny::get_type_id_static::<u32>(),
1935            AzString::from_const_str("autotest::NullPtr"),
1936            noop_destructor,
1937            0,
1938            0,
1939        ));
1940    }
1941
1942    // A non-power-of-two alignment cannot form a valid `Layout`; it must panic
1943    // before allocating rather than allocate with a bogus layout (which would
1944    // make the matching `dealloc` in `drop` UB).
1945    #[test]
1946    #[should_panic(expected = "Failed to create layout")]
1947    fn new_c_non_power_of_two_align_panics() {
1948        let value: u32 = 7;
1949        drop(RefAny::new_c(
1950            (&raw const value).cast::<c_void>(),
1951            4,
1952            3, // not a power of two
1953            RefAny::get_type_id_static::<u32>(),
1954            AzString::from_const_str("autotest::BadAlign"),
1955            noop_destructor,
1956            0,
1957            0,
1958        ));
1959    }
1960
1961    // `usize::MAX` bytes overflows `isize::MAX` and cannot be a `Layout`: the
1962    // checked constructor must reject it (no silent overflow into a tiny alloc).
1963    #[test]
1964    #[should_panic(expected = "Failed to create layout")]
1965    fn new_c_huge_len_panics_instead_of_overflowing() {
1966        let value: u8 = 1;
1967        drop(RefAny::new_c(
1968            (&raw const value).cast::<c_void>(),
1969            usize::MAX,
1970            1,
1971            RefAny::get_type_id_static::<u8>(),
1972            AzString::from_const_str("autotest::HugeLen"),
1973            noop_destructor,
1974            0,
1975            0,
1976        ));
1977    }
1978
1979    // len == 0 is the ZST path: NULL data pointer is legal, `align` is ignored
1980    // (even a nonsensical 0), nothing is allocated, and downcasts fail cleanly.
1981    #[test]
1982    fn new_c_zero_len_null_ptr_is_a_clean_zst() {
1983        let mut a = RefAny::new_c(
1984            core::ptr::null(),
1985            0,
1986            0, // invalid alignment, but unused on the ZST path
1987            RefAny::get_type_id_static::<()>(),
1988            AzString::from_const_str("autotest::Zst"),
1989            noop_destructor,
1990            0,
1991            0,
1992        );
1993        assert_eq!(a.get_data_len(), 0);
1994        assert!(a.get_data_ptr().is_null());
1995        assert!(a.is_type(RefAny::get_type_id_static::<()>()));
1996        // Type matches, but there is no data behind the pointer -> None, and the
1997        // borrow counters must be released again on that bail-out path.
1998        assert!(a.downcast_ref::<()>().is_none());
1999        assert!(a.downcast_mut::<()>().is_none());
2000        assert!(a.sharing_info.can_be_shared_mut());
2001    }
2002
2003    // Round-trip through the raw C-ABI constructor: what `new_c` encodes,
2004    // `downcast_ref` must decode bit-for-bit.
2005    #[test]
2006    fn new_c_round_trip_matches_rust_constructor() {
2007        let value: u64 = 0xDEAD_BEEF_CAFE_BABE;
2008        let mut a = RefAny::new_c(
2009            (&raw const value).cast::<c_void>(),
2010            core::mem::size_of::<u64>(),
2011            core::mem::align_of::<u64>(),
2012            RefAny::get_type_id_static::<u64>(),
2013            AzString::from_const_str("u64"),
2014            noop_destructor,
2015            7,
2016            9,
2017        );
2018        assert_eq!(a.get_data_len(), core::mem::size_of::<u64>());
2019        assert_eq!(a.get_ref_count(), 1);
2020        assert_eq!(a.get_serialize_fn(), 7);
2021        assert_eq!(a.get_deserialize_fn(), 9);
2022        assert!(a.can_serialize());
2023        assert!(a.can_deserialize());
2024        assert_eq!(*a.downcast_ref::<u64>().unwrap(), value);
2025    }
2026
2027    // The runtime guard is the type ID, nothing else: a matching size, name and
2028    // destructor must NOT be enough to downcast if the ID differs by one bit.
2029    #[test]
2030    fn new_c_wrong_type_id_rejects_downcast() {
2031        let value: u64 = 0x0102_0304_0506_0708;
2032        let real_id = RefAny::get_type_id_static::<u64>();
2033        let mut a = RefAny::new_c(
2034            (&raw const value).cast::<c_void>(),
2035            core::mem::size_of::<u64>(),
2036            core::mem::align_of::<u64>(),
2037            real_id ^ 1, // one bit off
2038            AzString::from_const_str("u64"),
2039            noop_destructor,
2040            0,
2041            0,
2042        );
2043        assert!(!a.is_type(real_id));
2044        assert!(a.downcast_ref::<u64>().is_none());
2045        assert!(a.downcast_mut::<u64>().is_none());
2046        // The rejected downcasts must not have left a borrow behind.
2047        assert!(a.sharing_info.can_be_shared_mut());
2048    }
2049
2050    // Over-alignment (align > len) is a valid `Layout`; the payload must land on
2051    // an address that satisfies the requested alignment.
2052    #[test]
2053    fn new_c_over_aligned_small_payload() {
2054        let value: u8 = 0x5A;
2055        let mut a = RefAny::new_c(
2056            (&raw const value).cast::<c_void>(),
2057            1,
2058            16,
2059            RefAny::get_type_id_static::<u8>(),
2060            AzString::from_const_str("u8"),
2061            noop_destructor,
2062            0,
2063            0,
2064        );
2065        assert_eq!(a.get_data_ptr() as usize % 16, 0);
2066        assert_eq!(*a.downcast_ref::<u8>().unwrap(), 0x5A);
2067    }
2068
2069    // The type name is arbitrary caller-supplied UTF-8 (generated by foreign
2070    // codegen): empty, unicode, RTL overrides and embedded NULs must survive.
2071    #[test]
2072    fn new_c_preserves_unicode_and_empty_type_names() {
2073        let value: u32 = 0;
2074        let weird = "app::💥Ünïcødé<T>\u{202E}rtl\u{0}nul";
2075        let a = RefAny::new_c(
2076            (&raw const value).cast::<c_void>(),
2077            4,
2078            4,
2079            1,
2080            AzString::from(String::from(weird)),
2081            noop_destructor,
2082            0,
2083            0,
2084        );
2085        assert_eq!(a.get_type_name().as_str(), weird);
2086
2087        let b = RefAny::new_c(
2088            (&raw const value).cast::<c_void>(),
2089            4,
2090            4,
2091            2,
2092            AzString::from_const_str(""),
2093            noop_destructor,
2094            0,
2095            0,
2096        );
2097        assert_eq!(b.get_type_name().as_str(), "");
2098    }
2099
2100    // ---- RefAny::new — post-construction invariants ----
2101
2102    #[test]
2103    fn new_invariants_hold() {
2104        let mut a = RefAny::new(0x1122_3344u32);
2105        assert_eq!(a.get_data_len(), core::mem::size_of::<u32>());
2106        assert!(!a.get_data_ptr().is_null());
2107        assert_eq!(a.get_data_ptr() as usize % core::mem::align_of::<u32>(), 0);
2108        assert_eq!(a.get_type_id(), RefAny::get_type_id_static::<u32>());
2109        assert!(a.is_type(RefAny::get_type_id_static::<u32>()));
2110        assert_eq!(a.get_type_name().as_str(), "u32");
2111        assert_eq!(a.get_ref_count(), 1);
2112        assert!(a.has_no_copies());
2113        assert_eq!(a.get_serialize_fn(), 0);
2114        assert_eq!(a.get_deserialize_fn(), 0);
2115        assert_eq!(a.get_update_fn(), 0);
2116        assert!(!a.can_serialize());
2117        assert!(!a.can_deserialize());
2118        assert!(a.sharing_info.can_be_shared());
2119        assert!(a.sharing_info.can_be_shared_mut());
2120        assert_eq!(a.instance_id, 0);
2121        assert_eq!(*a.downcast_ref::<u32>().unwrap(), 0x1122_3344);
2122    }
2123
2124    // A zero-length array of an 8-aligned element is still a ZST: `new` must take
2125    // the null-pointer path (no zero-size allocation, which would be UB).
2126    #[test]
2127    fn new_zero_sized_array_of_aligned_type_is_a_zst() {
2128        let mut a = RefAny::new([0u64; 0]);
2129        assert_eq!(a.get_data_len(), 0);
2130        assert!(a.get_data_ptr().is_null());
2131        assert_eq!(
2132            a.sharing_info.debug_get_refcount_copied()._internal_layout_size,
2133            0
2134        );
2135        assert!(a.downcast_ref::<[u64; 0]>().is_none());
2136        assert_eq!(a.get_ref_count(), 1);
2137    }
2138
2139    // Large + heavily over-aligned payload: the alignment recorded at
2140    // construction must be honoured by the allocation, or every downcast would
2141    // hand out a misaligned reference.
2142    #[test]
2143    fn new_large_over_aligned_payload_round_trips() {
2144        #[repr(align(64))]
2145        #[derive(Clone)]
2146        struct Big([u8; 4096]);
2147
2148        let mut a = RefAny::new(Big([0xAB; 4096]));
2149        assert_eq!(a.get_data_len(), 4096);
2150        assert_eq!(a.get_data_ptr() as usize % 64, 0);
2151        let r = a.downcast_ref::<Big>().unwrap();
2152        assert_eq!((&raw const *r) as usize % 64, 0);
2153        assert!(r.0.iter().all(|&b| b == 0xAB));
2154    }
2155
2156    // ---- numeric limits / round-trip ----
2157
2158    #[test]
2159    fn integer_limits_round_trip() {
2160        round_trip(u8::MIN);
2161        round_trip(u8::MAX);
2162        round_trip(i8::MIN);
2163        round_trip(i8::MAX);
2164        round_trip(u16::MAX);
2165        round_trip(i16::MIN);
2166        round_trip(u32::MAX);
2167        round_trip(i32::MIN);
2168        round_trip(u64::MAX);
2169        round_trip(i64::MIN);
2170        // u128/i128 are 16-aligned on most targets -> exercises the align path
2171        round_trip(u128::MAX);
2172        round_trip(i128::MIN);
2173        round_trip(i128::MAX);
2174        round_trip(usize::MAX);
2175        round_trip(isize::MIN);
2176        round_trip(0usize);
2177    }
2178
2179    // Floats are copied as raw bytes, so every bit pattern (NaN payloads, signed
2180    // zero, infinities) must survive unchanged — no normalization, no rounding.
2181    #[test]
2182    fn float_extremes_round_trip_bit_exact() {
2183        let mut nan = RefAny::new(f64::NAN);
2184        assert!(nan.downcast_ref::<f64>().unwrap().is_nan());
2185
2186        // A NaN with a non-canonical payload must come back bit-identical.
2187        let bits = 0x7FF0_0000_0000_0001u64;
2188        let mut payload_nan = RefAny::new(f64::from_bits(bits));
2189        assert_eq!(payload_nan.downcast_ref::<f64>().unwrap().to_bits(), bits);
2190
2191        let mut neg_zero = RefAny::new(-0.0f64);
2192        let nz = neg_zero.downcast_ref::<f64>().unwrap();
2193        assert!(*nz == 0.0 && nz.is_sign_negative());
2194        drop(nz);
2195
2196        let mut inf = RefAny::new(f32::NEG_INFINITY);
2197        assert_eq!(*inf.downcast_ref::<f32>().unwrap(), f32::NEG_INFINITY);
2198        // f32 and f64 are distinct types even though both are "floats".
2199        assert!(inf.downcast_ref::<f64>().is_none());
2200
2201        round_trip(f64::MIN);
2202        round_trip(f64::MAX);
2203        round_trip(f64::MIN_POSITIVE);
2204        round_trip(f32::EPSILON);
2205        round_trip(f32::MAX);
2206    }
2207
2208    // Owned heap payloads: the value is moved in (`mem::forget` on the original)
2209    // and dropped exactly once at the end — a double-drop here would be a
2210    // double-free of the String/Vec buffers.
2211    #[test]
2212    fn owned_unicode_payloads_round_trip() {
2213        round_trip(String::new());
2214        round_trip(String::from("héllo 🌍 \u{202E}rtl\u{0}nul"));
2215        round_trip('🌍');
2216
2217        let v: Vec<String> = vec![String::from("a"), String::from("🎉"), String::new()];
2218        round_trip(v);
2219    }
2220
2221    // A struct with interior padding is byte-copied, padding included: the copy
2222    // must not disturb the initialized fields.
2223    #[test]
2224    fn padded_struct_round_trips() {
2225        #[derive(Clone, PartialEq, Debug)]
2226        #[repr(C)]
2227        struct Padded {
2228            a: u8,
2229            b: u64,
2230            c: u8,
2231        }
2232        round_trip(Padded {
2233            a: 0xFF,
2234            b: u64::MAX,
2235            c: 0x01,
2236        });
2237    }
2238
2239    // ---- setters: 0 / 1 / usize::MAX (never dereferenced by azul-core) ----
2240
2241    #[test]
2242    fn set_serialize_fn_zero_and_extremes() {
2243        let mut a = RefAny::new(1u32);
2244        assert_eq!(a.get_serialize_fn(), 0);
2245        assert!(!a.can_serialize());
2246
2247        a.set_serialize_fn(usize::MAX);
2248        assert_eq!(a.get_serialize_fn(), usize::MAX);
2249        assert!(a.can_serialize());
2250
2251        a.set_serialize_fn(1);
2252        assert_eq!(a.get_serialize_fn(), 1);
2253        assert!(a.can_serialize());
2254
2255        a.set_serialize_fn(0);
2256        assert_eq!(a.get_serialize_fn(), 0);
2257        assert!(!a.can_serialize());
2258
2259        // The fn pointer lives in the SHARED inner, so a clone's setter is
2260        // visible through the original.
2261        let mut b = a.clone();
2262        b.set_serialize_fn(42);
2263        assert_eq!(a.get_serialize_fn(), 42);
2264        assert!(a.can_serialize());
2265        b.set_serialize_fn(0);
2266        assert!(!a.can_serialize());
2267    }
2268
2269    #[test]
2270    fn set_deserialize_fn_zero_and_extremes() {
2271        let mut a = RefAny::new(1u32);
2272        assert_eq!(a.get_deserialize_fn(), 0);
2273        assert!(!a.can_deserialize());
2274
2275        a.set_deserialize_fn(usize::MAX);
2276        assert_eq!(a.get_deserialize_fn(), usize::MAX);
2277        assert!(a.can_deserialize());
2278
2279        a.set_deserialize_fn(1);
2280        assert_eq!(a.get_deserialize_fn(), 1);
2281
2282        a.set_deserialize_fn(0);
2283        assert_eq!(a.get_deserialize_fn(), 0);
2284        assert!(!a.can_deserialize());
2285
2286        let mut b = a.clone();
2287        b.set_deserialize_fn(42);
2288        assert_eq!(a.get_deserialize_fn(), 42);
2289        b.set_deserialize_fn(0);
2290        assert!(!a.can_deserialize());
2291    }
2292
2293    // `set_update_fn` only *stores* the address; a bogus value must round-trip
2294    // and must be resettable to 0. (Deliberately no `downcast_mut` while the
2295    // observer is bogus — `downcast_mut` transmutes and CALLS it.)
2296    #[test]
2297    fn set_update_fn_zero_and_extremes() {
2298        let mut a = RefAny::new(1u32);
2299        assert_eq!(a.get_update_fn(), 0);
2300
2301        a.set_update_fn(usize::MAX);
2302        assert_eq!(a.get_update_fn(), usize::MAX);
2303
2304        a.set_update_fn(0);
2305        assert_eq!(a.get_update_fn(), 0);
2306        // With the observer unset again, mutable borrows work as normal.
2307        assert!(a.downcast_mut::<u32>().is_some());
2308    }
2309
2310    // The registered observer must fire exactly once per *successful*
2311    // `downcast_mut`, and must see the PRE-mutation bytes + the payload length.
2312    static UPDATE_CALLS: AtomicUsize = AtomicUsize::new(0);
2313    static UPDATE_LEN: AtomicUsize = AtomicUsize::new(0);
2314    static UPDATE_PRE_VALUE: AtomicUsize = AtomicUsize::new(0);
2315
2316    extern "C" fn record_update(ptr: *const c_void, len: usize) {
2317        UPDATE_CALLS.fetch_add(1, Ordering::SeqCst);
2318        UPDATE_LEN.store(len, Ordering::SeqCst);
2319        if !ptr.is_null() && len == core::mem::size_of::<u32>() {
2320            // SAFETY: only installed on a `RefAny` holding a `u32`, and
2321            // `downcast_mut` fires it with that live payload pointer.
2322            let pre = unsafe { core::ptr::read_unaligned(ptr.cast::<u32>()) };
2323            UPDATE_PRE_VALUE.store(pre as usize, Ordering::SeqCst);
2324        }
2325    }
2326
2327    #[test]
2328    fn update_fn_fires_once_with_pre_mutation_data() {
2329        UPDATE_CALLS.store(0, Ordering::SeqCst);
2330
2331        let mut a = RefAny::new(7u32);
2332        let cb: extern "C" fn(*const c_void, usize) = record_update;
2333        a.set_update_fn(cb as usize);
2334        assert_eq!(a.get_update_fn(), cb as usize);
2335
2336        {
2337            let mut m = a.downcast_mut::<u32>().unwrap();
2338            *m = 9;
2339        }
2340        assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
2341        assert_eq!(UPDATE_LEN.load(Ordering::SeqCst), 4);
2342        // The observer saw 7, not 9: it runs BEFORE the borrow is handed out.
2343        assert_eq!(UPDATE_PRE_VALUE.load(Ordering::SeqCst), 7);
2344
2345        // A wrong-type downcast must not fire it.
2346        assert!(a.downcast_mut::<u64>().is_none());
2347        assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
2348
2349        // A shared borrow is not a mutation -> must not fire it.
2350        assert_eq!(*a.downcast_ref::<u32>().unwrap(), 9);
2351        assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
2352
2353        // A *denied* mutable borrow (shared borrow live on a sibling clone)
2354        // must not fire it either.
2355        let mut b = a.clone();
2356        let r = a.downcast_ref::<u32>().unwrap();
2357        assert!(b.downcast_mut::<u32>().is_none());
2358        assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
2359        drop(r);
2360
2361        // Unregistering stops the observer.
2362        b.set_update_fn(0);
2363        assert!(b.downcast_mut::<u32>().is_some());
2364        assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
2365    }
2366
2367    // ---- predicates ----
2368
2369    #[test]
2370    fn is_type_true_false_and_extremes() {
2371        let a = RefAny::new(0u32);
2372        let id = a.get_type_id();
2373
2374        assert!(a.is_type(id));
2375        assert!(!a.is_type(!id)); // every bit flipped -> always a different id
2376        assert!(!a.is_type(id.wrapping_add(1)));
2377        assert!(!a.is_type(RefAny::get_type_id_static::<i32>()));
2378        if id != 0 {
2379            assert!(!a.is_type(0));
2380        }
2381        if id != u64::MAX {
2382            assert!(!a.is_type(u64::MAX));
2383        }
2384    }
2385
2386    #[test]
2387    fn has_no_copies_transitions() {
2388        let mut a = RefAny::new(1u32);
2389        assert!(a.has_no_copies());
2390
2391        {
2392            let b = a.clone();
2393            assert!(!a.has_no_copies()); // num_copies == 2
2394            assert!(!b.has_no_copies());
2395        }
2396        assert!(a.has_no_copies()); // clone dropped -> exclusive again
2397
2398        {
2399            // A live shared borrow (taken via a sibling clone) also disqualifies.
2400            let mut c = a.clone();
2401            let r = c.downcast_ref::<u32>().unwrap();
2402            assert_eq!(*r, 1);
2403            assert!(!a.has_no_copies());
2404        }
2405        assert!(a.has_no_copies());
2406
2407        {
2408            let mut c = a.clone();
2409            let m = c.downcast_mut::<u32>().unwrap();
2410            assert_eq!(*m, 1);
2411            assert!(!a.has_no_copies());
2412        }
2413        assert!(a.has_no_copies());
2414    }
2415
2416    #[test]
2417    fn can_serialize_and_can_deserialize_track_the_fn_pointers() {
2418        let mut a = RefAny::new(1u32);
2419        assert!(!a.can_serialize());
2420        assert!(!a.can_deserialize());
2421
2422        a.set_serialize_fn(1);
2423        assert!(a.can_serialize());
2424        assert!(!a.can_deserialize());
2425
2426        a.set_deserialize_fn(usize::MAX);
2427        assert!(a.can_serialize());
2428        assert!(a.can_deserialize());
2429
2430        a.set_serialize_fn(0);
2431        a.set_deserialize_fn(0);
2432        assert!(!a.can_serialize());
2433        assert!(!a.can_deserialize());
2434    }
2435
2436    // ---- getters ----
2437
2438    #[test]
2439    fn get_ref_count_tracks_clones_and_borrow_guards() {
2440        let mut a = RefAny::new(5u8);
2441        assert_eq!(a.get_ref_count(), 1);
2442
2443        let mut b = a.clone();
2444        assert_eq!(a.get_ref_count(), 2);
2445        assert_eq!(b.get_ref_count(), 2);
2446
2447        {
2448            // The guard clones the RefCount, so it keeps the data alive.
2449            let r = b.downcast_ref::<u8>().unwrap();
2450            assert_eq!(*r, 5);
2451            assert_eq!(a.get_ref_count(), 3);
2452        }
2453        assert_eq!(a.get_ref_count(), 2);
2454
2455        {
2456            let m = b.downcast_mut::<u8>().unwrap();
2457            assert_eq!(*m, 5);
2458            assert_eq!(a.get_ref_count(), 3);
2459        }
2460        assert_eq!(a.get_ref_count(), 2);
2461
2462        drop(b);
2463        assert_eq!(a.get_ref_count(), 1);
2464        assert_eq!(*a.downcast_ref::<u8>().unwrap(), 5);
2465    }
2466
2467    #[test]
2468    fn debug_snapshot_matches_the_live_counters() {
2469        let a = RefAny::new(0x1122_3344u32);
2470        let d = a.sharing_info.debug_get_refcount_copied();
2471        assert_eq!(d.num_copies, 1);
2472        assert_eq!(d.num_refs, 0);
2473        assert_eq!(d.num_mutable_refs, 0);
2474        assert_eq!(d._internal_len, 4);
2475        assert_eq!(d._internal_layout_size, 4);
2476        assert_eq!(d._internal_layout_align, core::mem::align_of::<u32>());
2477        assert_eq!(d.type_id, RefAny::get_type_id_static::<u32>());
2478        assert_eq!(d.type_name.as_str(), "u32");
2479        assert_ne!(d.custom_destructor, 0);
2480        assert_eq!(d.serialize_fn, 0);
2481        assert_eq!(d.deserialize_fn, 0);
2482
2483        a.sharing_info.increase_ref();
2484        a.sharing_info.increase_refmut();
2485        let d2 = a.sharing_info.debug_get_refcount_copied();
2486        assert_eq!(d2.num_refs, 1);
2487        assert_eq!(d2.num_mutable_refs, 1);
2488        // The first snapshot is a copy, not a view: it must not have changed.
2489        assert_eq!(d.num_refs, 0);
2490
2491        a.sharing_info.decrease_ref();
2492        a.sharing_info.decrease_refmut();
2493        let d3 = a.sharing_info.debug_get_refcount_copied();
2494        assert_eq!((d3.num_refs, d3.num_mutable_refs), (0, 0));
2495
2496        // The Debug impl goes through `downcast()` — it must not panic.
2497        assert!(!alloc::format!("{:?}", a.sharing_info).is_empty());
2498    }
2499
2500    #[test]
2501    fn get_type_name_reports_the_rust_type() {
2502        #[derive(Clone)]
2503        struct AutotestNamed(#[allow(dead_code)] u8);
2504
2505        let a = RefAny::new(AutotestNamed(1));
2506        let name = a.get_type_name();
2507        assert!(
2508            name.as_str().contains("AutotestNamed"),
2509            "unexpected type name: {}",
2510            name.as_str()
2511        );
2512
2513        let generic = RefAny::new(Vec::<String>::new());
2514        assert!(generic.get_type_name().as_str().contains("Vec"));
2515
2516        assert_eq!(RefAny::new(1u32).get_type_name().as_str(), "u32");
2517    }
2518
2519    // ---- RefCount: construction, downcast, clone/drop balance ----
2520
2521    #[test]
2522    fn refcount_new_downcast_and_clone_lifecycle() {
2523        let rc = RefCount::new(RefCountInner {
2524            _internal_ptr: core::ptr::null(),
2525            num_copies: AtomicUsize::new(1),
2526            num_refs: AtomicUsize::new(0),
2527            num_mutable_refs: AtomicUsize::new(0),
2528            _internal_len: 0,
2529            _internal_layout_size: 0,
2530            _internal_layout_align: 1,
2531            type_id: 0xDEAD_BEEF,
2532            type_name: AzString::from_const_str("autotest::Synthetic"),
2533            custom_destructor: noop_destructor,
2534            serialize_fn: 0,
2535            deserialize_fn: 0,
2536            update_fn: 0,
2537        });
2538        assert!(!rc.ptr.is_null());
2539        assert!(rc.run_destructor);
2540
2541        let inner = rc.downcast();
2542        assert_eq!(inner.type_id, 0xDEAD_BEEF);
2543        assert_eq!(inner.type_name.as_str(), "autotest::Synthetic");
2544        assert_eq!(inner._internal_len, 0);
2545        assert!(rc.can_be_shared());
2546        assert!(rc.can_be_shared_mut());
2547
2548        // Clones must keep the boxed inner alive; the counters must return to 1
2549        // so the final drop frees it exactly once.
2550        let c1 = rc.clone();
2551        assert_eq!(rc.debug_get_refcount_copied().num_copies, 2);
2552        let c2 = c1.clone();
2553        assert_eq!(rc.debug_get_refcount_copied().num_copies, 3);
2554        drop(c2);
2555        drop(c1);
2556        assert_eq!(rc.debug_get_refcount_copied().num_copies, 1);
2557    }
2558
2559    // The borrow counters must saturate at 0 instead of wrapping to usize::MAX
2560    // (an unmatched `FooRef_delete` from C would otherwise permanently wedge the
2561    // runtime borrow checker), and stay usable afterwards.
2562    #[test]
2563    fn borrow_counters_saturate_at_zero_and_stay_usable() {
2564        let mut a = RefAny::new(3i64);
2565        {
2566            let rc = &a.sharing_info;
2567
2568            // 64 unmatched decrements on both counters.
2569            for _ in 0..64 {
2570                rc.decrease_ref();
2571                rc.decrease_refmut();
2572            }
2573            let d = rc.debug_get_refcount_copied();
2574            assert_eq!(d.num_refs, 0);
2575            assert_eq!(d.num_mutable_refs, 0);
2576            assert!(rc.can_be_shared());
2577            assert!(rc.can_be_shared_mut());
2578
2579            // Many shared borrows coexist, but block a mutable one.
2580            for _ in 0..256 {
2581                rc.increase_ref();
2582            }
2583            assert_eq!(rc.debug_get_refcount_copied().num_refs, 256);
2584            assert!(rc.can_be_shared());
2585            assert!(!rc.can_be_shared_mut());
2586            for _ in 0..256 {
2587                rc.decrease_ref();
2588            }
2589            assert_eq!(rc.debug_get_refcount_copied().num_refs, 0);
2590            assert!(rc.can_be_shared_mut());
2591
2592            // Same for the mutable counter, plus one extra decrement.
2593            rc.increase_refmut();
2594            rc.increase_refmut();
2595            assert!(!rc.can_be_shared());
2596            rc.decrease_refmut();
2597            rc.decrease_refmut();
2598            rc.decrease_refmut();
2599            assert_eq!(rc.debug_get_refcount_copied().num_mutable_refs, 0);
2600        }
2601
2602        // The borrow checker still works after all those underflow attempts.
2603        assert_eq!(*a.downcast_ref::<i64>().unwrap(), 3);
2604        assert!(a.downcast_mut::<i64>().is_some());
2605    }
2606
2607    // ---- get_type_id_static ----
2608
2609    // The u64 type ID is the ONLY runtime guard against a wrong-type downcast,
2610    // so distinct types must not collide (this is what folding ALL TypeId bytes
2611    // buys us) and it must be stable within a process run.
2612    #[test]
2613    fn type_id_static_is_stable_and_collision_free() {
2614        let ids = [
2615            RefAny::get_type_id_static::<u8>(),
2616            RefAny::get_type_id_static::<u16>(),
2617            RefAny::get_type_id_static::<u32>(),
2618            RefAny::get_type_id_static::<u64>(),
2619            RefAny::get_type_id_static::<u128>(),
2620            RefAny::get_type_id_static::<usize>(),
2621            RefAny::get_type_id_static::<i8>(),
2622            RefAny::get_type_id_static::<i16>(),
2623            RefAny::get_type_id_static::<i32>(),
2624            RefAny::get_type_id_static::<i64>(),
2625            RefAny::get_type_id_static::<i128>(),
2626            RefAny::get_type_id_static::<isize>(),
2627            RefAny::get_type_id_static::<f32>(),
2628            RefAny::get_type_id_static::<f64>(),
2629            RefAny::get_type_id_static::<bool>(),
2630            RefAny::get_type_id_static::<char>(),
2631            RefAny::get_type_id_static::<()>(),
2632            RefAny::get_type_id_static::<String>(),
2633            RefAny::get_type_id_static::<Vec<u8>>(),
2634            RefAny::get_type_id_static::<Vec<u16>>(),
2635            RefAny::get_type_id_static::<[u8; 1]>(),
2636            RefAny::get_type_id_static::<[u8; 2]>(),
2637            RefAny::get_type_id_static::<(u8, u8)>(),
2638            RefAny::get_type_id_static::<(u8, u16)>(),
2639            RefAny::get_type_id_static::<Option<u8>>(),
2640            RefAny::get_type_id_static::<Option<u16>>(),
2641        ];
2642
2643        for i in 0..ids.len() {
2644            for j in (i + 1)..ids.len() {
2645                assert_ne!(ids[i], ids[j], "type id collision between {i} and {j}");
2646            }
2647        }
2648
2649        // Deterministic within a run.
2650        assert_eq!(RefAny::get_type_id_static::<Vec<u8>>(), ids[18]);
2651        assert_eq!(RefAny::get_type_id_static::<u8>(), ids[0]);
2652    }
2653
2654    // ---- clone / instance ids ----
2655
2656    #[test]
2657    fn root_instance_id_is_zero_and_clones_are_distinct() {
2658        let a = RefAny::new(0u8);
2659        assert_eq!(a.instance_id, 0);
2660
2661        let b = a.clone();
2662        let c = b.clone();
2663        assert_ne!(b.instance_id, 0);
2664        assert_ne!(c.instance_id, 0);
2665        assert_ne!(b.instance_id, c.instance_id);
2666        assert_eq!(a.get_ref_count(), 3);
2667    }
2668
2669    // ---- replace_contents ----
2670
2671    #[test]
2672    fn replace_contents_zst_and_value_transitions() {
2673        #[derive(Clone)]
2674        struct Zst;
2675
2676        let mut a = RefAny::new(Zst);
2677        assert_eq!(a.get_data_len(), 0);
2678        assert!(a.get_data_ptr().is_null());
2679
2680        // ZST -> sized: a real allocation must appear.
2681        assert!(a.replace_contents(RefAny::new(0x4142_4344u32)));
2682        assert_eq!(a.get_data_len(), 4);
2683        assert!(!a.get_data_ptr().is_null());
2684        assert!(a.is_type(RefAny::get_type_id_static::<u32>()));
2685        assert_eq!(*a.downcast_ref::<u32>().unwrap(), 0x4142_4344);
2686
2687        // sized -> ZST: the pointer goes back to null and downcasts must fail
2688        // safely (releasing the borrow slot they speculatively took).
2689        assert!(a.replace_contents(RefAny::new(Zst)));
2690        assert_eq!(a.get_data_len(), 0);
2691        assert!(a.get_data_ptr().is_null());
2692        assert!(a.downcast_ref::<Zst>().is_none());
2693        assert!(a.downcast_mut::<Zst>().is_none());
2694        assert!(a.sharing_info.can_be_shared_mut());
2695    }
2696
2697    #[test]
2698    fn replace_contents_is_visible_to_all_clones() {
2699        let mut a = RefAny::new(1u32);
2700        let mut b = a.clone();
2701
2702        assert!(a.replace_contents(RefAny::new(2u32)));
2703        assert_eq!(*b.downcast_ref::<u32>().unwrap(), 2);
2704
2705        // The type may change too — every clone sees the new type.
2706        assert!(a.replace_contents(RefAny::new(String::from("swapped"))));
2707        assert!(b.downcast_ref::<u32>().is_none());
2708        assert_eq!(b.downcast_ref::<String>().unwrap().as_str(), "swapped");
2709        assert!(b.get_type_name().as_str().contains("String"));
2710        assert_eq!(b.get_type_id(), RefAny::get_type_id_static::<String>());
2711    }
2712
2713    #[test]
2714    fn replace_contents_denied_while_mutably_borrowed() {
2715        let mut a = RefAny::new(1u32);
2716        let mut b = a.clone();
2717
2718        let m = a.downcast_mut::<u32>().unwrap();
2719        // A live mutable borrow on the shared inner must block the replacement
2720        // (performing it would free memory the `RefMut` still points at).
2721        assert!(!b.replace_contents(RefAny::new(2u32)));
2722        drop(m);
2723
2724        assert!(b.replace_contents(RefAny::new(2u32)));
2725        assert_eq!(*b.downcast_ref::<u32>().unwrap(), 2);
2726    }
2727
2728    // The serialize/deserialize/update hooks are part of the replaced metadata:
2729    // after a replacement they describe the NEW value, not the old one.
2730    #[test]
2731    fn replace_contents_resets_the_fn_pointers_to_the_new_value() {
2732        let mut a = RefAny::new(1u32);
2733        a.set_serialize_fn(3);
2734        a.set_deserialize_fn(4);
2735        assert!(a.can_serialize());
2736        assert!(a.can_deserialize());
2737
2738        assert!(a.replace_contents(RefAny::new(2u32)));
2739        assert_eq!(a.get_serialize_fn(), 0);
2740        assert_eq!(a.get_deserialize_fn(), 0);
2741        assert_eq!(a.get_update_fn(), 0);
2742        assert!(!a.can_serialize());
2743        assert!(!a.can_deserialize());
2744    }
2745
2746    // Repeated replacement across changing sizes/alignments must neither leak nor
2747    // corrupt the payload (Miri checks the alloc/dealloc balance here).
2748    #[test]
2749    fn repeated_replace_contents_stays_consistent() {
2750        let mut a = RefAny::new(String::from("start"));
2751        for i in 0..16u32 {
2752            assert!(a.replace_contents(RefAny::new(i)));
2753            assert_eq!(*a.downcast_ref::<u32>().unwrap(), i);
2754            assert!(a.replace_contents(RefAny::new(u128::from(i) | (1 << 100))));
2755            assert_eq!(
2756                *a.downcast_ref::<u128>().unwrap(),
2757                u128::from(i) | (1 << 100)
2758            );
2759            assert!(a.replace_contents(RefAny::new(String::from("s"))));
2760        }
2761        assert_eq!(a.downcast_ref::<String>().unwrap().as_str(), "s");
2762    }
2763
2764    // ---- destructor robustness / concurrency ----
2765
2766    // `default_custom_destructor` is `extern "C"`: a panic from the payload's
2767    // `Drop` must be caught there, not unwound across the FFI boundary (UB).
2768    #[cfg(feature = "std")]
2769    #[test]
2770    fn panicking_payload_drop_is_contained() {
2771        struct PanicOnDrop(#[allow(dead_code)] u64);
2772        impl Drop for PanicOnDrop {
2773            fn drop(&mut self) {
2774                panic!("autotest: payload Drop panicked (expected, must be contained)");
2775            }
2776        }
2777
2778        let a = RefAny::new(PanicOnDrop(1));
2779        drop(a); // must not propagate the panic out of the extern "C" destructor
2780    }
2781
2782    // RefAny is Send + Sync: concurrent clone/borrow/drop from several threads
2783    // must leave the reference count exactly where it started.
2784    #[cfg(feature = "std")]
2785    #[test]
2786    fn concurrent_clone_and_borrow_keeps_the_refcount_balanced() {
2787        use std::{sync::Arc, thread};
2788
2789        let shared = Arc::new(RefAny::new(11u32));
2790        let mut handles = Vec::new();
2791
2792        for _ in 0..4 {
2793            let s = Arc::clone(&shared);
2794            handles.push(thread::spawn(move || {
2795                for _ in 0..16 {
2796                    let mut local = (*s).clone();
2797                    // No thread takes a mutable borrow, so a shared borrow can
2798                    // never be denied.
2799                    let r = local
2800                        .downcast_ref::<u32>()
2801                        .expect("shared borrow must always succeed here");
2802                    assert_eq!(*r, 11);
2803                }
2804            }));
2805        }
2806        for h in handles {
2807            h.join().expect("worker thread panicked");
2808        }
2809
2810        assert_eq!(shared.get_ref_count(), 1);
2811    }
2812}