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

            
32
use alloc::boxed::Box;
33
use alloc::string::String;
34
use core::{
35
    alloc::Layout,
36
    ffi::c_void,
37
    fmt,
38
    sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
39
};
40

            
41
use azul_css::AzString;
42

            
43
/// C-compatible destructor function type for `RefAny`.
44
/// Called when the last reference to a `RefAny` is dropped.
45
pub 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)]
73
pub 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)]
154
pub struct RefCount {
155
    pub ptr: *const RefCountInner,
156
    pub run_destructor: bool,
157
}
158

            
159
impl fmt::Debug for RefCount {
160
1
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161
1
        self.downcast().fmt(f)
162
1
    }
163
}
164

            
165
impl 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
140461
    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
140461
        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
140461
            unsafe {
183
140461
                (*self.ptr).num_copies.fetch_add(1, AtomicOrdering::SeqCst);
184
140461
            }
185
        }
186
140461
        Self {
187
140461
            ptr: self.ptr,
188
140461
            run_destructor: true,
189
140461
        }
190
140461
    }
191
}
192

            
193
impl 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
1048525
    fn drop(&mut self) {
200
        // Only decrement if run_destructor is true (meaning this is a clone)
201
        // and the pointer is valid
202
1048525
        if !self.run_destructor || self.ptr.is_null() {
203
            return;
204
1048525
        }
205
1048525
        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
1048525
        let inner = self.ptr;
218
1048525
        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
1048525
        let current_copies = unsafe {
228
1048525
            match (*inner).num_copies.fetch_update(
229
1048525
                AtomicOrdering::SeqCst,
230
1048525
                AtomicOrdering::SeqCst,
231
1048525
                |n| n.checked_sub(1),
232
            ) {
233
1048525
                Ok(prev) => prev,
234
                Err(_zero) => return,
235
            }
236
        };
237

            
238
        // If previous value wasn't 1, other references still exist
239
1048525
        if current_copies != 1 {
240
785608
            return;
241
262917
        }
242

            
243
        // We're the last reference! Clean up.
244
        // SAFETY: ptr came from Box::into_raw, and we're the last reference
245
262917
        let sharing_info = unsafe { Box::from_raw(inner.cast_mut()) };
246
262917
        let sharing_info = *sharing_info; // Box deallocates RefCountInner here
247

            
248
        // Get the data pointer
249
262917
        let data_ptr = sharing_info._internal_ptr;
250

            
251
        // Handle zero-sized types specially
252
262917
        if sharing_info._internal_len == 0
253
252735
            || sharing_info._internal_layout_size == 0
254
252735
            || data_ptr.is_null()
255
10182
        {
256
10182
            let mut _dummy: [u8; 0] = [];
257
10182
            // Call destructor even for ZSTs (may have side effects)
258
10182
            (sharing_info.custom_destructor)(_dummy.as_mut_ptr().cast::<c_void>());
259
10182
        } 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
252735
            let layout = Layout::from_size_align(
266
252735
                sharing_info._internal_layout_size,
267
252735
                sharing_info._internal_layout_align,
268
            )
269
252735
            .expect("RefCount::drop: stored layout was invalid");
270

            
271
            // Phase 1: Run the custom destructor
272
252735
            (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
252735
            unsafe {
279
252735
                alloc::alloc::dealloc(data_ptr as *mut u8, layout);
280
252735
            }
281
        }
282
1048525
    }
283
}
284

            
285
/// Debug-friendly snapshot of `RefCountInner` with non-atomic values.
286
#[derive(Debug, Clone)]
287
pub(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
impl 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
263049
    fn new(ref_count: RefCountInner) -> Self {
311
263049
        Self {
312
263049
            ptr: Box::into_raw(Box::new(ref_count)),
313
263049
            run_destructor: true,
314
263049
        }
315
263049
    }
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
1290009
    fn downcast(&self) -> &RefCountInner {
326
1290009
        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
1290009
        unsafe { &*self.ptr }
331
1290009
    }
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
35
    pub(crate) fn debug_get_refcount_copied(&self) -> RefCountInnerDebug {
338
35
        let dc = self.downcast();
339
35
        RefCountInnerDebug {
340
35
            num_copies: dc.num_copies.load(AtomicOrdering::SeqCst),
341
35
            num_refs: dc.num_refs.load(AtomicOrdering::SeqCst),
342
35
            num_mutable_refs: dc.num_mutable_refs.load(AtomicOrdering::SeqCst),
343
35
            _internal_len: dc._internal_len,
344
35
            _internal_layout_size: dc._internal_layout_size,
345
35
            _internal_layout_align: dc._internal_layout_align,
346
35
            type_id: dc.type_id,
347
35
            type_name: dc.type_name.clone(),
348
35
            custom_destructor: dc.custom_destructor as usize,
349
35
            serialize_fn: dc.serialize_fn,
350
35
            deserialize_fn: dc.deserialize_fn,
351
35
        }
352
35
    }
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
108768
    #[must_use] pub fn can_be_shared(&self) -> bool {
364
108768
        self.downcast()
365
108768
            .num_mutable_refs
366
108768
            .load(AtomicOrdering::SeqCst)
367
108768
            == 0
368
108768
    }
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
14
    #[must_use] pub fn can_be_shared_mut(&self) -> bool {
380
14
        let info = self.downcast();
381
14
        info.num_mutable_refs.load(AtomicOrdering::SeqCst) == 0
382
13
            && info.num_refs.load(AtomicOrdering::SeqCst) == 0
383
14
    }
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
109017
    pub fn increase_ref(&self) {
394
109017
        self.downcast()
395
109017
            .num_refs
396
109017
            .fetch_add(1, AtomicOrdering::SeqCst);
397
109017
    }
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
109082
    pub fn decrease_ref(&self) {
417
109082
        let _ = self.downcast().num_refs.fetch_update(
418
109082
            AtomicOrdering::SeqCst,
419
109082
            AtomicOrdering::SeqCst,
420
109082
            |n| n.checked_sub(1),
421
        );
422
109082
    }
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
4
    pub fn increase_refmut(&self) {
434
4
        self.downcast()
435
4
            .num_mutable_refs
436
4
            .fetch_add(1, AtomicOrdering::SeqCst);
437
4
    }
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
31857
    pub fn decrease_refmut(&self) {
455
31857
        let _ = self.downcast().num_mutable_refs.fetch_update(
456
31857
            AtomicOrdering::SeqCst,
457
31857
            AtomicOrdering::SeqCst,
458
31857
            |n| n.checked_sub(1),
459
        );
460
31857
    }
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)]
473
pub struct Ref<'a, T> {
474
    ptr: &'a T,
475
    sharing_info: RefCount,
476
}
477

            
478
impl<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
10938
    fn drop(&mut self) {
486
10938
        self.sharing_info.decrease_ref();
487
10938
    }
488
}
489

            
490
impl<T> core::ops::Deref for Ref<'_, T> {
491
    type Target = T;
492

            
493
11661
    fn deref(&self) -> &Self::Target {
494
11661
        self.ptr
495
11661
    }
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)]
508
pub struct RefMut<'a, T> {
509
    ptr: &'a mut T,
510
    sharing_info: RefCount,
511
}
512

            
513
impl<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
2947
    fn drop(&mut self) {
521
2947
        self.sharing_info.decrease_refmut();
522
2947
    }
523
}
524

            
525
impl<T> core::ops::Deref for RefMut<'_, T> {
526
    type Target = T;
527

            
528
8467
    fn deref(&self) -> &Self::Target {
529
8467
        &*self.ptr
530
8467
    }
