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
303/// Say ONCE, on stderr, that something borrowed a released `RefAny`.
304///
305/// Returning `None` keeps the process alive, but a callback whose data has been
306/// released silently does nothing, which is its own kind of bug — so the first
307/// occurrence is announced rather than swallowed. Once per process: this can
308/// fire from a hot path (every callback dispatch downcasts), and a per-frame
309/// warning would be its own denial of service. `RUST_BACKTRACE=1` on the run
310/// that prints it names the caller.
311#[cfg(feature = "std")]
312fn report_released_downcast() {
313    use std::sync::atomic::{AtomicBool, Ordering};
314    static SAID: AtomicBool = AtomicBool::new(false);
315    if !SAID.swap(true, Ordering::Relaxed) {
316        eprintln!(
317            "[azul][refany] a RELEASED RefAny was downcast: its RefCount is already freed, so \
318             the borrow returned None and whatever wanted the data did nothing. This is a \
319             use-after-release in the CALLER, not here. Re-run with RUST_BACKTRACE=1 to name \
320             it. (said once per process)"
321        );
322        if std::env::var("RUST_BACKTRACE").is_ok() {
323            eprintln!("{}", std::backtrace::Backtrace::force_capture());
324        }
325    }
326}
327
328#[cfg(not(feature = "std"))]
329const fn report_released_downcast() {}
330
331impl RefCount {
332    /// Creates a new `RefCount` by boxing the metadata on the heap.
333    ///
334    /// # Safety
335    ///
336    /// Safe because we're creating a new allocation with `Box::new`,
337    /// then immediately leaking it with `into_raw` to get a stable pointer.
338    fn new(ref_count: RefCountInner) -> Self {
339        Self {
340            ptr: Box::into_raw(Box::new(ref_count)),
341            run_destructor: true,
342        }
343    }
344
345    /// Has this `RefCount`'s metadata already been released?
346    ///
347    /// A null `ptr` is a REAL state, not corruption: [`Drop`] nulls the field
348    /// after freeing the `RefCountInner`, deliberately mirroring the `ptr = 0`
349    /// convention the `AZ_REFLECT` C macros use, so that a second
350    /// `AzRefCount_delete` of the same C-owned struct is a no-op instead of a
351    /// double free. Anything reachable from SAFE Rust must therefore treat a
352    /// released `RefCount` as "no data", not as a reason to abort — see
353    /// [`RefAny::get_type_id`].
354    pub(crate) const fn is_released(&self) -> bool {
355        self.ptr.is_null()
356    }
357
358    /// Dereferences the raw pointer to access the metadata.
359    ///
360    /// # Safety
361    ///
362    /// Safe because:
363    /// - The pointer is created from `Box::into_raw`, so it's valid and properly aligned
364    /// - The lifetime is tied to `&self`, ensuring the pointer is still alive
365    /// - Reference counting ensures the data isn't freed while references exist
366    fn downcast(&self) -> &RefCountInner {
367        assert!(
368            !self.ptr.is_null(),
369            "[RefCount::downcast] FATAL: self.ptr is null!"
370        );
371        // SAFETY: `ptr` is non-null (asserted) and came from `Box::into_raw`; the
372        // returned reference is bounded by `&self`, and refcounting keeps the
373        // `RefCountInner` alive for at least that long.
374        unsafe { &*self.ptr }
375    }
376
377    /// Creates a debug snapshot of the current reference counts.
378    ///
379    /// Loads all atomic values with `SeqCst` ordering to get a consistent view.
380    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
381    pub(crate) fn debug_get_refcount_copied(&self) -> RefCountInnerDebug {
382        let dc = self.downcast();
383        RefCountInnerDebug {
384            num_copies: dc.num_copies.load(AtomicOrdering::SeqCst),
385            num_refs: dc.num_refs.load(AtomicOrdering::SeqCst),
386            num_mutable_refs: dc.num_mutable_refs.load(AtomicOrdering::SeqCst),
387            _internal_len: dc._internal_len,
388            _internal_layout_size: dc._internal_layout_size,
389            _internal_layout_align: dc._internal_layout_align,
390            type_id: dc.type_id,
391            type_name: dc.type_name.clone(),
392            custom_destructor: dc.custom_destructor as usize,
393            serialize_fn: dc.serialize_fn,
394            deserialize_fn: dc.deserialize_fn,
395        }
396    }
397
398    /// Runtime check: can we create a shared borrow?
399    ///
400    /// Returns `true` if there are no active mutable borrows.
401    /// Multiple shared borrows can coexist (like `&T` in Rust).
402    ///
403    /// # Memory Ordering
404    ///
405    /// Uses `SeqCst` to ensure we see the most recent state from all threads.
406    /// If another thread just released a mutable borrow, we'll see it.
407    #[must_use]
408    pub fn can_be_shared(&self) -> bool {
409        self.downcast()
410            .num_mutable_refs
411            .load(AtomicOrdering::SeqCst)
412            == 0
413    }
414
415    /// Runtime check: can we create a mutable borrow?
416    ///
417    /// Returns `true` only if there are ZERO active borrows of any kind.
418    /// This enforces Rust's exclusive mutability rule (like `&mut T`).
419    ///
420    /// # Memory Ordering
421    ///
422    /// Uses `SeqCst` to ensure we see all recent borrows from all threads.
423    /// Both counters must be checked atomically to prevent races.
424    #[must_use]
425    pub fn can_be_shared_mut(&self) -> bool {
426        let info = self.downcast();
427        info.num_mutable_refs.load(AtomicOrdering::SeqCst) == 0
428            && info.num_refs.load(AtomicOrdering::SeqCst) == 0
429    }
430
431    /// Increments the shared borrow counter.
432    ///
433    /// Called when a `Ref<T>` is created. The `Ref::drop` will decrement it.
434    ///
435    /// # Memory Ordering
436    ///
437    /// `SeqCst` ensures this increment is visible to all threads before they
438    /// try to acquire a mutable borrow (which checks this counter).
439    pub fn increase_ref(&self) {
440        self.downcast()
441            .num_refs
442            .fetch_add(1, AtomicOrdering::SeqCst);
443    }
444
445    /// Decrements the shared borrow counter.
446    ///
447    /// Called when a `Ref<T>` is dropped, indicating the borrow is released.
448    ///
449    /// # Underflow guard
450    ///
451    /// Saturates at 0: an unmatched decrement — e.g. a C caller running
452    /// `FooRef_delete` after a FAILED downcast with a pre-0.2.1 copy of
453    /// `azul.h` (whose macro did not skip the decrease), or a plain
454    /// double-delete — must not wrap `num_refs` to `usize::MAX`, which
455    /// would make `can_be_shared_mut()` return `false` for the rest of
456    /// the process (callbacks silently stop mutating state).
457    ///
458    /// # Memory Ordering
459    ///
460    /// `SeqCst` ensures this decrement is immediately visible to other threads
461    /// waiting to acquire a mutable borrow.
462    pub fn decrease_ref(&self) {
463        let _ = self.downcast().num_refs.fetch_update(
464            AtomicOrdering::SeqCst,
465            AtomicOrdering::SeqCst,
466            |n| n.checked_sub(1),
467        );
468    }
469
470    /// Increments the mutable borrow counter.
471    ///
472    /// Called when a `RefMut<T>` is created. Should only succeed when this
473    /// counter and `num_refs` are both 0.
474    ///
475    /// # Memory Ordering
476    ///
477    /// `SeqCst` ensures this increment is visible to all other threads,
478    /// blocking them from acquiring any borrow (shared or mutable).
479    pub fn increase_refmut(&self) {
480        self.downcast()
481            .num_mutable_refs
482            .fetch_add(1, AtomicOrdering::SeqCst);
483    }
484
485    /// Decrements the mutable borrow counter.
486    ///
487    /// Called when a `RefMut<T>` is dropped, releasing exclusive access.
488    ///
489    /// # Underflow guard
490    ///
491    /// Saturates at 0 (see [`Self::decrease_ref`]): a double
492    /// `FooRefMut_delete` from C must not wrap `num_mutable_refs`, which
493    /// would corrupt the runtime borrow checker and let a second thread
494    /// or timer callback obtain an aliasing mutable borrow.
495    ///
496    /// # Memory Ordering
497    ///
498    /// `SeqCst` ensures this decrement is immediately visible, allowing
499    /// other threads to acquire borrows.
500    pub fn decrease_refmut(&self) {
501        let _ = self.downcast().num_mutable_refs.fetch_update(
502            AtomicOrdering::SeqCst,
503            AtomicOrdering::SeqCst,
504            |n| n.checked_sub(1),
505        );
506    }
507}
508
509/// RAII guard for a shared borrow of type `T` from a `RefAny`.
510///
511/// Similar to `std::cell::Ref`, this automatically decrements the borrow
512/// counter when dropped, ensuring borrows are properly released.
513///
514/// # Deref
515///
516/// Implements `Deref<Target = T>` so you can use it like `&T`.
517#[derive(Debug)]
518#[repr(C)]
519pub struct Ref<'a, T> {
520    ptr: &'a T,
521    sharing_info: RefCount,
522}
523
524impl<T> Drop for Ref<'_, T> {
525    /// Automatically releases the shared borrow when the guard goes out of scope.
526    ///
527    /// # Safety
528    ///
529    /// Safe because `decrease_ref` uses atomic operations and is designed to be
530    /// called exactly once per `Ref` instance.
531    fn drop(&mut self) {
532        self.sharing_info.decrease_ref();
533    }
534}
535
536impl<T> core::ops::Deref for Ref<'_, T> {
537    type Target = T;
538
539    fn deref(&self) -> &Self::Target {
540        self.ptr
541    }
542}
543
544/// RAII guard for a mutable borrow of type `T` from a `RefAny`.
545///
546/// Similar to `std::cell::RefMut`, this automatically decrements the mutable
547/// borrow counter when dropped, releasing exclusive access.
548///
549/// # Deref / `DerefMut`
550///
551/// Implements both `Deref` and `DerefMut` so you can use it like `&mut T`.
552#[derive(Debug)]
553#[repr(C)]
554pub struct RefMut<'a, T> {
555    ptr: &'a mut T,
556    sharing_info: RefCount,
557}
558
559impl<T> Drop for RefMut<'_, T> {
560    /// Automatically releases the mutable borrow when the guard goes out of scope.
561    ///
562    /// # Safety
563    ///
564    /// Safe because `decrease_refmut` uses atomic operations and is designed to be
565    /// called exactly once per `RefMut` instance.
566    fn drop(&mut self) {
567        self.sharing_info.decrease_refmut();
568    }
569}
570
571impl<T> core::ops::Deref for RefMut<'_, T> {
572    type Target = T;
573
574    fn deref(&self) -> &Self::Target {
575        &*self.ptr
576    }
577}
578
579impl<T> core::ops::DerefMut for RefMut<'_, T> {
580    fn deref_mut(&mut self) -> &mut Self::Target {
581        self.ptr
582    }
583}
584
585/// Type-erased, reference-counted smart pointer with runtime borrow checking.
586///
587/// `RefAny` is similar to `Arc<RefCell<dyn Any>>`, providing:
588/// - Type erasure (stores any `'static` type)
589/// - Reference counting (clones share the same data)
590/// - Runtime borrow checking (enforces Rust's borrowing rules at runtime)
591/// - FFI compatibility (`#[repr(C)]` and C-compatible API)
592///
593/// # Thread Safety
594///
595/// - `Send`: Can be moved between threads (heap-allocated data, atomic counters)
596/// - `Sync`: Can be shared between threads (`downcast_ref/mut` require `&mut self`)
597///
598/// # Memory Safety
599///
600/// Fixed critical UB bugs in alignment, copy count, and pointer provenance.
601/// All operations are verified with Miri to ensure absence of undefined behavior.
602///
603/// # Usage
604///
605/// ```rust
606/// # use azul_core::refany::RefAny;
607/// let data = RefAny::new(42i32);
608/// let mut data_clone = data.clone(); // shares the same heap allocation
609///
610/// // Runtime-checked downcasting with type safety
611/// if let Some(value_ref) = data_clone.downcast_ref::<i32>() {
612///     assert_eq!(*value_ref, 42);
613/// };
614///
615/// // Runtime-checked mutable borrowing
616/// if let Some(mut value_mut) = data_clone.downcast_mut::<i32>() {
617///     *value_mut = 100;
618/// };
619/// ```
620#[derive(Debug)]
621#[repr(C)]
622pub struct RefAny {
623    /// Shared metadata: reference counts, type info, destructor, AND data pointer.
624    ///
625    /// All `RefAny` clones point to the same `RefCountInner` via this field.
626    /// The data pointer is stored in `RefCountInner` so all clones see the same
627    /// pointer, even after `replace_contents()` is called.
628    ///
629    /// The `run_destructor` flag on `RefCount` controls whether dropping this
630    /// `RefAny` should decrement the reference count and potentially free memory.
631    pub sharing_info: RefCount,
632
633    /// Unique ID for this specific clone (root = 0, subsequent clones increment).
634    ///
635    /// Used to distinguish between the original and clones for debugging.
636    pub instance_id: u64,
637}
638
639// The comparison traits below are hand-written, NOT derived, and key on
640// `sharing_info` ALONE. `instance_id` is deliberately omitted:
641//
642//     // self.instance_id == other.instance_id   <-- NEVER compare this
643//
644// `instance_id` is a debug-only counter that `clone()` increments (original = 0,
645// first clone = 1, ...). Deriving equality folded it in, so a `RefAny` never
646// equaled its own clone even though both point at the same `RefCountInner` — the
647// same heap data, same refcount. Equality here means "same data", not "same
648// handle"; `sharing_info` (a pointer + flag) already distinguishes unrelated
649// instances.
650//
651// Hash/Ord must key on exactly the same fields as PartialEq or they break their
652// own contracts (equal values must hash equally; `cmp() == Equal` must imply
653// `==`), so all five delegate to `sharing_info`.
654impl PartialEq for RefAny {
655    fn eq(&self, other: &Self) -> bool {
656        self.sharing_info == other.sharing_info
657    }
658}
659
660impl Eq for RefAny {}
661
662impl core::hash::Hash for RefAny {
663    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
664        core::hash::Hash::hash(&self.sharing_info, state);
665    }
666}
667
668impl PartialOrd for RefAny {
669    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
670        Some(self.cmp(other))
671    }
672}
673
674impl Ord for RefAny {
675    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
676        self.sharing_info.cmp(&other.sharing_info)
677    }
678}
679
680impl_option!(
681    RefAny,
682    OptionRefAny,
683    copy = false,
684    [Debug, Hash, Clone, PartialEq, PartialOrd, Ord, Eq]
685);
686
687// AUDIT: unsound-but-required. These `Send`/`Sync` impls are unconditional in
688// `T`: a `!Send`/`!Sync` payload moved or shared cross-thread races its own
689// internals. This is an INTENTIONAL FFI design constraint — `RefAny` is a
690// type-erased C-ABI handle with no way to carry `T: Send + Sync` bounds across
691// the boundary, and the framework's threading model keeps a given payload on
692// one thread in practice. Left as-is per the audit; do not "fix" by adding
693// bounds (it would break the erased FFI type).
694//
695// SAFETY: RefAny is Send because:
696// - The data pointer points to heap memory (can be sent between threads)
697// - All shared state (RefCountInner) uses atomic operations
698// - No thread-local storage is used
699#[allow(clippy::non_send_fields_in_send_ty)] // see SAFETY note above: atomic refcount, no TLS, no cross-thread deref
700unsafe impl Send for RefAny {}
701
702// SAFETY: RefAny is Sync because:
703// - Methods on `&RefAny` (like `clone`, `get_type_id`) only use atomic operations or
704//   read immutable data, which is inherently thread-safe
705// - The runtime borrow checker (via `can_be_shared/shared_mut`) uses SeqCst atomics
706//
707// AUDIT: unsound-but-required (same intentional FFI constraint as `Send` above).
708//
709// The check-then-increment race that this note described in `downcast_ref/mut`
710// is now FIXED (both use atomic `fetch_add`+validate / `compare_exchange`
711// acquisition — see those methods). The remaining unsoundness is only the
712// unconditional-in-`T` `Sync`, which is required by the erased C-ABI type.
713unsafe impl Sync for RefAny {}
714
715impl RefAny {
716    /// Creates a new type-erased `RefAny` containing the given value.
717    ///
718    /// This is the primary way to construct a `RefAny` from Rust code.
719    ///
720    /// # Type Safety
721    ///
722    /// Stores the `TypeId` of `T` for runtime type checking during downcasts.
723    ///
724    /// # Memory Layout
725    ///
726    /// - Allocates memory on the heap with correct size (`size_of::<T>()`) and alignment
727    ///   (`align_of::<T>()`)
728    /// - Copies the value into the heap allocation
729    /// - Forgets the original value to prevent double-drop
730    ///
731    /// # Custom Destructor
732    ///
733    /// Creates a type-specific destructor that:
734    /// 1. Copies the data from heap back to stack
735    /// 2. Calls `mem::drop` to run `T`'s destructor
736    /// 3. The heap memory is freed separately in `RefAny::drop`
737    ///
738    /// This two-phase destruction ensures proper cleanup even for complex types.
739    ///
740    /// # Safety
741    ///
742    /// Safe because:
743    /// - `mem::forget` prevents double-drop of the original value
744    /// - Type `T` and destructor `<U>` are matched at compile time
745    /// - `ptr::copy_nonoverlapping` with count=1 copies exactly one `T`
746    ///
747    /// # Example
748    ///
749    /// ```rust
750    /// # use azul_core::refany::RefAny;
751    /// let mut data = RefAny::new(42i32);
752    /// let value = data.downcast_ref::<i32>().unwrap();
753    /// assert_eq!(*value, 42);
754    /// ```
755    pub fn new<T: 'static>(value: T) -> Self {
756        /// Type-specific destructor that properly drops the inner value.
757        ///
758        /// # Safety
759        ///
760        /// Safe to call ONLY with a pointer that was created by `RefAny::new<U>`.
761        /// The type `U` must match the original type `T`.
762        ///
763        /// # Why Copy to Stack?
764        ///
765        /// Rust's drop glue expects a value, not a pointer. We copy the data
766        /// to the stack so `mem::drop` can run the destructor properly.
767        ///
768        /// # Critical Fix
769        ///
770        /// The third argument to `copy_nonoverlapping` is the COUNT (1 element),
771        /// not the SIZE in bytes. Using `size_of::<U>()` here would copy
772        /// `size_of::<U>()` elements, causing buffer overflow.
773        extern "C" fn default_custom_destructor<U: 'static>(ptr: *mut c_void) {
774            use core::{mem, ptr};
775
776            // The actual drop glue. `U::drop` is arbitrary user code and this
777            // function is `extern "C"` (called across the FFI boundary from the
778            // C ABI teardown), so a panic escaping here would unwind across that
779            // boundary = UB.
780            // SAFETY: this fn is only installed by `RefAny::new::<U>`, so `ptr`
781            // points to an initialized, properly aligned `U` that no other code
782            // still references (we are in the final drop). We move it out exactly
783            // once (`count = 1`) and run its drop glue.
784            let run = || unsafe {
785                // A ZST has no bytes to move, and `ptr` is not a real pointer to one:
786                // `RefAny::new` never allocates for a ZST, and `RefCount::drop`
787                // substitutes a 1-byte-aligned dummy. Feeding that to
788                // `copy_nonoverlapping` violates its "aligned and non-null"
789                // precondition (`[u64; 0]` demands align 8) — UB, and Rust's debug
790                // check turns it into a NON-UNWINDING abort that kills the process.
791                //
792                // A ZST has exactly one value, so conjure it directly and run its drop
793                // glue without touching `ptr` at all.
794                if size_of::<U>() == 0 {
795                    // Sound for a ZST (exactly one value, touches no memory); the
796                    // size_of == 0 guard is what makes assume_init well-defined here.
797                    #[allow(clippy::uninit_assumed_init)]
798                    drop(mem::MaybeUninit::<U>::uninit().assume_init());
799                    return;
800                }
801
802                // Allocate uninitialized stack space for one `U`
803                let mut stack_mem = mem::MaybeUninit::<U>::uninit();
804
805                // Copy 1 element of type U from heap to stack
806                ptr::copy_nonoverlapping(
807                    ptr as *const U,
808                    stack_mem.as_mut_ptr(),
809                    1, // CRITICAL: This is element count, not byte count!
810                );
811
812                // Take ownership and run the destructor
813                let stack_mem = stack_mem.assume_init();
814                drop(stack_mem); // Runs U's Drop implementation
815            };
816
817            // AUDIT: contain any panic from `U::drop` so it can't unwind across
818            // the `extern "C"` boundary. `catch_unwind` needs `std`; `no_std`
819            // builds use `panic = "abort"`, where unwinding cannot occur.
820            #[cfg(feature = "std")]
821            {
822                drop(std::panic::catch_unwind(std::panic::AssertUnwindSafe(run)));
823            }
824            #[cfg(not(feature = "std"))]
825            {
826                run();
827            }
828        }
829
830        let type_name = ::core::any::type_name::<T>();
831        let type_id = Self::get_type_id_static::<T>();
832
833        let st = AzString::from_const_str(type_name);
834        let s = Self::new_c(
835            (&raw const value) as *const c_void,
836            ::core::mem::size_of::<T>(),
837            ::core::mem::align_of::<T>(), // CRITICAL: Pass alignment to prevent UB
838            type_id,
839            st,
840            default_custom_destructor::<T>,
841            0, // serialize_fn: not set for Rust types by default
842            0, // deserialize_fn: not set for Rust types by default
843        );
844        ::core::mem::forget(value); // Prevent double-drop
845        s
846    }
847
848    /// C-ABI compatible function to create a `RefAny` from raw components.
849    ///
850    /// This is the low-level constructor used by FFI bindings (C, Python, etc.).
851    ///
852    /// # Parameters
853    ///
854    /// - `ptr`: Pointer to the value to store (will be copied)
855    /// - `len`: Size of the value in bytes (`size_of::<T>()`)
856    /// - `align`: Required alignment in bytes (`align_of::<T>()`)
857    /// - `type_id`: Unique identifier for the type (for downcast safety)
858    /// - `type_name`: Human-readable type name (for debugging)
859    /// - `custom_destructor`: Function to call when the last reference is dropped
860    /// - `serialize_fn`: Function pointer for JSON serialization (0 = not set)
861    /// - `deserialize_fn`: Function pointer for JSON deserialization (0 = not set)
862    ///
863    /// # Safety
864    ///
865    /// Caller must ensure:
866    /// - `ptr` points to valid data of size `len` with alignment `align`
867    /// - `type_id` uniquely identifies the type
868    /// - `custom_destructor` correctly drops the type at `ptr`
869    /// - `len` and `align` match the actual type's layout
870    /// - If `serialize_fn != 0`, it must be a valid function pointer of type
871    ///   `extern "C" fn(RefAny) -> Json`
872    /// - If `deserialize_fn != 0`, it must be a valid function pointer of type
873    ///   `extern "C" fn(Json) -> ResultRefAnyString`
874    ///
875    /// # Zero-Sized Types
876    ///
877    /// Special case: ZSTs use a null pointer but still track the type info
878    /// and call the destructor (which may have side effects even for ZSTs).
879    ///
880    /// # Panics
881    ///
882    /// Panics if `ptr` is null while `len > 0` (a non-empty value must have a
883    /// valid backing pointer).
884    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
885    pub fn new_c(
886        // *const T
887        ptr: *const c_void,
888        // sizeof(T)
889        len: usize,
890        // alignof(T)
891        align: usize,
892        // unique ID of the type (used for type comparison when downcasting)
893        type_id: u64,
894        // name of the class such as "app::MyData", usually compiler- or macro-generated
895        type_name: AzString,
896        custom_destructor: extern "C" fn(*mut c_void),
897        // function pointer for JSON serialization (0 = not set)
898        serialize_fn: usize,
899        // function pointer for JSON deserialization (0 = not set)
900        deserialize_fn: usize,
901    ) -> Self {
902        use core::ptr;
903
904        // CRITICAL: Validate input pointer for non-ZST types
905        // A NULL pointer for a non-zero-sized type would cause UB when copying
906        assert!(
907            !(len > 0 && ptr.is_null()),
908            "RefAny::new_c: NULL pointer passed for non-ZST type (size={}). \
909                This would cause undefined behavior. Type: {:?}",
910            len,
911            type_name.as_str()
912        );
913
914        // Special case: Zero-sized types
915        //
916        // Calling `alloc(Layout { size: 0, .. })` is UB, so we use a null pointer.
917        // The destructor is still called (it may have side effects even for ZSTs).
918        let (_internal_ptr, layout) = if len == 0 {
919            let _dummy: [u8; 0] = [];
920            (ptr::null_mut(), Layout::for_value(&_dummy))
921        } else {
922            // CRITICAL FIX: Use the caller-provided alignment, not alignment of [u8]
923            //
924            // Previous bug: `Layout::for_value(&[u8])` created align=1
925            // This caused unaligned references when downcasting to types like i32 (align=4)
926            //
927            // Fixed: `Layout::from_size_align(len, align)` respects the type's alignment
928            let layout = Layout::from_size_align(len, align).expect("Failed to create layout");
929
930            // Allocate heap memory with correct alignment
931            // SAFETY: `layout` has non-zero size (this branch is `len != 0`), the
932            // required precondition for `alloc`; null return is handled below.
933            let heap_struct_as_bytes = unsafe { alloc::alloc::alloc(layout) };
934
935            // Handle allocation failure (aborts the program)
936            if heap_struct_as_bytes.is_null() {
937                alloc::alloc::handle_alloc_error(layout);
938            }
939
940            // Copy the data byte-by-byte to the heap
941            // SAFETY: Both pointers are valid, non-overlapping, and properly aligned
942            unsafe { ptr::copy_nonoverlapping(ptr as *const u8, heap_struct_as_bytes, len) };
943
944            (heap_struct_as_bytes, layout)
945        };
946
947        let ref_count_inner = RefCountInner {
948            _internal_ptr: _internal_ptr as *const c_void,
949            num_copies: AtomicUsize::new(1), // This is the first instance
950            num_refs: AtomicUsize::new(0),   // No borrows yet
951            num_mutable_refs: AtomicUsize::new(0), // No mutable borrows yet
952            _internal_len: len,
953            _internal_layout_size: layout.size(),
954            _internal_layout_align: layout.align(),
955            type_id,
956            type_name,
957            custom_destructor,
958            serialize_fn,
959            deserialize_fn,
960            update_fn: 0, // on-update observer not set by default; see set_update_fn
961        };
962
963        let sharing_info = RefCount::new(ref_count_inner);
964
965        Self {
966            sharing_info,
967            instance_id: 0, // Root instance
968        }
969    }
970
971    /// Returns the raw data pointer for FFI downcasting.
972    ///
973    /// This is used by the `AZ_REFLECT` macros in C/C++ to access the
974    /// type-erased data pointer for downcasting operations.
975    ///
976    /// # Safety
977    ///
978    /// The returned pointer must only be dereferenced after verifying
979    /// the type ID matches the expected type. Callers are responsible
980    /// for proper type safety checks.
981    #[allow(clippy::used_underscore_binding)]
982    // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
983    #[must_use]
984    pub fn get_data_ptr(&self) -> *const c_void {
985        self.sharing_info.downcast()._internal_ptr
986    }
987
988    /// Returns the byte length of the type-erased payload behind
989    /// [`Self::get_data_ptr`] (`size_of::<T>()` of the stored type;
990    /// `0` for ZSTs).
991    #[allow(clippy::used_underscore_binding)]
992    // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
993    #[must_use]
994    pub fn get_data_len(&self) -> usize {
995        self.sharing_info.downcast()._internal_len
996    }
997
998    /// Checks if this is the only `RefAny` instance with no active borrows.
999    ///
1000    /// Returns `true` only if:
1001    /// - `num_copies == 1` (no clones exist)
1002    /// - `num_refs == 0` (no shared borrows active)
1003    /// - `num_mutable_refs == 0` (no mutable borrows active)
1004    ///
1005    /// Useful for checking if you have exclusive ownership.
1006    ///
1007    /// # Memory Ordering
1008    ///
1009    /// Uses `SeqCst` to ensure a consistent view across all three counters.
1010    pub(crate) fn has_no_copies(&self) -> bool {
1011        self.sharing_info
1012            .downcast()
1013            .num_copies
1014            .load(AtomicOrdering::SeqCst)
1015            == 1
1016            && self
1017                .sharing_info
1018                .downcast()
1019                .num_refs
1020                .load(AtomicOrdering::SeqCst)
1021                == 0
1022            && self
1023                .sharing_info
1024                .downcast()
1025                .num_mutable_refs
1026                .load(AtomicOrdering::SeqCst)
1027                == 0
1028    }
1029
1030    /// Attempts to downcast to a shared reference of type `U`.
1031    ///
1032    /// Returns `None` if:
1033    /// - The stored type doesn't match `U` (type safety)
1034    /// - A mutable borrow is already active (borrow checking)
1035    /// - The pointer is null AND `U` is not zero-sized (uninitialized). A
1036    ///   stored ZST has a null pointer *by design* (nothing is allocated) and
1037    ///   downcasts successfully, via a dangling-but-aligned reference.
1038    ///
1039    /// # Type Safety
1040    ///
1041    /// Compares `type_id` at runtime before casting. This prevents casting
1042    /// `*const c_void` to the wrong type, which would be immediate UB.
1043    ///
1044    /// # Borrow Checking
1045    ///
1046    /// Checks `can_be_shared()` to enforce Rust's borrowing rules:
1047    /// - Multiple shared borrows are allowed
1048    /// - Shared and mutable borrows cannot coexist
1049    ///
1050    /// # Safety
1051    ///
1052    /// The `unsafe` cast is safe because:
1053    /// - Type ID check ensures `U` matches the stored type
1054    /// - Memory was allocated with correct alignment for `U`
1055    /// - Lifetime `'a` is tied to `&'a mut self`, preventing use-after-free
1056    /// - Reference count is incremented atomically before returning
1057    ///
1058    /// # Why `&mut self`?
1059    ///
1060    /// Requires `&mut self` to prevent multiple threads from calling this
1061    /// simultaneously on the same `RefAny`. The borrow checker enforces this.
1062    /// Clones of the `RefAny` can call this independently (they share data
1063    /// but have separate runtime borrow tracking).
1064    #[allow(clippy::used_underscore_binding)]
1065    // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
1066    #[inline]
1067    pub fn downcast_ref<U: 'static>(&mut self) -> Option<Ref<'_, U>> {
1068        // A RELEASED `RefAny` holds nothing to borrow. Checked explicitly
1069        // rather than relying on `get_type_id`'s `0` never colliding with a
1070        // real `TypeId` hash.
1071        if self.sharing_info.is_released() {
1072            report_released_downcast();
1073            return None;
1074        }
1075        // Runtime type check: prevent downcasting to wrong type
1076        let stored_type_id = self.get_type_id();
1077        let target_type_id = Self::get_type_id_static::<U>();
1078        let is_same_type = stored_type_id == target_type_id;
1079
1080        if !is_same_type {
1081            return None;
1082        }
1083
1084        // AUDIT: ATOMIC shared-borrow acquisition.
1085        //
1086        // `RefAny` is `Sync` and clones share one `RefCountInner`, so the old
1087        // check-then-increment (`can_be_shared()` then `increase_ref()`) raced a
1088        // concurrent `downcast_mut` on another clone: both could pass their
1089        // pre-checks and hand out aliasing `&`/`&mut` to the same memory (UB).
1090        //
1091        // Fix (mirrors the `compare_exchange` discipline in `replace_contents`):
1092        // increment `num_refs` FIRST, then validate that no mutable borrow is
1093        // live. `SeqCst` imposes a single total order, so a writer (which CASes
1094        // `num_mutable_refs` 0->1 then reads `num_refs`) and this reader (which
1095        // adds to `num_refs` then reads `num_mutable_refs`) can never both
1096        // succeed — at least one observes the other's write. Back the increment
1097        // out on any failure path.
1098        self.sharing_info.increase_ref();
1099
1100        if !self.sharing_info.can_be_shared() {
1101            // A mutable borrow is (being) acquired — release and fail.
1102            self.sharing_info.decrease_ref();
1103            return None;
1104        }
1105
1106        // Get data pointer from shared RefCountInner (stable while we hold the
1107        // shared borrow: `replace_contents` needs `num_refs == 0` to proceed).
1108        let data_ptr = self.sharing_info.downcast()._internal_ptr;
1109
1110        // A null `_internal_ptr` means either an uninitialized `RefAny` or a ZST:
1111        // `RefAny::new_c` stores ZSTs with a null pointer (they need no backing
1112        // allocation). A ZST is a *valid* stored value, so the type check above is
1113        // authoritative and a `&U` to a ZST dereferences no bytes — only a
1114        // *non-ZST* null pointer is a genuine failure.
1115        if data_ptr.is_null() && size_of::<U>() != 0 {
1116            self.sharing_info.decrease_ref();
1117            return None;
1118        }
1119
1120        Some(Ref {
1121            // SAFETY: type check passed. For a real value `data_ptr` is non-null
1122            // and correctly aligned; for a ZST (null pointer) we hand out a
1123            // dangling-but-aligned `NonNull::dangling` reference, valid precisely
1124            // because it is never dereferenced for bytes.
1125            ptr: unsafe {
1126                if data_ptr.is_null() {
1127                    &*core::ptr::NonNull::<U>::dangling().as_ptr()
1128                } else {
1129                    &*(data_ptr as *const U)
1130                }
1131            },
1132            sharing_info: self.sharing_info.clone(),
1133        })
1134    }
1135
1136    /// Attempts to downcast to a mutable reference of type `U`.
1137    ///
1138    /// Returns `None` if:
1139    /// - The stored type doesn't match `U` (type safety)
1140    /// - Any borrow is already active (borrow checking)
1141    /// - The pointer is null AND `U` is not zero-sized (uninitialized). A
1142    ///   stored ZST has a null pointer *by design* and downcasts successfully,
1143    ///   via a dangling-but-aligned reference; note that the on-update observer
1144    ///   is NOT fired for a ZST (there are no bytes for it to snapshot).
1145    ///
1146    /// # Type Safety
1147    ///
1148    /// Compares `type_id` at runtime before casting, preventing UB.
1149    ///
1150    /// # Borrow Checking
1151    ///
1152    /// Checks `can_be_shared_mut()` to enforce exclusive mutability:
1153    /// - No other borrows (shared or mutable) can be active
1154    /// - This is Rust's `&mut T` rule, enforced at runtime
1155    ///
1156    /// # Safety
1157    ///
1158    /// The `unsafe` cast is safe because:
1159    ///
1160    /// - Type ID check ensures `U` matches the stored type
1161    /// - Memory was allocated with correct alignment for `U`
1162    /// - Borrow check ensures no other references exist
1163    /// - Lifetime `'a` is tied to `&'a mut self`, preventing aliasing
1164    /// - Mutable reference count is incremented atomically
1165    ///
1166    /// # Memory Ordering
1167    ///
1168    /// The `increase_refmut()` uses `SeqCst`, ensuring other threads see
1169    /// this mutable borrow before they try to acquire any borrow.
1170    #[allow(clippy::used_underscore_binding)]
1171    // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
1172    #[inline]
1173    pub fn downcast_mut<U: 'static>(&mut self) -> Option<RefMut<'_, U>> {
1174        if self.sharing_info.is_released() {
1175            report_released_downcast();
1176            return None;
1177        }
1178        // Runtime type check
1179        let is_same_type = self.get_type_id() == Self::get_type_id_static::<U>();
1180        if !is_same_type {
1181            return None;
1182        }
1183
1184        // AUDIT: ATOMIC exclusive-borrow acquisition (mirror `replace_contents`).
1185        //
1186        // The old check-then-increment (`can_be_shared_mut()` then
1187        // `increase_refmut()`) raced concurrent borrows on sibling clones and
1188        // could hand out an aliasing `&mut` (UB). Instead, `compare_exchange`
1189        // `num_mutable_refs` 0->1 to atomically take the exclusive slot, THEN
1190        // verify no shared borrow is live; release + fail otherwise. The CAS
1191        // both acquires and rejects a second mutable borrow in one step.
1192        let inner = self.sharing_info.downcast();
1193        if inner
1194            .num_mutable_refs
1195            .compare_exchange(0, 1, AtomicOrdering::SeqCst, AtomicOrdering::SeqCst)
1196            .is_err()
1197        {
1198            return None;
1199        }
1200        if inner.num_refs.load(AtomicOrdering::SeqCst) != 0 {
1201            // A shared borrow is live — release the exclusive slot and fail.
1202            inner.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
1203            return None;
1204        }
1205
1206        // Get data pointer from shared RefCountInner
1207        let data_ptr = inner._internal_ptr;
1208
1209        // A null `_internal_ptr` is either an uninitialized `RefAny` or a ZST
1210        // (stored with a null pointer; see `downcast_ref`). A non-ZST null is a
1211        // real failure — release the exclusive slot and bail. For a ZST there are
1212        // no bytes to observe or mutate, so skip the update observer below and
1213        // hand out a dangling-but-aligned `&mut`, keeping the exclusive borrow.
1214        if data_ptr.is_null() {
1215            if size_of::<U>() != 0 {
1216                inner.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
1217                return None;
1218            }
1219            return Some(RefMut {
1220                // SAFETY: type check passed; `U` is a ZST, so a dangling-but-
1221                // aligned pointer is a valid `&mut` never dereferenced for bytes.
1222                ptr: unsafe { &mut *core::ptr::NonNull::<U>::dangling().as_ptr() },
1223                sharing_info: self.sharing_info.clone(),
1224            });
1225        }
1226
1227        // Fire the on-update observer (if registered) BEFORE handing out the
1228        // mutable borrow: the callback sees the pre-mutation data + its byte
1229        // length, enabling undo/redo snapshots and client/server state sync.
1230        let update_fn = inner.update_fn;
1231        if update_fn != 0 {
1232            // SAFETY: `update_fn` is non-zero (checked) and, per `set_update_fn`'s
1233            // contract, is a valid `extern "C" fn(*const c_void, usize)`. The
1234            // round-trip goes through an int-to-pointer CAST (not a direct
1235            // usize->fn transmute): a transmuted integer carries no provenance,
1236            // which is UB to call (Miri rejects it); the cast re-acquires it.
1237            let cb: extern "C" fn(*const c_void, usize) =
1238                unsafe { core::mem::transmute(update_fn as *const ()) };
1239            let len = inner._internal_len;
1240            // AUDIT: the observer is a host-provided `extern "C"` fn. A Rust
1241            // panic escaping it would unwind across the FFI boundary (UB), so
1242            // contain it. `catch_unwind` needs `std`; `no_std` builds use
1243            // `panic = "abort"` where no unwinding can occur.
1244            #[cfg(feature = "std")]
1245            {
1246                drop(std::panic::catch_unwind(std::panic::AssertUnwindSafe(
1247                    || {
1248                        cb(data_ptr, len);
1249                    },
1250                )));
1251            }
1252            #[cfg(not(feature = "std"))]
1253            {
1254                cb(data_ptr, len);
1255            }
1256        }
1257
1258        Some(RefMut {
1259            // SAFETY: Type and borrow checks passed, exclusive access guaranteed
1260            ptr: unsafe { &mut *(data_ptr as *mut U) },
1261            sharing_info: self.sharing_info.clone(),
1262        })
1263    }
1264
1265    /// Computes a runtime type ID from Rust's `TypeId`.
1266    ///
1267    /// Rust's `TypeId` is not `#[repr(C)]` and can't cross FFI boundaries.
1268    /// This function converts it to a `u64` by treating it as a byte array.
1269    ///
1270    /// # Safety
1271    ///
1272    /// Safe because:
1273    /// - `TypeId` is a valid type with a stable layout
1274    /// - We only read from it, never write
1275    /// - The slice lifetime is bounded by the function scope
1276    ///
1277    /// # Implementation
1278    ///
1279    /// Treats the `TypeId` as bytes and sums them with bit shifts to create
1280    /// a unique (but not cryptographically secure) hash.
1281    #[inline]
1282    fn get_type_id_static<T: 'static>() -> u64 {
1283        use core::{any::TypeId, mem};
1284
1285        let t_id = TypeId::of::<T>();
1286
1287        // SAFETY: TypeId is a valid type, we're only reading it
1288        let struct_as_bytes = unsafe {
1289            core::slice::from_raw_parts((&raw const t_id) as *const u8, size_of::<TypeId>())
1290        };
1291
1292        // AUDIT: fold ALL bytes of the `TypeId` (16 on current toolchains),
1293        // not just the first 8. This u64 is the ONLY runtime type guard used by
1294        // `downcast_*`; dropping the high 8 bytes let two distinct types whose
1295        // `TypeId`s differ only in their upper half collide, permitting a
1296        // wrong-type downcast (UB). An FxHash-style rotate+multiply mixes every
1297        // byte into the result and is deterministic within a process run (which
1298        // is all `TypeId` itself guarantees).
1299        struct_as_bytes.iter().fold(0u64, |hash, &b| {
1300            (hash.rotate_left(5) ^ u64::from(b)).wrapping_mul(0x51_7c_c1_b7_27_22_0a_95)
1301        })
1302    }
1303
1304    /// Checks if the stored type matches the given type ID.
1305    #[must_use]
1306    pub fn is_type(&self, type_id: u64) -> bool {
1307        self.sharing_info.downcast().type_id == type_id
1308    }
1309
1310    /// Returns the stored type ID.
1311    #[must_use]
1312    pub fn get_type_id(&self) -> u64 {
1313        // A RELEASED `RefAny` has no type, and saying so beats aborting. This
1314        // is the gate every `downcast_ref` / `downcast_mut` passes through
1315        // first, so one check here makes the whole safe surface null-tolerant.
1316        // It used to reach `RefCount::downcast`'s assertion instead, which in a
1317        // `panic = "abort"` release build takes the process down — a
1318        // widget callback whose data had been released killed the app on an
1319        // ordinary focus change (device report, 2026-09-01). `0` is not a
1320        // `TypeId` any real `U` hashes to, so no downcast can match it.
1321        if self.sharing_info.is_released() {
1322            return 0;
1323        }
1324        self.sharing_info.downcast().type_id
1325    }
1326
1327    /// Returns the human-readable type name for debugging.
1328    #[must_use]
1329    pub fn get_type_name(&self) -> AzString {
1330        if self.sharing_info.is_released() {
1331            return AzString::from_const_str("<released>");
1332        }
1333        self.sharing_info.downcast().type_name.clone()
1334    }
1335
1336    /// Returns the current reference count (number of `RefAny` clones sharing this data).
1337    ///
1338    /// This is useful for debugging and metadata purposes.
1339    #[must_use]
1340    pub fn get_ref_count(&self) -> usize {
1341        self.sharing_info
1342            .downcast()
1343            .num_copies
1344            .load(AtomicOrdering::SeqCst)
1345    }
1346
1347    /// Returns the serialize function pointer (0 = not set).
1348    ///
1349    /// This is used for JSON serialization of `RefAny` contents.
1350    #[must_use]
1351    pub fn get_serialize_fn(&self) -> usize {
1352        self.sharing_info.downcast().serialize_fn
1353    }
1354
1355    /// Returns the deserialize function pointer (0 = not set).
1356    ///
1357    /// This is used for JSON deserialization to create a new `RefAny`.
1358    #[must_use]
1359    pub fn get_deserialize_fn(&self) -> usize {
1360        self.sharing_info.downcast().deserialize_fn
1361    }
1362
1363    /// Sets the serialize function pointer.
1364    ///
1365    /// # Safety
1366    ///
1367    /// The caller must ensure the function pointer is valid and has the correct
1368    /// signature: `extern "C" fn(RefAny) -> Json`
1369    ///
1370    /// **Known issue:** `&mut self` is exclusive to this clone, not to the shared
1371    /// `RefCountInner`. Concurrent calls via different clones are a data race
1372    /// because `serialize_fn` is a plain `usize`, not atomic.
1373    pub fn set_serialize_fn(&mut self, serialize_fn: usize) {
1374        // FIXME: &mut self is exclusive to this clone only, not to the shared
1375        // RefCountInner — concurrent calls via different clones are a data race.
1376        let inner = self.sharing_info.ptr.cast_mut();
1377        // SAFETY: `inner` came from `Box::into_raw` and is live (we hold `self`).
1378        unsafe {
1379            (*inner).serialize_fn = serialize_fn;
1380        }
1381    }
1382
1383    /// Sets the deserialize function pointer.
1384    ///
1385    /// # Safety
1386    ///
1387    /// The caller must ensure the function pointer is valid and has the correct
1388    /// signature: `extern "C" fn(Json) -> ResultRefAnyString`
1389    ///
1390    /// **Known issue:** `&mut self` is exclusive to this clone, not to the shared
1391    /// `RefCountInner`. Concurrent calls via different clones are a data race
1392    /// because `deserialize_fn` is a plain `usize`, not atomic.
1393    pub fn set_deserialize_fn(&mut self, deserialize_fn: usize) {
1394        // FIXME: &mut self is exclusive to this clone only, not to the shared
1395        // RefCountInner — concurrent calls via different clones are a data race.
1396        let inner = self.sharing_info.ptr.cast_mut();
1397        // SAFETY: `inner` came from `Box::into_raw` and is live (we hold `self`).
1398        unsafe {
1399            (*inner).deserialize_fn = deserialize_fn;
1400        }
1401    }
1402
1403    /// Registers an on-update observer (`0` = unset). It is fired from
1404    /// [`Self::downcast_mut`] with the (data ptr, byte len) of the *pre-mutation*
1405    /// data, just before the mutable borrow is handed out — the foundation for
1406    /// undo/redo snapshots and client/server state sync.
1407    ///
1408    /// # Safety
1409    ///
1410    /// If `update_fn != 0` it must be a valid `extern "C" fn(*const c_void, usize)`.
1411    /// Same shared-`RefCountInner` caveat as [`Self::set_serialize_fn`]: `&mut self`
1412    /// is exclusive to this clone, not to the shared inner.
1413    pub fn set_update_fn(&mut self, update_fn: usize) {
1414        let inner = self.sharing_info.ptr.cast_mut();
1415        // SAFETY: `inner` came from `Box::into_raw` and is live (we hold `self`).
1416        unsafe {
1417            (*inner).update_fn = update_fn;
1418        }
1419    }
1420
1421    /// Returns the registered on-update observer fn pointer (`0` = unset).
1422    #[must_use]
1423    pub fn get_update_fn(&self) -> usize {
1424        self.sharing_info.downcast().update_fn
1425    }
1426
1427    /// Returns true if this `RefAny` supports JSON serialization.
1428    #[must_use]
1429    pub fn can_serialize(&self) -> bool {
1430        self.get_serialize_fn() != 0
1431    }
1432
1433    /// Returns true if this `RefAny` type supports JSON deserialization.
1434    #[must_use]
1435    pub fn can_deserialize(&self) -> bool {
1436        self.get_deserialize_fn() != 0
1437    }
1438
1439    /// Replaces the contents of this `RefAny` with a new value from another `RefAny`.
1440    ///
1441    /// This method:
1442    /// 1. Atomically acquires a mutable "lock" via `compare_exchange`
1443    /// 2. Calls the destructor on the old value
1444    /// 3. Deallocates the old memory
1445    /// 4. Copies the new value's memory
1446    /// 5. Updates metadata (`type_id`, `type_name`, destructor, serialize/deserialize fns)
1447    /// 6. Updates the shared _`internal_ptr` so ALL clones see the new data
1448    /// 7. Releases the lock
1449    ///
1450    /// Since all clones of a `RefAny` share the same `RefCountInner`, this change
1451    /// will be visible to ALL clones of this `RefAny`.
1452    ///
1453    /// # Returns
1454    ///
1455    /// - `true` if the replacement was successful
1456    /// - `false` if there are active borrows (would cause UB)
1457    ///
1458    /// # Thread Safety
1459    ///
1460    /// Uses `compare_exchange` to atomically acquire exclusive access, preventing
1461    /// any race condition between checking for borrows and modifying the data.
1462    ///
1463    /// # Safety
1464    ///
1465    /// Safe because:
1466    /// - We atomically acquire exclusive access before modifying
1467    /// - The old destructor is called before deallocation
1468    /// - Memory is properly allocated with correct alignment
1469    /// - All metadata is updated while holding the lock
1470    ///
1471    /// # Panics
1472    ///
1473    /// Panics if a memory `Layout` for the replacement value cannot be
1474    /// constructed (its size overflows `isize::MAX`).
1475    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
1476    pub fn replace_contents(&mut self, new_value: Self) -> bool {
1477        use core::ptr;
1478
1479        let inner = self.sharing_info.ptr.cast_mut();
1480
1481        // Atomically acquire exclusive access by setting num_mutable_refs to 1.
1482        // This uses compare_exchange to ensure no race condition:
1483        // - If num_mutable_refs is 0, set it to 1 (success)
1484        // - If num_mutable_refs is not 0, someone else has it (fail)
1485        // We also need to check num_refs == 0 atomically.
1486        let inner_ref = self.sharing_info.downcast();
1487
1488        // First, try to acquire the mutable lock
1489        let mutable_lock_result = inner_ref.num_mutable_refs.compare_exchange(
1490            0, // expected: no mutable refs
1491            1, // desired: we take the mutable ref
1492            AtomicOrdering::SeqCst,
1493            AtomicOrdering::SeqCst,
1494        );
1495
1496        if mutable_lock_result.is_err() {
1497            // Someone else has a mutable reference
1498            return false;
1499        }
1500
1501        // Now check that there are no shared references
1502        // Note: We hold the mutable lock, so no new shared refs can be acquired
1503        if inner_ref.num_refs.load(AtomicOrdering::SeqCst) != 0 {
1504            // Release the lock and fail
1505            inner_ref.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
1506            return false;
1507        }
1508
1509        // We now have exclusive access - perform the replacement
1510        // SAFETY: we hold the exclusive lock (num_mutable_refs==1, num_refs==0),
1511        // so no live `Ref`/`RefMut` aliases the data; `inner` is the live
1512        // `RefCountInner` from `Box::into_raw`. Old data is destructed+freed with
1513        // its own stored layout before the pointer is overwritten, and the new
1514        // data is freshly allocated and byte-copied.
1515        unsafe {
1516            // Get old layout info before we overwrite it
1517            let old_ptr = (*inner)._internal_ptr;
1518            let old_len = (*inner)._internal_len;
1519            let old_layout_size = (*inner)._internal_layout_size;
1520            let old_layout_align = (*inner)._internal_layout_align;
1521            let old_destructor = (*inner).custom_destructor;
1522
1523            // Step 1: Call destructor on old value (if non-ZST)
1524            if old_len > 0 && !old_ptr.is_null() {
1525                old_destructor(old_ptr.cast_mut());
1526            }
1527
1528            // Step 2: Deallocate old memory (if non-ZST). Use the *checked*
1529            // `Layout::from_size_align` (not `_unchecked`): the stored
1530            // size/align came from a valid `Layout`, so it always succeeds, and
1531            // this shrinks the unchecked surface inside this unsafe block.
1532            if old_layout_size > 0 && !old_ptr.is_null() {
1533                let old_layout = Layout::from_size_align(old_layout_size, old_layout_align)
1534                    .expect("replace_contents: stored old layout was invalid");
1535                alloc::alloc::dealloc(old_ptr as *mut u8, old_layout);
1536            }
1537
1538            // Get new value's metadata
1539            let new_inner = new_value.sharing_info.downcast();
1540            let new_ptr = new_inner._internal_ptr;
1541            let new_len = new_inner._internal_len;
1542            let new_layout_size = new_inner._internal_layout_size;
1543            let new_layout_align = new_inner._internal_layout_align;
1544
1545            // Step 3: Allocate new memory and copy data
1546            let allocated_ptr = if new_len == 0 {
1547                ptr::null_mut()
1548            } else {
1549                let new_layout = Layout::from_size_align(new_len, new_layout_align)
1550                    .expect("Failed to create layout");
1551                let heap_ptr = alloc::alloc::alloc(new_layout);
1552                if heap_ptr.is_null() {
1553                    alloc::alloc::handle_alloc_error(new_layout);
1554                }
1555                // Copy data from new_value
1556                ptr::copy_nonoverlapping(new_ptr as *const u8, heap_ptr, new_len);
1557                heap_ptr
1558            };
1559
1560            // Step 4: Update the shared internal pointer in RefCountInner
1561            // All clones will see this new pointer!
1562            (*inner)._internal_ptr = allocated_ptr as *const c_void;
1563
1564            // Step 5: Update metadata in RefCountInner
1565            (*inner)._internal_len = new_len;
1566            (*inner)._internal_layout_size = new_layout_size;
1567            (*inner)._internal_layout_align = new_layout_align;
1568            (*inner).type_id = new_inner.type_id;
1569            (*inner).type_name = new_inner.type_name.clone();
1570            (*inner).custom_destructor = new_inner.custom_destructor;
1571            (*inner).serialize_fn = new_inner.serialize_fn;
1572            (*inner).deserialize_fn = new_inner.deserialize_fn;
1573            (*inner).update_fn = new_inner.update_fn;
1574        }
1575
1576        // Release the mutable lock
1577        self.sharing_info
1578            .downcast()
1579            .num_mutable_refs
1580            .store(0, AtomicOrdering::SeqCst);
1581
1582        // AUDIT: reclaim `new_value` instead of leaking it.
1583        //
1584        // The old code `mem::forget(new_value)` to stop `RefAny::drop` from
1585        // running the T-destructor a SECOND time on the bytes we just copied
1586        // into our own allocation — but that leaked `new_value`'s entire
1587        // `RefCountInner` box AND its heap data block on every single call.
1588        //
1589        // Instead, neutralize `new_value`'s destructor to a no-op and let the
1590        // normal refcount teardown run: it frees BOTH allocations (data block +
1591        // inner box) when this was the last reference, without re-running the
1592        // real T-destructor (which now lives on OUR inner, to run exactly once
1593        // when `self` is finally dropped). If `new_value` still had clones, the
1594        // no-op keeps them from double-dropping the shared T while their own
1595        // last drop still reclaims the shared block — no double free, no leak.
1596        #[allow(clippy::items_after_statements)]
1597        const extern "C" fn noop_destructor(_: *mut c_void) {}
1598        let new_inner = new_value.sharing_info.ptr.cast_mut();
1599        if !new_inner.is_null() {
1600            // SAFETY: `new_inner` came from `Box::into_raw` in `RefCount::new`
1601            // and is still alive (we hold `new_value`).
1602            unsafe {
1603                (*new_inner).custom_destructor = noop_destructor;
1604            }
1605        }
1606        drop(new_value);
1607
1608        true
1609    }
1610}
1611
1612impl Clone for RefAny {
1613    /// Creates a new `RefAny` sharing the same heap-allocated data.
1614    ///
1615    /// This is cheap (just increments a counter) and is how multiple parts
1616    /// of the code can hold references to the same data.
1617    ///
1618    /// # Reference Counting
1619    ///
1620    /// Atomically increments `num_copies` with `SeqCst` ordering before
1621    /// creating the clone. This ensures all threads see the updated count
1622    /// before the clone can be used.
1623    ///
1624    /// # Instance ID
1625    ///
1626    /// Each clone gets a unique `instance_id` based on the current copy count.
1627    /// The original has `instance_id=0`, the first clone gets `1`, etc.
1628    ///
1629    /// # Memory Ordering
1630    ///
1631    /// The `fetch_add` followed by `load` both use `SeqCst`:
1632    /// - `fetch_add`: Ensures the increment is visible to all threads
1633    /// - `load`: Gets the updated value for the `instance_id`
1634    ///
1635    /// This prevents race conditions where two threads clone simultaneously
1636    /// and both see the same `instance_id`.
1637    ///
1638    /// # Safety
1639    ///
1640    /// Safe because:
1641    ///
1642    /// - Atomic operations prevent data races
1643    /// - The heap allocation remains valid (only freed when count reaches 0)
1644    /// - `run_destructor` is set to `true` for all clones
1645    fn clone(&self) -> Self {
1646        // Atomically increment the reference count
1647        let inner = self.sharing_info.downcast();
1648        let prev = inner.num_copies.fetch_add(1, AtomicOrdering::SeqCst);
1649
1650        let new_instance_id = (prev + 1) as u64;
1651
1652        Self {
1653            // Data pointer is now in RefCountInner, shared automatically
1654            sharing_info: RefCount {
1655                ptr: self.sharing_info.ptr, // Share the same metadata (and data pointer)
1656                run_destructor: true,       // This clone should decrement num_copies on drop
1657            },
1658            // Give this clone a unique ID based on the updated count
1659            instance_id: new_instance_id,
1660        }
1661    }
1662}
1663
1664impl Drop for RefAny {
1665    /// Empty drop implementation - all cleanup is handled by `RefCount::drop`.
1666    ///
1667    /// When a `RefAny` is dropped, its `sharing_info: RefCount` field is automatically
1668    /// dropped by Rust. The `RefCount::drop` implementation handles all cleanup:
1669    ///
1670    /// 1. Atomically decrements `num_copies` with `fetch_sub`
1671    /// 2. If the previous value was 1 (we're the last reference):
1672    ///    - Reclaims the `RefCountInner` via `Box::from_raw`
1673    ///    - Calls the custom destructor to run `T::drop()`
1674    ///    - Deallocates the heap memory with the stored layout
1675    ///
1676    /// # Why No Code Here?
1677    ///
1678    /// Previously, `RefAny::drop` handled cleanup, but this caused issues with the
1679    /// C API where `Ref<T>` and `RefMut<T>` guards (which clone the `RefCount`) need
1680    /// to keep the data alive even after the original `RefAny` is dropped.
1681    ///
1682    /// By moving all cleanup to `RefCount::drop`, we ensure that:
1683    /// - `RefAny::clone()` creates a `RefCount` with `run_destructor = true`
1684    /// - `AZ_REFLECT` macros create `Ref`/`RefMut` guards that clone `RefCount`
1685    /// - Each `RefCount` drop decrements the counter
1686    /// - Only the LAST drop (when `num_copies` was 1) cleans up memory
1687    ///
1688    /// See `RefCount::drop` for the full algorithm and safety documentation.
1689    fn drop(&mut self) {
1690        // RefCount::drop handles everything automatically.
1691        // The sharing_info field is dropped by Rust, triggering RefCount::drop.
1692    }
1693}
1694
1695#[cfg(test)]
1696#[path = "refany_test.rs"]
1697mod refany_test;