531
}
532

            
533
impl<T> core::ops::DerefMut for RefMut<'_, T> {
534
8353
    fn deref_mut(&mut self) -> &mut Self::Target {
535
8353
        self.ptr
536
8353
    }
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)]
576
pub 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`.
608
impl PartialEq for RefAny {
609
696
    fn eq(&self, other: &Self) -> bool {
610
696
        self.sharing_info == other.sharing_info
611
696
    }
612
}
613

            
614
impl Eq for RefAny {}
615

            
616
impl core::hash::Hash for RefAny {
617
3100
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
618
3100
        core::hash::Hash::hash(&self.sharing_info, state);
619
3100
    }
620
}
621

            
622
impl PartialOrd for RefAny {
623
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
624
        Some(self.cmp(other))
625
    }
626
}
627

            
628
impl Ord for RefAny {
629
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
630
        self.sharing_info.cmp(&other.sharing_info)
631
    }
632
}
633

            
634
impl_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
654
unsafe 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.
667
unsafe impl Sync for RefAny {}
668

            
669
impl 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
37443
    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
37370
        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
37370
            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
37370
                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
1818
                    drop(mem::MaybeUninit::<U>::uninit().assume_init());
753
1818
                    return;
754
35552
                }
755

            
756
                // Allocate uninitialized stack space for one `U`
757
35552
                let mut stack_mem = mem::MaybeUninit::<U>::uninit();
758

            
759
                // Copy 1 element of type U from heap to stack
760
35552
                ptr::copy_nonoverlapping(
761
35552
                    ptr as *const U,
762
35552
                    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
35552
                let stack_mem = stack_mem.assume_init();
768
35552
                drop(stack_mem); // Runs U's Drop implementation
769
37370
            };
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
37370
            {
776
37370
                drop(std::panic::catch_unwind(std::panic::AssertUnwindSafe(run)));
777
37370
            }
778
            #[cfg(not(feature = "std"))]
779
            {
780
                run();
781
            }
782
37370
        }
783

            
784
37443
        let type_name = ::core::any::type_name::<T>();
785
37443
        let type_id = Self::get_type_id_static::<T>();
786

            
787
37443
        let st = AzString::from_const_str(type_name);
788
37443
        let s = Self::new_c(
789
37443
            (&raw const value) as *const c_void,
790
37443
            ::core::mem::size_of::<T>(),
791
37443
            ::core::mem::align_of::<T>(), // CRITICAL: Pass alignment to prevent UB
792
37443
            type_id,
793
37443
            st,
794
37443
            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
37443
        ::core::mem::forget(value); // Prevent double-drop
799
37443
        s
800
37443
    }
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
263051
    pub fn new_c(
840
263051
        // *const T
841
263051
        ptr: *const c_void,
842
263051
        // sizeof(T)
843
263051
        len: usize,
844
263051
        // alignof(T)
845
263051
        align: usize,
846
263051
        // unique ID of the type (used for type comparison when downcasting)
847
263051
        type_id: u64,
848
263051
        // name of the class such as "app::MyData", usually compiler- or macro-generated
849
263051
        type_name: AzString,
850
263051
        custom_destructor: extern "C" fn(*mut c_void),
851
263051
        // function pointer for JSON serialization (0 = not set)
852
263051
        serialize_fn: usize,
853
263051
        // function pointer for JSON deserialization (0 = not set)
854
263051
        deserialize_fn: usize,
855
263051
    ) -> 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
263051
        assert!(!(len > 0 && ptr.is_null()), 
861
1
                "RefAny::new_c: NULL pointer passed for non-ZST type (size={}). \
862
1
                This would cause undefined behavior. Type: {:?}",
863
                len,
864
1
                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
263050
        let (_internal_ptr, layout) = if len == 0 {
872
10247
            let _dummy: [u8; 0] = [];
873
10247
            (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
252803
            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
252803
            let heap_struct_as_bytes = unsafe { alloc::alloc::alloc(layout) };
887

            
888
            // Handle allocation failure (aborts the program)
889
252803
            if heap_struct_as_bytes.is_null() {
890
2
                alloc::alloc::handle_alloc_error(layout);
891
252801
            }
892

            
893
            // Copy the data byte-by-byte to the heap
894
            // SAFETY: Both pointers are valid, non-overlapping, and properly aligned
895
252801
            unsafe { ptr::copy_nonoverlapping(ptr as *const u8, heap_struct_as_bytes, len) };
896

            
897
252801
            (heap_struct_as_bytes, layout)
898
        };
899

            
900
263048
        let ref_count_inner = RefCountInner {
901
263048
            _internal_ptr: _internal_ptr as *const c_void,
902
263048
            num_copies: AtomicUsize::new(1),       // This is the first instance
903
263048
            num_refs: AtomicUsize::new(0),         // No borrows yet
904
263048
            num_mutable_refs: AtomicUsize::new(0), // No mutable borrows yet
905
263048
            _internal_len: len,
906
263048
            _internal_layout_size: layout.size(),
907
263048
            _internal_layout_align: layout.align(),
908
263048
            type_id,
909
263048
            type_name,
910
263048
            custom_destructor,
911
263048
            serialize_fn,
912
263048
            deserialize_fn,
913
263048
            update_fn: 0, // on-update observer not set by default; see set_update_fn
914
263048
        };
915

            
916
263048
        let sharing_info = RefCount::new(ref_count_inner);
917

            
918
263048
        Self {
919
263048
            sharing_info,
920
263048
            instance_id: 0, // Root instance
921
263048
        }
922
263048
    }
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
180
    #[must_use] pub fn get_data_ptr(&self) -> *const c_void {
936
180
        self.sharing_info.downcast()._internal_ptr
937
180
    }
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
10
    #[must_use] pub fn get_data_len(&self) -> usize {
944
10
        self.sharing_info.downcast()._internal_len
945
10
    }
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
9
    pub(crate) fn has_no_copies(&self) -> bool {
960
9
        self.sharing_info
961
9
            .downcast()
962
9
            .num_copies
963
9
            .load(AtomicOrdering::SeqCst)
964
9
            == 1
965
5
            && self
966
5
                .sharing_info
967
5
                .downcast()
968
5
                .num_refs
969
5
                .load(AtomicOrdering::SeqCst)
970
5
                == 0
971
5
            && self
972
5
                .sharing_info
973
5
                .downcast()
974
5
                .num_mutable_refs
975
5
                .load(AtomicOrdering::SeqCst)
976
5
                == 0
977
9
    }
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 AND `U` is not zero-sized (uninitialized). A
985
    ///   stored ZST has a null pointer *by design* (nothing is allocated) and
986
    ///   downcasts successfully, via a dangling-but-aligned reference.
987
    ///
988
    /// # Type Safety
989
    ///
990
    /// Compares `type_id` at runtime before casting. This prevents casting
991
    /// `*const c_void` to the wrong type, which would be immediate UB.
992
    ///
993
    /// # Borrow Checking
994
    ///
995
    /// Checks `can_be_shared()` to enforce Rust's borrowing rules:
996
    /// - Multiple shared borrows are allowed
997
    /// - Shared and mutable borrows cannot coexist
998
    ///
999
    /// # Safety
    ///
    /// The `unsafe` cast is safe because:
    /// - Type ID check ensures `U` matches the stored type
    /// - Memory was allocated with correct alignment for `U`
    /// - Lifetime `'a` is tied to `&'a mut self`, preventing use-after-free
    /// - Reference count is incremented atomically before returning
    ///
    /// # Why `&mut self`?
    ///
    /// Requires `&mut self` to prevent multiple threads from calling this
    /// simultaneously on the same `RefAny`. The borrow checker enforces this.
    /// Clones of the `RefAny` can call this independently (they share data
    /// but have separate runtime borrow tracking).
    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
    #[inline]
11110
    pub fn downcast_ref<U: 'static>(&mut self) -> Option<Ref<'_, U>> {
        // Runtime type check: prevent downcasting to wrong type
11110
        let stored_type_id = self.get_type_id();
11110
        let target_type_id = Self::get_type_id_static::<U>();
11110
        let is_same_type = stored_type_id == target_type_id;
11110
        if !is_same_type {
163
            return None;
10947
        }
        // AUDIT: ATOMIC shared-borrow acquisition.
        //
        // `RefAny` is `Sync` and clones share one `RefCountInner`, so the old
        // check-then-increment (`can_be_shared()` then `increase_ref()`) raced a
        // concurrent `downcast_mut` on another clone: both could pass their
        // pre-checks and hand out aliasing `&`/`&mut` to the same memory (UB).
        //
        // Fix (mirrors the `compare_exchange` discipline in `replace_contents`):
        // increment `num_refs` FIRST, then validate that no mutable borrow is
        // live. `SeqCst` imposes a single total order, so a writer (which CASes
        // `num_mutable_refs` 0->1 then reads `num_refs`) and this reader (which
        // adds to `num_refs` then reads `num_mutable_refs`) can never both
        // succeed — at least one observes the other's write. Back the increment
        // out on any failure path.
10947
        self.sharing_info.increase_ref();
10947
        if !self.sharing_info.can_be_shared() {
            // A mutable borrow is (being) acquired — release and fail.
9
            self.sharing_info.decrease_ref();
9
            return None;
10938
        }
        // Get data pointer from shared RefCountInner (stable while we hold the
        // shared borrow: `replace_contents` needs `num_refs == 0` to proceed).
10938
        let data_ptr = self.sharing_info.downcast()._internal_ptr;
        // A null `_internal_ptr` means either an uninitialized `RefAny` or a ZST:
        // `RefAny::new_c` stores ZSTs with a null pointer (they need no backing
        // allocation). A ZST is a *valid* stored value, so the type check above is
        // authoritative and a `&U` to a ZST dereferences no bytes — only a
        // *non-ZST* null pointer is a genuine failure.
10938
        if data_ptr.is_null() && size_of::<U>() != 0 {
            self.sharing_info.decrease_ref();
            return None;
10938
        }
        Some(Ref {
            // SAFETY: type check passed. For a real value `data_ptr` is non-null
            // and correctly aligned; for a ZST (null pointer) we hand out a
            // dangling-but-aligned `NonNull::dangling` reference, valid precisely
            // because it is never dereferenced for bytes.
            ptr: unsafe {
10938
                if data_ptr.is_null() {
5
                    &*core::ptr::NonNull::<U>::dangling().as_ptr()
                } else {
10933
                    &*(data_ptr as *const U)
                }
            },
10938
            sharing_info: self.sharing_info.clone(),
        })
11110
    }
    /// Attempts to downcast to a mutable reference of type `U`.
    ///
    /// Returns `None` if:
    /// - The stored type doesn't match `U` (type safety)
    /// - Any borrow is already active (borrow checking)
    /// - The pointer is null AND `U` is not zero-sized (uninitialized). A
    ///   stored ZST has a null pointer *by design* and downcasts successfully,
    ///   via a dangling-but-aligned reference; note that the on-update observer
    ///   is NOT fired for a ZST (there are no bytes for it to snapshot).
    ///
    /// # Type Safety
    ///
    /// Compares `type_id` at runtime before casting, preventing UB.
    ///
    /// # Borrow Checking
    ///
    /// Checks `can_be_shared_mut()` to enforce exclusive mutability:
    /// - No other borrows (shared or mutable) can be active
    /// - This is Rust's `&mut T` rule, enforced at runtime
    ///
    /// # Safety
    ///
    /// The `unsafe` cast is safe because:
    ///
    /// - Type ID check ensures `U` matches the stored type
    /// - Memory was allocated with correct alignment for `U`
    /// - Borrow check ensures no other references exist
    /// - Lifetime `'a` is tied to `&'a mut self`, preventing aliasing
    /// - Mutable reference count is incremented atomically
    ///
    /// # Memory Ordering
    ///
    /// The `increase_refmut()` uses `SeqCst`, ensuring other threads see
    /// this mutable borrow before they try to acquire any borrow.
    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
    #[inline]
3053
    pub fn downcast_mut<U: 'static>(&mut self) -> Option<RefMut<'_, U>> {
        // Runtime type check
3053
        let is_same_type = self.get_type_id() == Self::get_type_id_static::<U>();
3053
        if !is_same_type {
93
            return None;
2960
        }
        // AUDIT: ATOMIC exclusive-borrow acquisition (mirror `replace_contents`).
        //
        // The old check-then-increment (`can_be_shared_mut()` then
        // `increase_refmut()`) raced concurrent borrows on sibling clones and
        // could hand out an aliasing `&mut` (UB). Instead, `compare_exchange`
        // `num_mutable_refs` 0->1 to atomically take the exclusive slot, THEN
        // verify no shared borrow is live; release + fail otherwise. The CAS
        // both acquires and rejects a second mutable borrow in one step.
2960
        let inner = self.sharing_info.downcast();
2960
        if inner
2960
            .num_mutable_refs
2960
            .compare_exchange(0, 1, AtomicOrdering::SeqCst, AtomicOrdering::SeqCst)
2960
            .is_err()
        {
5
            return None;
2955
        }
2955
        if inner.num_refs.load(AtomicOrdering::SeqCst) != 0 {
            // A shared borrow is live — release the exclusive slot and fail.
8
            inner.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
8
            return None;
2947
        }
        // Get data pointer from shared RefCountInner
2947
        let data_ptr = inner._internal_ptr;
        // A null `_internal_ptr` is either an uninitialized `RefAny` or a ZST
        // (stored with a null pointer; see `downcast_ref`). A non-ZST null is a
        // real failure — release the exclusive slot and bail. For a ZST there are
        // no bytes to observe or mutate, so skip the update observer below and
        // hand out a dangling-but-aligned `&mut`, keeping the exclusive borrow.
2947
        if data_ptr.is_null() {
2
            if size_of::<U>() != 0 {
                inner.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
                return None;
2
            }
2
            return Some(RefMut {
2
                // SAFETY: type check passed; `U` is a ZST, so a dangling-but-
2
                // aligned pointer is a valid `&mut` never dereferenced for bytes.
2
                ptr: unsafe { &mut *core::ptr::NonNull::<U>::dangling().as_ptr() },
2
                sharing_info: self.sharing_info.clone(),
2
            });
2945
        }
        // Fire the on-update observer (if registered) BEFORE handing out the
        // mutable borrow: the callback sees the pre-mutation data + its byte
        // length, enabling undo/redo snapshots and client/server state sync.
2945
        let update_fn = inner.update_fn;
2945
        if update_fn != 0 {
            // SAFETY: `update_fn` is non-zero (checked) and, per `set_update_fn`'s
            // contract, is a valid `extern "C" fn(*const c_void, usize)`. The
            // round-trip goes through an int-to-pointer CAST (not a direct
            // usize->fn transmute): a transmuted integer carries no provenance,
            // which is UB to call (Miri rejects it); the cast re-acquires it.
1
            let cb: extern "C" fn(*const c_void, usize) =
1
                unsafe { core::mem::transmute(update_fn as *const ()) };
1
            let len = inner._internal_len;
            // AUDIT: the observer is a host-provided `extern "C"` fn. A Rust
            // panic escaping it would unwind across the FFI boundary (UB), so
            // contain it. `catch_unwind` needs `std`; `no_std` builds use
            // `panic = "abort"` where no unwinding can occur.
            #[cfg(feature = "std")]
            {
1
                drop(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1
                    cb(data_ptr, len);
1
                })));
            }
            #[cfg(not(feature = "std"))]
            {
                cb(data_ptr, len);
            }
2944
        }
2945
        Some(RefMut {
2945
            // SAFETY: Type and borrow checks passed, exclusive access guaranteed
2945
            ptr: unsafe { &mut *(data_ptr as *mut U) },
2945
            sharing_info: self.sharing_info.clone(),
2945
        })
3053
    }
    /// Computes a runtime type ID from Rust's `TypeId`.
    ///
    /// Rust's `TypeId` is not `#[repr(C)]` and can't cross FFI boundaries.
    /// This function converts it to a `u64` by treating it as a byte array.
    ///
    /// # Safety
    ///
    /// Safe because:
    /// - `TypeId` is a valid type with a stable layout
    /// - We only read from it, never write
    /// - The slice lifetime is bounded by the function scope
    ///
    /// # Implementation
    ///
    /// Treats the `TypeId` as bytes and sums them with bit shifts to create
    /// a unique (but not cryptographically secure) hash.
    #[inline]
51656
    fn get_type_id_static<T: 'static>() -> u64 {
        use core::{any::TypeId, mem};
51656
        let t_id = TypeId::of::<T>();
        // SAFETY: TypeId is a valid type, we're only reading it
51656
        let struct_as_bytes = unsafe {
51656
            core::slice::from_raw_parts(
51656
                (&raw const t_id) as *const u8,
51656
                size_of::<TypeId>(),
            )
        };
        // AUDIT: fold ALL bytes of the `TypeId` (16 on current toolchains),
        // not just the first 8. This u64 is the ONLY runtime type guard used by
        // `downcast_*`; dropping the high 8 bytes let two distinct types whose
        // `TypeId`s differ only in their upper half collide, permitting a
        // wrong-type downcast (UB). An FxHash-style rotate+multiply mixes every
        // byte into the result and is deterministic within a process run (which
        // is all `TypeId` itself guarantees).
826496
        struct_as_bytes.iter().fold(0u64, |hash, &b| {
826496
            (hash.rotate_left(5) ^ u64::from(b)).wrapping_mul(0x51_7c_c1_b7_27_22_0a_95)
826496
        })
51656
    }
    /// Checks if the stored type matches the given type ID.
160
    #[must_use] pub fn is_type(&self, type_id: u64) -> bool {
160
        self.sharing_info.downcast().type_id == type_id
160
    }
    /// Returns the stored type ID.
144735
    #[must_use] pub fn get_type_id(&self) -> u64 {
144735
        self.sharing_info.downcast().type_id
144735
    }
    /// Returns the human-readable type name for debugging.
32
    #[must_use] pub fn get_type_name(&self) -> AzString {
32
        self.sharing_info.downcast().type_name.clone()
32
    }
    /// Returns the current reference count (number of `RefAny` clones sharing this data).
    ///
    /// This is useful for debugging and metadata purposes.
143
    #[must_use] pub fn get_ref_count(&self) -> usize {
143
        self.sharing_info
143
            .downcast()
143
            .num_copies
143
            .load(AtomicOrdering::SeqCst)
143
    }
    /// Returns the serialize function pointer (0 = not set).
    /// 
    /// This is used for JSON serialization of `RefAny` contents.
22
    #[must_use] pub fn get_serialize_fn(&self) -> usize {
22
        self.sharing_info.downcast().serialize_fn
22
    }
    /// Returns the deserialize function pointer (0 = not set).
    /// 
    /// This is used for JSON deserialization to create a new `RefAny`.
20
    #[must_use] pub fn get_deserialize_fn(&self) -> usize {
20
        self.sharing_info.downcast().deserialize_fn
20
    }
    /// Sets the serialize function pointer.
    ///
    /// # Safety
    ///
    /// The caller must ensure the function pointer is valid and has the correct
    /// signature: `extern "C" fn(RefAny) -> Json`
    ///
    /// **Known issue:** `&mut self` is exclusive to this clone, not to the shared
    /// `RefCountInner`. Concurrent calls via different clones are a data race
    /// because `serialize_fn` is a plain `usize`, not atomic.
8
    pub fn set_serialize_fn(&mut self, serialize_fn: usize) {
        // FIXME: &mut self is exclusive to this clone only, not to the shared
        // RefCountInner — concurrent calls via different clones are a data race.
8
        let inner = self.sharing_info.ptr.cast_mut();
        // SAFETY: `inner` came from `Box::into_raw` and is live (we hold `self`).
8
        unsafe {
8
            (*inner).serialize_fn = serialize_fn;
8
        }
8
    }
    /// Sets the deserialize function pointer.
    ///
    /// # Safety
    ///
    /// The caller must ensure the function pointer is valid and has the correct
    /// signature: `extern "C" fn(Json) -> ResultRefAnyString`
    ///
    /// **Known issue:** `&mut self` is exclusive to this clone, not to the shared
    /// `RefCountInner`. Concurrent calls via different clones are a data race
    /// because `deserialize_fn` is a plain `usize`, not atomic.
8
    pub fn set_deserialize_fn(&mut self, deserialize_fn: usize) {
        // FIXME: &mut self is exclusive to this clone only, not to the shared
        // RefCountInner — concurrent calls via different clones are a data race.
8
        let inner = self.sharing_info.ptr.cast_mut();
        // SAFETY: `inner` came from `Box::into_raw` and is live (we hold `self`).
8
        unsafe {
8
            (*inner).deserialize_fn = deserialize_fn;
8
        }
8
    }
    /// Registers an on-update observer (`0` = unset). It is fired from
    /// [`Self::downcast_mut`] with the (data ptr, byte len) of the *pre-mutation*
    /// data, just before the mutable borrow is handed out — the foundation for
    /// undo/redo snapshots and client/server state sync.
    ///
    /// # Safety
    ///
    /// If `update_fn != 0` it must be a valid `extern "C" fn(*const c_void, usize)`.
    /// Same shared-`RefCountInner` caveat as [`Self::set_serialize_fn`]: `&mut self`
    /// is exclusive to this clone, not to the shared inner.
4
    pub fn set_update_fn(&mut self, update_fn: usize) {
4
        let inner = self.sharing_info.ptr.cast_mut();
        // SAFETY: `inner` came from `Box::into_raw` and is live (we hold `self`).
4
        unsafe {
4
            (*inner).update_fn = update_fn;
4
        }
4
    }
    /// Returns the registered on-update observer fn pointer (`0` = unset).
6
    #[must_use] pub fn get_update_fn(&self) -> usize {
6
        self.sharing_info.downcast().update_fn
6
    }
    /// Returns true if this `RefAny` supports JSON serialization.
14
    #[must_use] pub fn can_serialize(&self) -> bool {
14
        self.get_serialize_fn() != 0
14
    }
    /// Returns true if this `RefAny` type supports JSON deserialization.
12
    #[must_use] pub fn can_deserialize(&self) -> bool {
12
        self.get_deserialize_fn() != 0
12
    }
    /// Replaces the contents of this `RefAny` with a new value from another `RefAny`.
    ///
    /// This method:
    /// 1. Atomically acquires a mutable "lock" via `compare_exchange`
    /// 2. Calls the destructor on the old value
    /// 3. Deallocates the old memory
    /// 4. Copies the new value's memory
    /// 5. Updates metadata (`type_id`, `type_name`, destructor, serialize/deserialize fns)
    /// 6. Updates the shared _`internal_ptr` so ALL clones see the new data
    /// 7. Releases the lock
    ///
    /// Since all clones of a `RefAny` share the same `RefCountInner`, this change
    /// will be visible to ALL clones of this `RefAny`.
    ///
    /// # Returns
    ///
    /// - `true` if the replacement was successful
    /// - `false` if there are active borrows (would cause UB)
    ///
    /// # Thread Safety
    ///
    /// Uses `compare_exchange` to atomically acquire exclusive access, preventing
    /// any race condition between checking for borrows and modifying the data.
    ///
    /// # Safety
    ///
    /// Safe because:
    /// - We atomically acquire exclusive access before modifying
    /// - The old destructor is called before deallocation
    /// - Memory is properly allocated with correct alignment
    /// - All metadata is updated while holding the lock
    ///
    /// # Panics
    ///
    /// Panics if a memory `Layout` for the replacement value cannot be
    /// constructed (its size overflows `isize::MAX`).
    #[allow(clippy::used_underscore_binding)] // `_`-prefixed fields are an intentional FFI/api.json naming convention; internal access is required
60
    pub fn replace_contents(&mut self, new_value: Self) -> bool {
        use core::ptr;
60
        let inner = self.sharing_info.ptr.cast_mut();
        // Atomically acquire exclusive access by setting num_mutable_refs to 1.
        // This uses compare_exchange to ensure no race condition:
        // - If num_mutable_refs is 0, set it to 1 (success)
        // - If num_mutable_refs is not 0, someone else has it (fail)
        // We also need to check num_refs == 0 atomically.
60
        let inner_ref = self.sharing_info.downcast();
        // First, try to acquire the mutable lock
60
        let mutable_lock_result = inner_ref.num_mutable_refs.compare_exchange(
            0,  // expected: no mutable refs
            1,  // desired: we take the mutable ref
60
            AtomicOrdering::SeqCst,
60
            AtomicOrdering::SeqCst,
        );
60
        if mutable_lock_result.is_err() {
            // Someone else has a mutable reference
1
            return false;
59
        }
        // Now check that there are no shared references
        // Note: We hold the mutable lock, so no new shared refs can be acquired
59
        if inner_ref.num_refs.load(AtomicOrdering::SeqCst) != 0 {
            // Release the lock and fail
1
            inner_ref.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
1
            return false;
58
        }
        // We now have exclusive access - perform the replacement
        // SAFETY: we hold the exclusive lock (num_mutable_refs==1, num_refs==0),
        // so no live `Ref`/`RefMut` aliases the data; `inner` is the live
        // `RefCountInner` from `Box::into_raw`. Old data is destructed+freed with
        // its own stored layout before the pointer is overwritten, and the new
        // data is freshly allocated and byte-copied.
        unsafe {
            // Get old layout info before we overwrite it
58
            let old_ptr = (*inner)._internal_ptr;
58
            let old_len = (*inner)._internal_len;
58
            let old_layout_size = (*inner)._internal_layout_size;
58
            let old_layout_align = (*inner)._internal_layout_align;
58
            let old_destructor = (*inner).custom_destructor;
            // Step 1: Call destructor on old value (if non-ZST)
58
            if old_len > 0 && !old_ptr.is_null() {
57
                old_destructor(old_ptr.cast_mut());
57
            }
            // Step 2: Deallocate old memory (if non-ZST). Use the *checked*
            // `Layout::from_size_align` (not `_unchecked`): the stored
            // size/align came from a valid `Layout`, so it always succeeds, and
            // this shrinks the unchecked surface inside this unsafe block.
58
            if old_layout_size > 0 && !old_ptr.is_null() {
57
                let old_layout = Layout::from_size_align(old_layout_size, old_layout_align)
57
                    .expect("replace_contents: stored old layout was invalid");
57
                alloc::alloc::dealloc(old_ptr as *mut u8, old_layout);
57
            }
            // Get new value's metadata
58
            let new_inner = new_value.sharing_info.downcast();
58
            let new_ptr = new_inner._internal_ptr;
58
            let new_len = new_inner._internal_len;
58
            let new_layout_size = new_inner._internal_layout_size;
58
            let new_layout_align = new_inner._internal_layout_align;
            // Step 3: Allocate new memory and copy data
58
            let allocated_ptr = if new_len == 0 {
1
                ptr::null_mut()
            } else {
57
                let new_layout = Layout::from_size_align(new_len, new_layout_align)
57
                    .expect("Failed to create layout");
57
                let heap_ptr = alloc::alloc::alloc(new_layout);
57
                if heap_ptr.is_null() {
                    alloc::alloc::handle_alloc_error(new_layout);
57
                }
                // Copy data from new_value
57
                ptr::copy_nonoverlapping(
57
                    new_ptr as *const u8,
57
                    heap_ptr,
57
                    new_len,
                );
57
                heap_ptr
            };
            // Step 4: Update the shared internal pointer in RefCountInner
            // All clones will see this new pointer!
58
            (*inner)._internal_ptr = allocated_ptr as *const c_void;
            // Step 5: Update metadata in RefCountInner
58
            (*inner)._internal_len = new_len;
58
            (*inner)._internal_layout_size = new_layout_size;
58
            (*inner)._internal_layout_align = new_layout_align;
58
            (*inner).type_id = new_inner.type_id;
58
            (*inner).type_name = new_inner.type_name.clone();
58
            (*inner).custom_destructor = new_inner.custom_destructor;
58
            (*inner).serialize_fn = new_inner.serialize_fn;
58
            (*inner).deserialize_fn = new_inner.deserialize_fn;
58
            (*inner).update_fn = new_inner.update_fn;
        }
        // Release the mutable lock
58
        self.sharing_info.downcast().num_mutable_refs.store(0, AtomicOrdering::SeqCst);
        // AUDIT: reclaim `new_value` instead of leaking it.
        //
        // The old code `mem::forget(new_value)` to stop `RefAny::drop` from
        // running the T-destructor a SECOND time on the bytes we just copied
        // into our own allocation — but that leaked `new_value`'s entire
        // `RefCountInner` box AND its heap data block on every single call.
        //
        // Instead, neutralize `new_value`'s destructor to a no-op and let the
        // normal refcount teardown run: it frees BOTH allocations (data block +
        // inner box) when this was the last reference, without re-running the
        // real T-destructor (which now lives on OUR inner, to run exactly once
        // when `self` is finally dropped). If `new_value` still had clones, the
        // no-op keeps them from double-dropping the shared T while their own
        // last drop still reclaims the shared block — no double free, no leak.
        #[allow(clippy::items_after_statements)]
58
        const extern "C" fn noop_destructor(_: *mut c_void) {}
58
        let new_inner = new_value.sharing_info.ptr.cast_mut();
58
        if !new_inner.is_null() {
            // SAFETY: `new_inner` came from `Box::into_raw` in `RefCount::new`
            // and is still alive (we hold `new_value`).
58
            unsafe {
58
                (*new_inner).custom_destructor = noop_destructor;
58
            }
        }
58
        drop(new_value);
58
        true
60
    }
}
impl Clone for RefAny {
    /// Creates a new `RefAny` sharing the same heap-allocated data.
    ///
    /// This is cheap (just increments a counter) and is how multiple parts
    /// of the code can hold references to the same data.
    ///
    /// # Reference Counting
    ///
    /// Atomically increments `num_copies` with `SeqCst` ordering before
    /// creating the clone. This ensures all threads see the updated count
    /// before the clone can be used.
    ///
    /// # Instance ID
    ///
    /// Each clone gets a unique `instance_id` based on the current copy count.
    /// The original has `instance_id=0`, the first clone gets `1`, etc.
    ///
    /// # Memory Ordering
    ///
    /// The `fetch_add` followed by `load` both use `SeqCst`:
    /// - `fetch_add`: Ensures the increment is visible to all threads
    /// - `load`: Gets the updated value for the `instance_id`
    ///
    /// This prevents race conditions where two threads clone simultaneously
    /// and both see the same `instance_id`.
    ///
    /// # Safety
    ///
    /// Safe because:
    ///
    /// - Atomic operations prevent data races
    /// - The heap allocation remains valid (only freed when count reaches 0)
    /// - `run_destructor` is set to `true` for all clones
645147
    fn clone(&self) -> Self {
        // Atomically increment the reference count
645147
        let inner = self.sharing_info.downcast();
645147
        let prev = inner.num_copies.fetch_add(1, AtomicOrdering::SeqCst);
645147
        let new_instance_id = (prev + 1) as u64;
645147
        Self {
645147
            // Data pointer is now in RefCountInner, shared automatically
645147
            sharing_info: RefCount {
645147
                ptr: self.sharing_info.ptr, // Share the same metadata (and data pointer)
645147
                run_destructor: true,       // This clone should decrement num_copies on drop
645147
            },
645147
            // Give this clone a unique ID based on the updated count
645147
            instance_id: new_instance_id,
645147
        }
645147
    }
}
impl Drop for RefAny {
    /// Empty drop implementation - all cleanup is handled by `RefCount::drop`.
    ///
    /// When a `RefAny` is dropped, its `sharing_info: RefCount` field is automatically
    /// dropped by Rust. The `RefCount::drop` implementation handles all cleanup:
    ///
    /// 1. Atomically decrements `num_copies` with `fetch_sub`
    /// 2. If the previous value was 1 (we're the last reference):
    ///    - Reclaims the `RefCountInner` via `Box::from_raw`
    ///    - Calls the custom destructor to run `T::drop()`
    ///    - Deallocates the heap memory with the stored layout
    ///
    /// # Why No Code Here?
    ///
    /// Previously, `RefAny::drop` handled cleanup, but this caused issues with the
    /// C API where `Ref<T>` and `RefMut<T>` guards (which clone the `RefCount`) need
    /// to keep the data alive even after the original `RefAny` is dropped.
    ///
    /// By moving all cleanup to `RefCount::drop`, we ensure that:
    /// - `RefAny::clone()` creates a `RefCount` with `run_destructor = true`
    /// - `AZ_REFLECT` macros create `Ref`/`RefMut` guards that clone `RefCount`
    /// - Each `RefCount` drop decrements the counter
    /// - Only the LAST drop (when `num_copies` was 1) cleans up memory
    ///
    /// See `RefCount::drop` for the full algorithm and safety documentation.
908063
    fn drop(&mut self) {
        // RefCount::drop handles everything automatically.
        // The sharing_info field is dropped by Rust, triggering RefCount::drop.
908063
    }
}
#[cfg(test)]
#[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
mod audit_tests {
    use super::*;
    use core::sync::atomic::{AtomicUsize, Ordering};
    static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
    // The tests below share the single `DROP_COUNT` static: each resets it to 0
    // and then asserts an exact drop count. Under the default multi-threaded
    // test runner they would otherwise interleave and corrupt each other's
    // counts (a real, if test-only, isolation bug). Every `DROP_COUNT`-using
    // test takes this lock first to serialize; it is poison-tolerant so one
    // failing test does not cascade `.unwrap()` panics into the rest.
    static DROP_COUNT_SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
5
    fn serialize_drop_count() -> std::sync::MutexGuard<'static, ()> {
5
        DROP_COUNT_SERIAL
5
            .lock()
5
            .unwrap_or_else(std::sync::PoisonError::into_inner)
5
    }
    struct DropCounter(#[allow(dead_code)] u32);
    impl Drop for DropCounter {
6
        fn drop(&mut self) {
6
            DROP_COUNT.fetch_add(1, Ordering::SeqCst);
6
        }
    }
    // AUDIT: exclusive borrow must be denied while a shared borrow is live and
    // vice-versa (runtime borrow checker), and must be recoverable after the
    // guard drops. Exercises the atomic acquire/release added to downcast_*.
    #[test]
1
    fn borrow_exclusion_and_recovery() {
        // The runtime borrow guard lives in the *shared* refcount inner, so it
        // is only observable across two clones (a single `RefAny` can't hold two
        // guards at once — the methods take `&mut self`). `b` shares `a`'s inner.
1
        let mut a = RefAny::new(7i32);
1
        let mut b = a.clone();
        {
1
            let r = a.downcast_ref::<i32>().unwrap();
1
            assert_eq!(*r, 7);
            // shared borrow live -> no mutable borrow via the shared inner
1
            assert!(b.downcast_mut::<i32>().is_none());
            // another shared borrow is fine
1
            assert!(b.downcast_ref::<i32>().is_some());
        }
        {
1
            let mut m = a.downcast_mut::<i32>().unwrap();
1
            *m = 42;
            // mutable borrow live -> no shared borrow via the shared inner
1
            assert!(b.downcast_ref::<i32>().is_none());
        }
1
        assert_eq!(*a.downcast_ref::<i32>().unwrap(), 42);
1
    }
    // AUDIT: wrong-type downcast must be rejected. Same type -> same id.
    #[test]
1
    fn type_id_guard() {
1
        let mut a = RefAny::new(1u64);
1
        assert!(a.downcast_ref::<i32>().is_none());
1
        assert!(a.downcast_ref::<u64>().is_some());
1
        assert_eq!(
1
            RefAny::get_type_id_static::<u64>(),
1
            RefAny::get_type_id_static::<u64>()
        );
1
        assert_ne!(
1
            RefAny::get_type_id_static::<u64>(),
1
            RefAny::get_type_id_static::<i64>()
        );
1
    }
    // AUDIT: replace_contents must run each stored value's destructor exactly
    // once (old value on replace, new value on final drop) and must not leak.
    #[test]
1
    fn replace_contents_drops_exactly_once() {
1
        let _serial = serialize_drop_count();
1
        DROP_COUNT.store(0, Ordering::SeqCst);
        {
1
            let mut a = RefAny::new(DropCounter(1));
1
            let b = RefAny::new(DropCounter(2));
1
            assert!(a.replace_contents(b));
            // The original `a` value was dropped during replacement.
1
            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
            // `a` now holds the (copied) `b` value; dropped at end of scope.
        }
        // Two DropCounter values were constructed; both must be dropped once.
1
        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 2);
1
    }
    // AUDIT: replace_contents must fail (return false) while a borrow is live.
    #[test]
1
    fn replace_contents_denied_while_borrowed() {
1
        let mut a = RefAny::new(1i32);
        // Clone first: `r` will exclusively borrow `a`, so the sibling clone
        // must exist beforehand. Both share the same inner RefCountInner.
1
        let mut a2 = a.clone();
1
        let r = a.downcast_ref::<i32>().unwrap();
        // A live shared borrow (num_refs != 0) on the shared inner must block
        // replace_contents via the sibling clone.
1
        assert!(!a2.replace_contents(RefAny::new(2i32)));
1
        drop(r);
1
        assert!(a2.replace_contents(RefAny::new(2i32)));
1
    }
    // ---- Miri-focused unit tests -------------------------------------------
    // These exercise the pure-Rust memory behavior of each unsafe path so Miri
    // can detect UB (bad provenance, misalignment, use-after-free, leaks,
    // refcount corruption). No FFI, no threads, no OS calls; tiny allocations.
    // MIRI: covers RefAny::new + new_c alloc/copy_nonoverlapping + downcast_ref
    // (&*(ptr as *const U)) + the final Drop path (Box::from_raw + dealloc +
    // custom destructor). A non-Copy heap type checks the destructor runs.
    #[test]
1
    fn miri_new_downcast_drop_roundtrip() {
1
        let _serial = serialize_drop_count();
1
        DROP_COUNT.store(0, Ordering::SeqCst);
        {
1
            let mut a = RefAny::new(DropCounter(9));
            // downcast_ref exercises the type-id guard + aligned pointer cast.
1
            assert!(a.downcast_ref::<DropCounter>().is_some());
1
            assert!(a.downcast_ref::<u8>().is_none());
        }
1
        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
1
    }
    // MIRI: alignment correctness of new_c's Layout::from_size_align path. An
    // over-aligned payload downcast to a misaligned pointer would be UB.
    #[test]
1
    fn miri_alignment_preserved() {
        #[repr(align(16))]
        #[derive(Debug)]
        struct Over(u64);
1
        let mut a = RefAny::new(Over(0xABCD));
1
        let r = a.downcast_ref::<Over>().unwrap();
1
        assert_eq!(r.0, 0xABCD);
1
        assert_eq!((&raw const *r) as usize % 16, 0);
1
    }
    // MIRI: clone shares one RefCountInner; num_copies increments on clone and
    // decrements on drop (RefCount::clone / RefCount::drop fetch paths). Data
    // must survive while any clone lives and be freed exactly once at the end.
    #[test]
1
    fn miri_clone_refcount_increment_decrement() {
1
        let _serial = serialize_drop_count();
1
        DROP_COUNT.store(0, Ordering::SeqCst);
        {
1
            let a = RefAny::new(DropCounter(1));
1
            assert_eq!(a.get_ref_count(), 1);
1
            let b = a.clone();
1
            assert_eq!(a.get_ref_count(), 2);
1
            assert_eq!(b.get_ref_count(), 2);
            {
1
                let c = b.clone();
1
                assert_eq!(c.get_ref_count(), 3);
            }
            // c dropped -> back to 2, nothing freed yet.
1
            assert_eq!(a.get_ref_count(), 2);
1
            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
        }
        // all clones dropped -> data destructed exactly once.
1
        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
1
    }
    // MIRI: downcast_mut hands out &mut *(ptr as *mut U); mutation must be
    // visible through a shared clone (shared RefCountInner data pointer).
    #[test]
1
    fn miri_downcast_mut_mutation_visible_across_clones() {
1
        let mut a = RefAny::new(10u32);
1
        let mut b = a.clone();
1
        {
1
            let mut m = a.downcast_mut::<u32>().unwrap();
1
            *m += 5;
1
        }
1
        assert_eq!(*b.downcast_ref::<u32>().unwrap(), 15);
1
    }
    // MIRI: the runtime borrow refcount on the shared inner. Exercises
    // increase_ref/decrease_ref/increase_refmut/decrease_refmut and the
    // can_be_shared / can_be_shared_mut predicates directly, plus the
    // checked_sub underflow guard (decrement at zero must saturate, not wrap).
    #[test]
1
    fn miri_borrow_counter_transitions_and_underflow_guard() {
1
        let a = RefAny::new(0i32);
1
        let rc = &a.sharing_info;
1
        assert!(rc.can_be_shared());
1
        assert!(rc.can_be_shared_mut());
1
        rc.increase_ref();
1
        assert!(rc.can_be_shared()); // shared borrows coexist
1
        assert!(!rc.can_be_shared_mut()); // but block a mutable borrow
1
        rc.decrease_ref();
1
        assert!(rc.can_be_shared_mut());
1
        rc.increase_refmut();
1
        assert!(!rc.can_be_shared()); // mutable borrow blocks shared
1
        assert!(!rc.can_be_shared_mut());
1
        rc.decrease_refmut();
1
        assert!(rc.can_be_shared_mut());
        // Underflow guard: extra decrements must saturate at 0, never wrap to
        // usize::MAX (which would permanently break the borrow checker).
1
        rc.decrease_ref();
1
        rc.decrease_refmut();
1
        assert!(rc.can_be_shared());
1
        assert!(rc.can_be_shared_mut());
1
    }
    // MIRI: get_type_id_static reads TypeId via from_raw_parts and folds ALL
    // bytes. Same type -> same id (stable within a run); distinct types differ.
    #[test]
1
    fn miri_type_id_static_stable_and_distinct() {
1
        assert_eq!(
1
            RefAny::get_type_id_static::<(u8, u64)>(),
1
            RefAny::get_type_id_static::<(u8, u64)>()
        );
1
        assert_ne!(
1
            RefAny::get_type_id_static::<u32>(),
1
            RefAny::get_type_id_static::<[u32; 2]>()
        );
1
    }
    // MIRI: ZST payload uses a null data pointer but must still construct, clone,
    // run its destructor exactly once, and downcast (a ZST reference reads no
    // bytes, so a dangling-but-aligned pointer is a valid reference).
    #[test]
1
    fn miri_zst_roundtrip_and_destructor() {
1
        let _serial = serialize_drop_count();
1
        DROP_COUNT.store(0, Ordering::SeqCst);
        struct ZstDrop;
        impl Drop for ZstDrop {
1
            fn drop(&mut self) {
1
                DROP_COUNT.fetch_add(1, Ordering::SeqCst);
1
            }
        }
        {
1
            let mut a = RefAny::new(ZstDrop);
1
            assert_eq!(a.get_data_len(), 0);
            // downcast_ref succeeds for a ZST (dangling ref, no bytes read); the
            // returned guard drops here without running the value's destructor.
1
            assert!(a.downcast_ref::<ZstDrop>().is_some());
1
            let _b = a.clone();
        }
1
        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
1
    }
    // MIRI: replace_contents alloc/dealloc/copy path plus the neutralized
    // new_value destructor. Old value destructed once, new value destructed
    // once at final drop, with no leak/double-free of either heap block.
    #[test]
1
    fn miri_replace_contents_alloc_paths() {
1
        let _serial = serialize_drop_count();
1
        DROP_COUNT.store(0, Ordering::SeqCst);
        {
1
            let mut a = RefAny::new(DropCounter(1));
1
            assert!(a.replace_contents(RefAny::new(DropCounter(2))));
1
            assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1); // old value gone
1
            assert_eq!(a.downcast_ref::<DropCounter>().unwrap().0, 2u32);
        }
1
        assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 2);
1
    }
    // MIRI: replacing across differing sizes/alignments (u8 -> u64) reallocates
    // correctly and keeps the shared pointer aligned for the new type.
    #[test]
1
    fn miri_replace_contents_changes_layout() {
1
        let mut a = RefAny::new(7u8);
1
        assert!(a.replace_contents(RefAny::new(0x1122_3344_5566_7788u64)));
        {
            // downcast_ref takes &mut self, so scope the guard before the next call.
1
            let r = a.downcast_ref::<u64>().unwrap();
1
            assert_eq!(*r, 0x1122_3344_5566_7788u64);
1
            assert_eq!((&raw const *r) as usize % core::mem::align_of::<u64>(), 0);
        }
        // old u8 type must no longer downcast.
1
        assert!(a.downcast_ref::<u8>().is_none());
1
    }
    // MIRI: RefCount clone/drop in isolation keeps the inner alive until the
    // last handle drops (Box::into_raw / Box::from_raw balance).
    #[test]
1
    fn miri_refcount_clone_keeps_inner_alive() {
1
        let a = RefAny::new(5usize);
1
        let rc0 = a.sharing_info.clone(); // +1 copy
1
        let rc1 = rc0.clone(); // +1 copy
1
        assert_eq!(a.get_ref_count(), 3);
1
        drop(rc1);
1
        drop(rc0);
1
        assert_eq!(a.get_ref_count(), 1);
        // `a` still usable -> inner not freed.
1
        assert_eq!(*a.clone().downcast_ref::<usize>().unwrap(), 5);
1
    }
}
#[cfg(test)]
#[allow(
    clippy::items_after_statements,
    clippy::redundant_clone,
    clippy::needless_pass_by_value,
    clippy::needless_range_loop,
    clippy::cast_possible_truncation,
    clippy::cast_sign_loss,
    clippy::cast_lossless,
    clippy::float_cmp,
    clippy::unreadable_literal,
    clippy::unusual_byte_groupings,
    clippy::many_single_char_names,
    clippy::used_underscore_binding,
    clippy::borrow_as_ptr,
    clippy::cast_ptr_alignment,
    clippy::fn_to_numeric_cast_any,
    trivial_casts,
    unused_qualifications,
    unreachable_pub,
    private_interfaces,
    missing_debug_implementations,
    missing_copy_implementations
)] // pedantic lints are noise in unsafe-exercising test code
mod autotest_generated {
    use alloc::{string::String, vec::Vec};
    use core::{
        ffi::c_void,
        sync::atomic::{AtomicUsize, Ordering},
    };
    use super::*;
    /// Destructor for payloads that need no drop glue (`Copy` types built via
    /// the raw C-ABI `new_c` path).
    extern "C" fn noop_destructor(_: *mut c_void) {}
    /// Store `value` in a `RefAny` and read it back out: the byte-copy into the
    /// heap allocation and the type-checked pointer cast must be lossless.
    fn round_trip<T: 'static + Clone + PartialEq + core::fmt::Debug>(value: T) {
        let mut a = RefAny::new(value.clone());
        let r = a
            .downcast_ref::<T>()
            .expect("downcast to the stored type must succeed");
        assert_eq!(*r, value);
    }
    // ---- RefAny::new_c — raw C-ABI constructor, malformed/boundary inputs ----
    // A NULL pointer with a non-zero length is the classic FFI mistake: copying
    // from it would be UB, so `new_c` must panic instead of reading it.
    #[test]
    #[should_panic(expected = "NULL pointer passed for non-ZST type")]
    fn new_c_null_ptr_with_nonzero_len_panics() {
        drop(RefAny::new_c(
            core::ptr::null(),
            4,
            4,
            RefAny::get_type_id_static::<u32>(),
            AzString::from_const_str("autotest::NullPtr"),
            noop_destructor,
            0,
            0,
        ));
    }
    // A non-power-of-two alignment cannot form a valid `Layout`; it must panic
    // before allocating rather than allocate with a bogus layout (which would
    // make the matching `dealloc` in `drop` UB).
    #[test]
    #[should_panic(expected = "Failed to create layout")]
    fn new_c_non_power_of_two_align_panics() {
        let value: u32 = 7;
        drop(RefAny::new_c(
            (&raw const value).cast::<c_void>(),
            4,
            3, // not a power of two
            RefAny::get_type_id_static::<u32>(),
            AzString::from_const_str("autotest::BadAlign"),
            noop_destructor,
            0,
            0,
        ));
    }
    // `usize::MAX` bytes overflows `isize::MAX` and cannot be a `Layout`: the
    // checked constructor must reject it (no silent overflow into a tiny alloc).
    #[test]
    #[should_panic(expected = "Failed to create layout")]
    fn new_c_huge_len_panics_instead_of_overflowing() {
        let value: u8 = 1;
        drop(RefAny::new_c(
            (&raw const value).cast::<c_void>(),
            usize::MAX,
            1,
            RefAny::get_type_id_static::<u8>(),
            AzString::from_const_str("autotest::HugeLen"),
            noop_destructor,
            0,
            0,
        ));
    }
    // len == 0 is the ZST path: NULL data pointer is legal, `align` is ignored
    // (even a nonsensical 0), nothing is allocated, and a ZST still downcasts
    // (via a dangling-but-aligned reference — there are no bytes to read).
    #[test]
    fn new_c_zero_len_null_ptr_is_a_clean_zst() {
        let mut a = RefAny::new_c(
            core::ptr::null(),
            0,
            0, // invalid alignment, but unused on the ZST path
            RefAny::get_type_id_static::<()>(),
            AzString::from_const_str("autotest::Zst"),
            noop_destructor,
            0,
            0,
        );
        assert_eq!(a.get_data_len(), 0);
        assert!(a.get_data_ptr().is_null());
        assert!(a.is_type(RefAny::get_type_id_static::<()>()));
        // Type matches and a `&()`/`&mut ()` needs no backing bytes, so the
        // downcast succeeds; each temporary guard releases its borrow slot when it
        // drops at the end of its statement.
        assert!(a.downcast_ref::<()>().is_some());
        assert!(a.downcast_mut::<()>().is_some());
        assert!(a.sharing_info.can_be_shared_mut());
    }
    // Round-trip through the raw C-ABI constructor: what `new_c` encodes,
    // `downcast_ref` must decode bit-for-bit.
    #[test]
    fn new_c_round_trip_matches_rust_constructor() {
        let value: u64 = 0xDEAD_BEEF_CAFE_BABE;
        let mut a = RefAny::new_c(
            (&raw const value).cast::<c_void>(),
            core::mem::size_of::<u64>(),
            core::mem::align_of::<u64>(),
            RefAny::get_type_id_static::<u64>(),
            AzString::from_const_str("u64"),
            noop_destructor,
            7,
            9,
        );
        assert_eq!(a.get_data_len(), core::mem::size_of::<u64>());
        assert_eq!(a.get_ref_count(), 1);
        assert_eq!(a.get_serialize_fn(), 7);
        assert_eq!(a.get_deserialize_fn(), 9);
        assert!(a.can_serialize());
        assert!(a.can_deserialize());
        assert_eq!(*a.downcast_ref::<u64>().unwrap(), value);
    }
    // The runtime guard is the type ID, nothing else: a matching size, name and
    // destructor must NOT be enough to downcast if the ID differs by one bit.
    #[test]
    fn new_c_wrong_type_id_rejects_downcast() {
        let value: u64 = 0x0102_0304_0506_0708;
        let real_id = RefAny::get_type_id_static::<u64>();
        let mut a = RefAny::new_c(
            (&raw const value).cast::<c_void>(),
            core::mem::size_of::<u64>(),
            core::mem::align_of::<u64>(),
            real_id ^ 1, // one bit off
            AzString::from_const_str("u64"),
            noop_destructor,
            0,
            0,
        );
        assert!(!a.is_type(real_id));
        assert!(a.downcast_ref::<u64>().is_none());
        assert!(a.downcast_mut::<u64>().is_none());
        // The rejected downcasts must not have left a borrow behind.
        assert!(a.sharing_info.can_be_shared_mut());
    }
    // Over-alignment (align > len) is a valid `Layout`; the payload must land on
    // an address that satisfies the requested alignment.
    #[test]
    fn new_c_over_aligned_small_payload() {
        let value: u8 = 0x5A;
        let mut a = RefAny::new_c(
            (&raw const value).cast::<c_void>(),
            1,
            16,
            RefAny::get_type_id_static::<u8>(),
            AzString::from_const_str("u8"),
            noop_destructor,
            0,
            0,
        );
        assert_eq!(a.get_data_ptr() as usize % 16, 0);
        assert_eq!(*a.downcast_ref::<u8>().unwrap(), 0x5A);
    }
    // The type name is arbitrary caller-supplied UTF-8 (generated by foreign
    // codegen): empty, unicode, RTL overrides and embedded NULs must survive.
    #[test]
    fn new_c_preserves_unicode_and_empty_type_names() {
        let value: u32 = 0;
        let weird = "app::💥Ünïcødé<T>\u{202E}rtl\u{0}nul";
        let a = RefAny::new_c(
            (&raw const value).cast::<c_void>(),
            4,
            4,
            1,
            AzString::from(String::from(weird)),
            noop_destructor,
            0,
            0,
        );
        assert_eq!(a.get_type_name().as_str(), weird);
        let b = RefAny::new_c(
            (&raw const value).cast::<c_void>(),
            4,
            4,
            2,
            AzString::from_const_str(""),
            noop_destructor,
            0,
            0,
        );
        assert_eq!(b.get_type_name().as_str(), "");
    }
    // ---- RefAny::new — post-construction invariants ----
    #[test]
    fn new_invariants_hold() {
        let mut a = RefAny::new(0x1122_3344u32);
        assert_eq!(a.get_data_len(), core::mem::size_of::<u32>());
        assert!(!a.get_data_ptr().is_null());
        assert_eq!(a.get_data_ptr() as usize % core::mem::align_of::<u32>(), 0);
        assert_eq!(a.get_type_id(), RefAny::get_type_id_static::<u32>());
        assert!(a.is_type(RefAny::get_type_id_static::<u32>()));
        assert_eq!(a.get_type_name().as_str(), "u32");
        assert_eq!(a.get_ref_count(), 1);
        assert!(a.has_no_copies());
        assert_eq!(a.get_serialize_fn(), 0);
        assert_eq!(a.get_deserialize_fn(), 0);
        assert_eq!(a.get_update_fn(), 0);
        assert!(!a.can_serialize());
        assert!(!a.can_deserialize());
        assert!(a.sharing_info.can_be_shared());
        assert!(a.sharing_info.can_be_shared_mut());
        assert_eq!(a.instance_id, 0);
        assert_eq!(*a.downcast_ref::<u32>().unwrap(), 0x1122_3344);
    }
    // A zero-length array of an 8-aligned element is still a ZST: `new` must take
    // the null-pointer path (no zero-size allocation, which would be UB).
    #[test]
    fn new_zero_sized_array_of_aligned_type_is_a_zst() {
        let mut a = RefAny::new([0u64; 0]);
        assert_eq!(a.get_data_len(), 0);
        assert!(a.get_data_ptr().is_null());
        assert_eq!(
            a.sharing_info.debug_get_refcount_copied()._internal_layout_size,
            0
        );
        assert!(a.downcast_ref::<[u64; 0]>().is_some());
        assert_eq!(a.get_ref_count(), 1);
    }
    // Large + heavily over-aligned payload: the alignment recorded at
    // construction must be honoured by the allocation, or every downcast would
    // hand out a misaligned reference.
    #[test]
    fn new_large_over_aligned_payload_round_trips() {
        #[repr(align(64))]
        #[derive(Clone)]
        struct Big([u8; 4096]);
        let mut a = RefAny::new(Big([0xAB; 4096]));
        assert_eq!(a.get_data_len(), 4096);
        assert_eq!(a.get_data_ptr() as usize % 64, 0);
        let r = a.downcast_ref::<Big>().unwrap();
        assert_eq!((&raw const *r) as usize % 64, 0);
        assert!(r.0.iter().all(|&b| b == 0xAB));
    }
    // ---- numeric limits / round-trip ----
    #[test]
    fn integer_limits_round_trip() {
        round_trip(u8::MIN);
        round_trip(u8::MAX);
        round_trip(i8::MIN);
        round_trip(i8::MAX);
        round_trip(u16::MAX);
        round_trip(i16::MIN);
        round_trip(u32::MAX);
        round_trip(i32::MIN);
        round_trip(u64::MAX);
        round_trip(i64::MIN);
        // u128/i128 are 16-aligned on most targets -> exercises the align path
        round_trip(u128::MAX);
        round_trip(i128::MIN);
        round_trip(i128::MAX);
        round_trip(usize::MAX);
        round_trip(isize::MIN);
        round_trip(0usize);
    }
    // Floats are copied as raw bytes, so every bit pattern (NaN payloads, signed
    // zero, infinities) must survive unchanged — no normalization, no rounding.
    #[test]
    fn float_extremes_round_trip_bit_exact() {
        let mut nan = RefAny::new(f64::NAN);
        assert!(nan.downcast_ref::<f64>().unwrap().is_nan());
        // A NaN with a non-canonical payload must come back bit-identical.
        let bits = 0x7FF0_0000_0000_0001u64;
        let mut payload_nan = RefAny::new(f64::from_bits(bits));
        assert_eq!(payload_nan.downcast_ref::<f64>().unwrap().to_bits(), bits);
        let mut neg_zero = RefAny::new(-0.0f64);
        let nz = neg_zero.downcast_ref::<f64>().unwrap();
        assert!(*nz == 0.0 && nz.is_sign_negative());
        drop(nz);
        let mut inf = RefAny::new(f32::NEG_INFINITY);
        assert_eq!(*inf.downcast_ref::<f32>().unwrap(), f32::NEG_INFINITY);
        // f32 and f64 are distinct types even though both are "floats".
        assert!(inf.downcast_ref::<f64>().is_none());
        round_trip(f64::MIN);
        round_trip(f64::MAX);
        round_trip(f64::MIN_POSITIVE);
        round_trip(f32::EPSILON);
        round_trip(f32::MAX);
    }
    // Owned heap payloads: the value is moved in (`mem::forget` on the original)
    // and dropped exactly once at the end — a double-drop here would be a
    // double-free of the String/Vec buffers.
    #[test]
    fn owned_unicode_payloads_round_trip() {
        round_trip(String::new());
        round_trip(String::from("héllo 🌍 \u{202E}rtl\u{0}nul"));
        round_trip('🌍');
        let v: Vec<String> = vec![String::from("a"), String::from("🎉"), String::new()];
        round_trip(v);
    }
    // A struct with interior padding is byte-copied, padding included: the copy
    // must not disturb the initialized fields.
    #[test]
    fn padded_struct_round_trips() {
        #[derive(Clone, PartialEq, Debug)]
        #[repr(C)]
        struct Padded {
            a: u8,
            b: u64,
            c: u8,
        }
        round_trip(Padded {
            a: 0xFF,
            b: u64::MAX,
            c: 0x01,
        });
    }
    // ---- setters: 0 / 1 / usize::MAX (never dereferenced by azul-core) ----
    #[test]
    fn set_serialize_fn_zero_and_extremes() {
        let mut a = RefAny::new(1u32);
        assert_eq!(a.get_serialize_fn(), 0);
        assert!(!a.can_serialize());
        a.set_serialize_fn(usize::MAX);
        assert_eq!(a.get_serialize_fn(), usize::MAX);
        assert!(a.can_serialize());
        a.set_serialize_fn(1);
        assert_eq!(a.get_serialize_fn(), 1);
        assert!(a.can_serialize());
        a.set_serialize_fn(0);
        assert_eq!(a.get_serialize_fn(), 0);
        assert!(!a.can_serialize());
        // The fn pointer lives in the SHARED inner, so a clone's setter is
        // visible through the original.
        let mut b = a.clone();
        b.set_serialize_fn(42);
        assert_eq!(a.get_serialize_fn(), 42);
        assert!(a.can_serialize());
        b.set_serialize_fn(0);
        assert!(!a.can_serialize());
    }
    #[test]
    fn set_deserialize_fn_zero_and_extremes() {
        let mut a = RefAny::new(1u32);
        assert_eq!(a.get_deserialize_fn(), 0);
        assert!(!a.can_deserialize());
        a.set_deserialize_fn(usize::MAX);
        assert_eq!(a.get_deserialize_fn(), usize::MAX);
        assert!(a.can_deserialize());
        a.set_deserialize_fn(1);
        assert_eq!(a.get_deserialize_fn(), 1);
        a.set_deserialize_fn(0);
        assert_eq!(a.get_deserialize_fn(), 0);
        assert!(!a.can_deserialize());
        let mut b = a.clone();
        b.set_deserialize_fn(42);
        assert_eq!(a.get_deserialize_fn(), 42);
        b.set_deserialize_fn(0);
        assert!(!a.can_deserialize());
    }
    // `set_update_fn` only *stores* the address; a bogus value must round-trip
    // and must be resettable to 0. (Deliberately no `downcast_mut` while the
    // observer is bogus — `downcast_mut` transmutes and CALLS it.)
    #[test]
    fn set_update_fn_zero_and_extremes() {
        let mut a = RefAny::new(1u32);
        assert_eq!(a.get_update_fn(), 0);
        a.set_update_fn(usize::MAX);
        assert_eq!(a.get_update_fn(), usize::MAX);
        a.set_update_fn(0);
        assert_eq!(a.get_update_fn(), 0);
        // With the observer unset again, mutable borrows work as normal.
        assert!(a.downcast_mut::<u32>().is_some());
    }
    // The registered observer must fire exactly once per *successful*
    // `downcast_mut`, and must see the PRE-mutation bytes + the payload length.
    static UPDATE_CALLS: AtomicUsize = AtomicUsize::new(0);
    static UPDATE_LEN: AtomicUsize = AtomicUsize::new(0);
    static UPDATE_PRE_VALUE: AtomicUsize = AtomicUsize::new(0);
    extern "C" fn record_update(ptr: *const c_void, len: usize) {
        UPDATE_CALLS.fetch_add(1, Ordering::SeqCst);
        UPDATE_LEN.store(len, Ordering::SeqCst);
        if !ptr.is_null() && len == core::mem::size_of::<u32>() {
            // SAFETY: only installed on a `RefAny` holding a `u32`, and
            // `downcast_mut` fires it with that live payload pointer.
            let pre = unsafe { core::ptr::read_unaligned(ptr.cast::<u32>()) };
            UPDATE_PRE_VALUE.store(pre as usize, Ordering::SeqCst);
        }
    }
    #[test]
    fn update_fn_fires_once_with_pre_mutation_data() {
        UPDATE_CALLS.store(0, Ordering::SeqCst);
        let mut a = RefAny::new(7u32);
        let cb: extern "C" fn(*const c_void, usize) = record_update;
        a.set_update_fn(cb as usize);
        assert_eq!(a.get_update_fn(), cb as usize);
        {
            let mut m = a.downcast_mut::<u32>().unwrap();
            *m = 9;
        }
        assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
        assert_eq!(UPDATE_LEN.load(Ordering::SeqCst), 4);
        // The observer saw 7, not 9: it runs BEFORE the borrow is handed out.
        assert_eq!(UPDATE_PRE_VALUE.load(Ordering::SeqCst), 7);
        // A wrong-type downcast must not fire it.
        assert!(a.downcast_mut::<u64>().is_none());
        assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
        // A shared borrow is not a mutation -> must not fire it.
        assert_eq!(*a.downcast_ref::<u32>().unwrap(), 9);
        assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
        // A *denied* mutable borrow (shared borrow live on a sibling clone)
        // must not fire it either.
        let mut b = a.clone();
        let r = a.downcast_ref::<u32>().unwrap();
        assert!(b.downcast_mut::<u32>().is_none());
        assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
        drop(r);
        // Unregistering stops the observer.
        b.set_update_fn(0);
        assert!(b.downcast_mut::<u32>().is_some());
        assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
    }
    // ---- predicates ----
    #[test]
    fn is_type_true_false_and_extremes() {
        let a = RefAny::new(0u32);
        let id = a.get_type_id();
        assert!(a.is_type(id));
        assert!(!a.is_type(!id)); // every bit flipped -> always a different id
        assert!(!a.is_type(id.wrapping_add(1)));
        assert!(!a.is_type(RefAny::get_type_id_static::<i32>()));
        if id != 0 {
            assert!(!a.is_type(0));
        }
        if id != u64::MAX {
            assert!(!a.is_type(u64::MAX));
        }
    }
    #[test]
    fn has_no_copies_transitions() {
        let mut a = RefAny::new(1u32);
        assert!(a.has_no_copies());
        {
            let b = a.clone();
            assert!(!a.has_no_copies()); // num_copies == 2
            assert!(!b.has_no_copies());
        }
        assert!(a.has_no_copies()); // clone dropped -> exclusive again
        {
            // A live shared borrow (taken via a sibling clone) also disqualifies.
            let mut c = a.clone();
            let r = c.downcast_ref::<u32>().unwrap();
            assert_eq!(*r, 1);
            assert!(!a.has_no_copies());
        }
        assert!(a.has_no_copies());
        {
            let mut c = a.clone();
            let m = c.downcast_mut::<u32>().unwrap();
            assert_eq!(*m, 1);
            assert!(!a.has_no_copies());
        }
        assert!(a.has_no_copies());
    }
    #[test]
    fn can_serialize_and_can_deserialize_track_the_fn_pointers() {
        let mut a = RefAny::new(1u32);
        assert!(!a.can_serialize());
        assert!(!a.can_deserialize());
        a.set_serialize_fn(1);
        assert!(a.can_serialize());
        assert!(!a.can_deserialize());
        a.set_deserialize_fn(usize::MAX);
        assert!(a.can_serialize());
        assert!(a.can_deserialize());
        a.set_serialize_fn(0);
        a.set_deserialize_fn(0);
        assert!(!a.can_serialize());
        assert!(!a.can_deserialize());
    }
    // ---- getters ----
    #[test]
    fn get_ref_count_tracks_clones_and_borrow_guards() {
        let mut a = RefAny::new(5u8);
        assert_eq!(a.get_ref_count(), 1);
        let mut b = a.clone();
        assert_eq!(a.get_ref_count(), 2);
        assert_eq!(b.get_ref_count(), 2);
        {
            // The guard clones the RefCount, so it keeps the data alive.
            let r = b.downcast_ref::<u8>().unwrap();
            assert_eq!(*r, 5);
            assert_eq!(a.get_ref_count(), 3);
        }
        assert_eq!(a.get_ref_count(), 2);
        {
            let m = b.downcast_mut::<u8>().unwrap();
            assert_eq!(*m, 5);
            assert_eq!(a.get_ref_count(), 3);
        }
        assert_eq!(a.get_ref_count(), 2);
        drop(b);
        assert_eq!(a.get_ref_count(), 1);
        assert_eq!(*a.downcast_ref::<u8>().unwrap(), 5);
    }
    #[test]
    fn debug_snapshot_matches_the_live_counters() {
        let a = RefAny::new(0x1122_3344u32);
        let d = a.sharing_info.debug_get_refcount_copied();
        assert_eq!(d.num_copies, 1);
        assert_eq!(d.num_refs, 0);
        assert_eq!(d.num_mutable_refs, 0);
        assert_eq!(d._internal_len, 4);
        assert_eq!(d._internal_layout_size, 4);
        assert_eq!(d._internal_layout_align, core::mem::align_of::<u32>());
        assert_eq!(d.type_id, RefAny::get_type_id_static::<u32>());
        assert_eq!(d.type_name.as_str(), "u32");
        assert_ne!(d.custom_destructor, 0);
        assert_eq!(d.serialize_fn, 0);
        assert_eq!(d.deserialize_fn, 0);
        a.sharing_info.increase_ref();
        a.sharing_info.increase_refmut();
        let d2 = a.sharing_info.debug_get_refcount_copied();
        assert_eq!(d2.num_refs, 1);
        assert_eq!(d2.num_mutable_refs, 1);
        // The first snapshot is a copy, not a view: it must not have changed.
        assert_eq!(d.num_refs, 0);
        a.sharing_info.decrease_ref();
        a.sharing_info.decrease_refmut();
        let d3 = a.sharing_info.debug_get_refcount_copied();
        assert_eq!((d3.num_refs, d3.num_mutable_refs), (0, 0));
        // The Debug impl goes through `downcast()` — it must not panic.
        assert!(!alloc::format!("{:?}", a.sharing_info).is_empty());
    }
    #[test]
    fn get_type_name_reports_the_rust_type() {
        #[derive(Clone)]
        struct AutotestNamed(#[allow(dead_code)] u8);
        let a = RefAny::new(AutotestNamed(1));
        let name = a.get_type_name();
        assert!(
            name.as_str().contains("AutotestNamed"),
            "unexpected type name: {}",
            name.as_str()
        );
        let generic = RefAny::new(Vec::<String>::new());
        assert!(generic.get_type_name().as_str().contains("Vec"));
        assert_eq!(RefAny::new(1u32).get_type_name().as_str(), "u32");
    }
    // ---- RefCount: construction, downcast, clone/drop balance ----
    #[test]
    fn refcount_new_downcast_and_clone_lifecycle() {
        let rc = RefCount::new(RefCountInner {
            _internal_ptr: core::ptr::null(),
            num_copies: AtomicUsize::new(1),
            num_refs: AtomicUsize::new(0),
            num_mutable_refs: AtomicUsize::new(0),
            _internal_len: 0,
            _internal_layout_size: 0,
            _internal_layout_align: 1,
            type_id: 0xDEAD_BEEF,
            type_name: AzString::from_const_str("autotest::Synthetic"),
            custom_destructor: noop_destructor,
            serialize_fn: 0,
            deserialize_fn: 0,
            update_fn: 0,
        });
        assert!(!rc.ptr.is_null());
        assert!(rc.run_destructor);
        let inner = rc.downcast();
        assert_eq!(inner.type_id, 0xDEAD_BEEF);
        assert_eq!(inner.type_name.as_str(), "autotest::Synthetic");
        assert_eq!(inner._internal_len, 0);
        assert!(rc.can_be_shared());
        assert!(rc.can_be_shared_mut());
        // Clones must keep the boxed inner alive; the counters must return to 1
        // so the final drop frees it exactly once.
        let c1 = rc.clone();
        assert_eq!(rc.debug_get_refcount_copied().num_copies, 2);
        let c2 = c1.clone();
        assert_eq!(rc.debug_get_refcount_copied().num_copies, 3);
        drop(c2);
        drop(c1);
        assert_eq!(rc.debug_get_refcount_copied().num_copies, 1);
    }
    // The borrow counters must saturate at 0 instead of wrapping to usize::MAX
    // (an unmatched `FooRef_delete` from C would otherwise permanently wedge the
    // runtime borrow checker), and stay usable afterwards.
    #[test]
    fn borrow_counters_saturate_at_zero_and_stay_usable() {
        let mut a = RefAny::new(3i64);
        {
            let rc = &a.sharing_info;
            // 64 unmatched decrements on both counters.
            for _ in 0..64 {
                rc.decrease_ref();
                rc.decrease_refmut();
            }
            let d = rc.debug_get_refcount_copied();
            assert_eq!(d.num_refs, 0);
            assert_eq!(d.num_mutable_refs, 0);
            assert!(rc.can_be_shared());
            assert!(rc.can_be_shared_mut());
            // Many shared borrows coexist, but block a mutable one.
            for _ in 0..256 {
                rc.increase_ref();
            }
            assert_eq!(rc.debug_get_refcount_copied().num_refs, 256);
            assert!(rc.can_be_shared());
            assert!(!rc.can_be_shared_mut());
            for _ in 0..256 {
                rc.decrease_ref();
            }
            assert_eq!(rc.debug_get_refcount_copied().num_refs, 0);
            assert!(rc.can_be_shared_mut());
            // Same for the mutable counter, plus one extra decrement.
            rc.increase_refmut();
            rc.increase_refmut();
            assert!(!rc.can_be_shared());
            rc.decrease_refmut();
            rc.decrease_refmut();
            rc.decrease_refmut();
            assert_eq!(rc.debug_get_refcount_copied().num_mutable_refs, 0);
        }
        // The borrow checker still works after all those underflow attempts.
        assert_eq!(*a.downcast_ref::<i64>().unwrap(), 3);
        assert!(a.downcast_mut::<i64>().is_some());
    }
    // ---- get_type_id_static ----
    // The u64 type ID is the ONLY runtime guard against a wrong-type downcast,
    // so distinct types must not collide (this is what folding ALL TypeId bytes
    // buys us) and it must be stable within a process run.
    #[test]
    fn type_id_static_is_stable_and_collision_free() {
        let ids = [
            RefAny::get_type_id_static::<u8>(),
            RefAny::get_type_id_static::<u16>(),
            RefAny::get_type_id_static::<u32>(),
            RefAny::get_type_id_static::<u64>(),
            RefAny::get_type_id_static::<u128>(),
            RefAny::get_type_id_static::<usize>(),
            RefAny::get_type_id_static::<i8>(),
            RefAny::get_type_id_static::<i16>(),
            RefAny::get_type_id_static::<i32>(),
            RefAny::get_type_id_static::<i64>(),
            RefAny::get_type_id_static::<i128>(),
            RefAny::get_type_id_static::<isize>(),
            RefAny::get_type_id_static::<f32>(),
            RefAny::get_type_id_static::<f64>(),
            RefAny::get_type_id_static::<bool>(),
            RefAny::get_type_id_static::<char>(),
            RefAny::get_type_id_static::<()>(),
            RefAny::get_type_id_static::<String>(),
            RefAny::get_type_id_static::<Vec<u8>>(),
            RefAny::get_type_id_static::<Vec<u16>>(),
            RefAny::get_type_id_static::<[u8; 1]>(),
            RefAny::get_type_id_static::<[u8; 2]>(),
            RefAny::get_type_id_static::<(u8, u8)>(),
            RefAny::get_type_id_static::<(u8, u16)>(),
            RefAny::get_type_id_static::<Option<u8>>(),
            RefAny::get_type_id_static::<Option<u16>>(),
        ];
        for i in 0..ids.len() {
            for j in (i + 1)..ids.len() {
                assert_ne!(ids[i], ids[j], "type id collision between {i} and {j}");
            }
        }
        // Deterministic within a run.
        assert_eq!(RefAny::get_type_id_static::<Vec<u8>>(), ids[18]);
        assert_eq!(RefAny::get_type_id_static::<u8>(), ids[0]);
    }
    // ---- clone / instance ids ----
    #[test]
    fn root_instance_id_is_zero_and_clones_are_distinct() {
        let a = RefAny::new(0u8);
        assert_eq!(a.instance_id, 0);
        let b = a.clone();
        let c = b.clone();
        assert_ne!(b.instance_id, 0);
        assert_ne!(c.instance_id, 0);
        assert_ne!(b.instance_id, c.instance_id);
        assert_eq!(a.get_ref_count(), 3);
    }
    // ---- replace_contents ----
    #[test]
    fn replace_contents_zst_and_value_transitions() {
        #[derive(Clone)]
        struct Zst;
        let mut a = RefAny::new(Zst);
        assert_eq!(a.get_data_len(), 0);
        assert!(a.get_data_ptr().is_null());
        // ZST -> sized: a real allocation must appear.
        assert!(a.replace_contents(RefAny::new(0x4142_4344u32)));
        assert_eq!(a.get_data_len(), 4);
        assert!(!a.get_data_ptr().is_null());
        assert!(a.is_type(RefAny::get_type_id_static::<u32>()));
        assert_eq!(*a.downcast_ref::<u32>().unwrap(), 0x4142_4344);
        // sized -> ZST: the pointer goes back to null, but the ZST still downcasts
        // via a dangling reference; each temporary guard releases its borrow slot
        // on drop, so the exclusive slot is free again afterwards.
        assert!(a.replace_contents(RefAny::new(Zst)));
        assert_eq!(a.get_data_len(), 0);
        assert!(a.get_data_ptr().is_null());
        assert!(a.downcast_ref::<Zst>().is_some());
        assert!(a.downcast_mut::<Zst>().is_some());
        assert!(a.sharing_info.can_be_shared_mut());
    }
    #[test]
    fn replace_contents_is_visible_to_all_clones() {
        let mut a = RefAny::new(1u32);
        let mut b = a.clone();
        assert!(a.replace_contents(RefAny::new(2u32)));
        assert_eq!(*b.downcast_ref::<u32>().unwrap(), 2);
        // The type may change too — every clone sees the new type.
        assert!(a.replace_contents(RefAny::new(String::from("swapped"))));
        assert!(b.downcast_ref::<u32>().is_none());
        assert_eq!(b.downcast_ref::<String>().unwrap().as_str(), "swapped");
        assert!(b.get_type_name().as_str().contains("String"));
        assert_eq!(b.get_type_id(), RefAny::get_type_id_static::<String>());
    }
    #[test]
    fn replace_contents_denied_while_mutably_borrowed() {
        let mut a = RefAny::new(1u32);
        let mut b = a.clone();
        let m = a.downcast_mut::<u32>().unwrap();
        // A live mutable borrow on the shared inner must block the replacement
        // (performing it would free memory the `RefMut` still points at).
        assert!(!b.replace_contents(RefAny::new(2u32)));
        drop(m);
        assert!(b.replace_contents(RefAny::new(2u32)));
        assert_eq!(*b.downcast_ref::<u32>().unwrap(), 2);
    }
    // The serialize/deserialize/update hooks are part of the replaced metadata:
    // after a replacement they describe the NEW value, not the old one.
    #[test]
    fn replace_contents_resets_the_fn_pointers_to_the_new_value() {
        let mut a = RefAny::new(1u32);
        a.set_serialize_fn(3);
        a.set_deserialize_fn(4);
        assert!(a.can_serialize());
        assert!(a.can_deserialize());
        assert!(a.replace_contents(RefAny::new(2u32)));
        assert_eq!(a.get_serialize_fn(), 0);
        assert_eq!(a.get_deserialize_fn(), 0);
        assert_eq!(a.get_update_fn(), 0);
        assert!(!a.can_serialize());
        assert!(!a.can_deserialize());
    }
    // Repeated replacement across changing sizes/alignments must neither leak nor
    // corrupt the payload (Miri checks the alloc/dealloc balance here).
    #[test]
    fn repeated_replace_contents_stays_consistent() {
        let mut a = RefAny::new(String::from("start"));
        for i in 0..16u32 {
            assert!(a.replace_contents(RefAny::new(i)));
            assert_eq!(*a.downcast_ref::<u32>().unwrap(), i);
            assert!(a.replace_contents(RefAny::new(u128::from(i) | (1 << 100))));
            assert_eq!(
                *a.downcast_ref::<u128>().unwrap(),
                u128::from(i) | (1 << 100)
            );
            assert!(a.replace_contents(RefAny::new(String::from("s"))));
        }
        assert_eq!(a.downcast_ref::<String>().unwrap().as_str(), "s");
    }
    // ---- destructor robustness / concurrency ----
    // `default_custom_destructor` is `extern "C"`: a panic from the payload's
    // `Drop` must be caught there, not unwound across the FFI boundary (UB).
    #[cfg(feature = "std")]
    #[test]
    fn panicking_payload_drop_is_contained() {
        struct PanicOnDrop(#[allow(dead_code)] u64);
        impl Drop for PanicOnDrop {
            fn drop(&mut self) {
                panic!("autotest: payload Drop panicked (expected, must be contained)");
            }
        }
        let a = RefAny::new(PanicOnDrop(1));
        drop(a); // must not propagate the panic out of the extern "C" destructor
    }
    // RefAny is Send + Sync: concurrent clone/borrow/drop from several threads
    // must leave the reference count exactly where it started.
    #[cfg(feature = "std")]
    #[test]
    fn concurrent_clone_and_borrow_keeps_the_refcount_balanced() {
        use std::{sync::Arc, thread};
        let shared = Arc::new(RefAny::new(11u32));
        let mut handles = Vec::new();
        for _ in 0..4 {
            let s = Arc::clone(&shared);
            handles.push(thread::spawn(move || {
                for _ in 0..16 {
                    let mut local = (*s).clone();
                    // No thread takes a mutable borrow, so a shared borrow can
                    // never be denied.
                    let r = local
                        .downcast_ref::<u32>()
                        .expect("shared borrow must always succeed here");
                    assert_eq!(*r, 11);
                }
            }));
        }
        for h in handles {
            h.join().expect("worker thread panicked");
        }
        assert_eq!(shared.get_ref_count(), 1);
    }
}