use alloc::boxed::Box;
use alloc::string::String;
use core::{
alloc::Layout,
ffi::c_void,
fmt,
sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
};
use azul_css::AzString;
pub type RefAnyDestructorType = extern "C" fn(*mut c_void);
#[derive(Debug)]
#[repr(C)]
#[allow(clippy::pub_underscore_fields)]
pub struct RefCountInner {
pub _internal_ptr: *const c_void,
pub num_copies: AtomicUsize,
pub num_refs: AtomicUsize,
pub num_mutable_refs: AtomicUsize,
pub _internal_len: usize,
pub _internal_layout_size: usize,
pub _internal_layout_align: usize,
pub type_id: u64,
pub type_name: AzString,
pub custom_destructor: extern "C" fn(*mut c_void),
pub serialize_fn: usize,
pub deserialize_fn: usize,
pub update_fn: usize,
}
#[derive(Hash, PartialEq, PartialOrd, Ord, Eq)]
#[repr(C)]
pub struct RefCount {
pub ptr: *const RefCountInner,
pub run_destructor: bool,
}
impl fmt::Debug for RefCount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.downcast().fmt(f)
}
}
impl Clone for RefCount {
fn clone(&self) -> Self {
if !self.ptr.is_null() {
unsafe {
(*self.ptr).num_copies.fetch_add(1, AtomicOrdering::SeqCst);
}
}
Self {
ptr: self.ptr,
run_destructor: true,
}
}
}
impl Drop for RefCount {
#[allow(clippy::used_underscore_binding)] fn drop(&mut self) {
if !self.run_destructor || self.ptr.is_null() {
return;
}
self.run_destructor = false;
let inner = self.ptr;
self.ptr = core::ptr::null();
let current_copies = unsafe {
match (*inner).num_copies.fetch_update(
AtomicOrdering::SeqCst,
AtomicOrdering::SeqCst,
|n| n.checked_sub(1),
) {
Ok(prev) => prev,
Err(_zero) => return,
}
};
if current_copies != 1 {
return;
}
let sharing_info = unsafe { Box::from_raw(inner.cast_mut()) };
let sharing_info = *sharing_info;
let data_ptr = sharing_info._internal_ptr;
if sharing_info._internal_len == 0
|| sharing_info._internal_layout_size == 0
|| data_ptr.is_null()
{
let mut _dummy: [u8; 0] = [];
(sharing_info.custom_destructor)(_dummy.as_mut_ptr().cast::<c_void>());
} else {
let layout = Layout::from_size_align(
sharing_info._internal_layout_size,
sharing_info._internal_layout_align,
)
.expect("RefCount::drop: stored layout was invalid");
(sharing_info.custom_destructor)(data_ptr.cast_mut());
unsafe {
alloc::alloc::dealloc(data_ptr as *mut u8, layout);
}
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct RefCountInnerDebug {
pub(crate) num_copies: usize,
pub(crate) num_refs: usize,
pub(crate) num_mutable_refs: usize,
pub(crate) _internal_len: usize,
pub(crate) _internal_layout_size: usize,
pub(crate) _internal_layout_align: usize,
pub(crate) type_id: u64,
pub(crate) type_name: AzString,
pub(crate) custom_destructor: usize,
pub(crate) serialize_fn: usize,
pub(crate) deserialize_fn: usize,
}
impl RefCount {
fn new(ref_count: RefCountInner) -> Self {
Self {
ptr: Box::into_raw(Box::new(ref_count)),
run_destructor: true,
}
}
fn downcast(&self) -> &RefCountInner {
assert!(!self.ptr.is_null(), "[RefCount::downcast] FATAL: self.ptr is null!");
unsafe { &*self.ptr }
}
#[allow(clippy::used_underscore_binding)] pub(crate) fn debug_get_refcount_copied(&self) -> RefCountInnerDebug {
let dc = self.downcast();
RefCountInnerDebug {
num_copies: dc.num_copies.load(AtomicOrdering::SeqCst),
num_refs: dc.num_refs.load(AtomicOrdering::SeqCst),
num_mutable_refs: dc.num_mutable_refs.load(AtomicOrdering::SeqCst),
_internal_len: dc._internal_len,
_internal_layout_size: dc._internal_layout_size,
_internal_layout_align: dc._internal_layout_align,
type_id: dc.type_id,
type_name: dc.type_name.clone(),
custom_destructor: dc.custom_destructor as usize,
serialize_fn: dc.serialize_fn,
deserialize_fn: dc.deserialize_fn,
}
}
#[must_use] pub fn can_be_shared(&self) -> bool {
self.downcast()
.num_mutable_refs
.load(AtomicOrdering::SeqCst)
== 0
}
#[must_use] pub fn can_be_shared_mut(&self) -> bool {
let info = self.downcast();
info.num_mutable_refs.load(AtomicOrdering::SeqCst) == 0
&& info.num_refs.load(AtomicOrdering::SeqCst) == 0
}
pub fn increase_ref(&self) {
self.downcast()
.num_refs
.fetch_add(1, AtomicOrdering::SeqCst);
}
pub fn decrease_ref(&self) {
let _ = self.downcast().num_refs.fetch_update(
AtomicOrdering::SeqCst,
AtomicOrdering::SeqCst,
|n| n.checked_sub(1),
);
}
pub fn increase_refmut(&self) {
self.downcast()
.num_mutable_refs
.fetch_add(1, AtomicOrdering::SeqCst);
}
pub fn decrease_refmut(&self) {
let _ = self.downcast().num_mutable_refs.fetch_update(
AtomicOrdering::SeqCst,
AtomicOrdering::SeqCst,
|n| n.checked_sub(1),
);
}
}
#[derive(Debug)]
#[repr(C)]
pub struct Ref<'a, T> {
ptr: &'a T,
sharing_info: RefCount,
}
impl<T> Drop for Ref<'_, T> {
fn drop(&mut self) {
self.sharing_info.decrease_ref();
}
}
impl<T> core::ops::Deref for Ref<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.ptr
}
}
#[derive(Debug)]
#[repr(C)]
pub struct RefMut<'a, T> {
ptr: &'a mut T,
sharing_info: RefCount,
}
impl<T> Drop for RefMut<'_, T> {
fn drop(&mut self) {
self.sharing_info.decrease_refmut();
}
}
impl<T> core::ops::Deref for RefMut<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&*self.ptr
}
}
impl<T> core::ops::DerefMut for RefMut<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.ptr
}
}
#[derive(Debug, Hash, PartialEq, PartialOrd, Ord, Eq)]
#[repr(C)]
pub struct RefAny {
pub sharing_info: RefCount,
pub instance_id: u64,
}
impl_option!(
RefAny,
OptionRefAny,
copy = false,
[Debug, Hash, Clone, PartialEq, PartialOrd, Ord, Eq]
);
#[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for RefAny {}
unsafe impl Sync for RefAny {}
impl RefAny {
pub fn new<T: 'static>(value: T) -> Self {
extern "C" fn default_custom_destructor<U: 'static>(ptr: *mut c_void) {
use core::{mem, ptr};
let run = || unsafe {
let mut stack_mem = mem::MaybeUninit::<U>::uninit();
ptr::copy_nonoverlapping(
ptr as *const U,
stack_mem.as_mut_ptr(),
1, );
let stack_mem = stack_mem.assume_init();
drop(stack_mem); };
#[cfg(feature = "std")]
{
drop(std::panic::catch_unwind(std::panic::AssertUnwindSafe(run)));
}
#[cfg(not(feature = "std"))]
{
run();
}
}
let type_name = ::core::any::type_name::<T>();
let type_id = Self::get_type_id_static::<T>();
let st = AzString::from_const_str(type_name);
let s = Self::new_c(
(&raw const value) as *const c_void,
::core::mem::size_of::<T>(),
::core::mem::align_of::<T>(), type_id,
st,
default_custom_destructor::<T>,
0, 0, );
::core::mem::forget(value); s
}
#[allow(clippy::used_underscore_binding)] pub fn new_c(
ptr: *const c_void,
len: usize,
align: usize,
type_id: u64,
type_name: AzString,
custom_destructor: extern "C" fn(*mut c_void),
serialize_fn: usize,
deserialize_fn: usize,
) -> Self {
use core::ptr;
assert!(!(len > 0 && ptr.is_null()),
"RefAny::new_c: NULL pointer passed for non-ZST type (size={}). \
This would cause undefined behavior. Type: {:?}",
len,
type_name.as_str()
);
let (_internal_ptr, layout) = if len == 0 {
let _dummy: [u8; 0] = [];
(ptr::null_mut(), Layout::for_value(&_dummy))
} else {
let layout = Layout::from_size_align(len, align).expect("Failed to create layout");
let heap_struct_as_bytes = unsafe { alloc::alloc::alloc(layout) };
if heap_struct_as_bytes.is_null() {
alloc::alloc::handle_alloc_error(layout);
}
unsafe { ptr::copy_nonoverlapping(ptr as *const u8, heap_struct_as_bytes, len) };
(heap_struct_as_bytes, layout)
};
let ref_count_inner = RefCountInner {
_internal_ptr: _internal_ptr as *const c_void,
num_copies: AtomicUsize::new(1), num_refs: AtomicUsize::new(0), num_mutable_refs: AtomicUsize::new(0), _internal_len: len,
_internal_layout_size: layout.size(),
_internal_layout_align: layout.align(),
type_id,
type_name,
custom_destructor,
serialize_fn,
deserialize_fn,
update_fn: 0, };
let sharing_info = RefCount::new(ref_count_inner);
Self {
sharing_info,
instance_id: 0, }
}
#[allow(clippy::used_underscore_binding)] #[must_use] pub fn get_data_ptr(&self) -> *const c_void {
self.sharing_info.downcast()._internal_ptr
}
#[allow(clippy::used_underscore_binding)] #[must_use] pub fn get_data_len(&self) -> usize {
self.sharing_info.downcast()._internal_len
}
pub(crate) fn has_no_copies(&self) -> bool {
self.sharing_info
.downcast()
.num_copies
.load(AtomicOrdering::SeqCst)
== 1
&& self
.sharing_info
.downcast()
.num_refs
.load(AtomicOrdering::SeqCst)
== 0
&& self
.sharing_info
.downcast()
.num_mutable_refs
.load(AtomicOrdering::SeqCst)
== 0
}
#[allow(clippy::used_underscore_binding)] #[inline]
pub fn downcast_ref<U: 'static>(&mut self) -> Option<Ref<'_, U>> {
let stored_type_id = self.get_type_id();
let target_type_id = Self::get_type_id_static::<U>();
let is_same_type = stored_type_id == target_type_id;
if !is_same_type {
return None;
}
self.sharing_info.increase_ref();
if !self.sharing_info.can_be_shared() {
self.sharing_info.decrease_ref();
return None;
}
let data_ptr = self.sharing_info.downcast()._internal_ptr;
if data_ptr.is_null() {
self.sharing_info.decrease_ref();
return None;
}
Some(Ref {
ptr: unsafe { &*(data_ptr as *const U) },
sharing_info: self.sharing_info.clone(),
})
}
#[allow(clippy::used_underscore_binding)] #[inline]
pub fn downcast_mut<U: 'static>(&mut self) -> Option<RefMut<'_, U>> {
let is_same_type = self.get_type_id() == Self::get_type_id_static::<U>();
if !is_same_type {
return None;
}
let inner = self.sharing_info.downcast();
if inner
.num_mutable_refs
.compare_exchange(0, 1, AtomicOrdering::SeqCst, AtomicOrdering::SeqCst)
.is_err()
{
return None;
}
if inner.num_refs.load(AtomicOrdering::SeqCst) != 0 {
inner.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
return None;
}
let data_ptr = inner._internal_ptr;
if data_ptr.is_null() {
inner.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
return None;
}
let update_fn = inner.update_fn;
if update_fn != 0 {
let cb: extern "C" fn(*const c_void, usize) =
unsafe { core::mem::transmute(update_fn) };
let len = inner._internal_len;
#[cfg(feature = "std")]
{
drop(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
cb(data_ptr, len);
})));
}
#[cfg(not(feature = "std"))]
{
cb(data_ptr, len);
}
}
Some(RefMut {
ptr: unsafe { &mut *(data_ptr as *mut U) },
sharing_info: self.sharing_info.clone(),
})
}
#[inline]
fn get_type_id_static<T: 'static>() -> u64 {
use core::{any::TypeId, mem};
let t_id = TypeId::of::<T>();
let struct_as_bytes = unsafe {
core::slice::from_raw_parts(
(&raw const t_id) as *const u8,
size_of::<TypeId>(),
)
};
struct_as_bytes.iter().fold(0u64, |hash, &b| {
(hash.rotate_left(5) ^ u64::from(b)).wrapping_mul(0x51_7c_c1_b7_27_22_0a_95)
})
}
#[must_use] pub fn is_type(&self, type_id: u64) -> bool {
self.sharing_info.downcast().type_id == type_id
}
#[must_use] pub fn get_type_id(&self) -> u64 {
self.sharing_info.downcast().type_id
}
#[must_use] pub fn get_type_name(&self) -> AzString {
self.sharing_info.downcast().type_name.clone()
}
#[must_use] pub fn get_ref_count(&self) -> usize {
self.sharing_info
.downcast()
.num_copies
.load(AtomicOrdering::SeqCst)
}
#[must_use] pub fn get_serialize_fn(&self) -> usize {
self.sharing_info.downcast().serialize_fn
}
#[must_use] pub fn get_deserialize_fn(&self) -> usize {
self.sharing_info.downcast().deserialize_fn
}
pub fn set_serialize_fn(&mut self, serialize_fn: usize) {
let inner = self.sharing_info.ptr.cast_mut();
unsafe {
(*inner).serialize_fn = serialize_fn;
}
}
pub fn set_deserialize_fn(&mut self, deserialize_fn: usize) {
let inner = self.sharing_info.ptr.cast_mut();
unsafe {
(*inner).deserialize_fn = deserialize_fn;
}
}
pub fn set_update_fn(&mut self, update_fn: usize) {
let inner = self.sharing_info.ptr.cast_mut();
unsafe {
(*inner).update_fn = update_fn;
}
}
#[must_use] pub fn get_update_fn(&self) -> usize {
self.sharing_info.downcast().update_fn
}
#[must_use] pub fn can_serialize(&self) -> bool {
self.get_serialize_fn() != 0
}
#[must_use] pub fn can_deserialize(&self) -> bool {
self.get_deserialize_fn() != 0
}
#[allow(clippy::used_underscore_binding)] pub fn replace_contents(&mut self, new_value: Self) -> bool {
use core::ptr;
let inner = self.sharing_info.ptr.cast_mut();
let inner_ref = self.sharing_info.downcast();
let mutable_lock_result = inner_ref.num_mutable_refs.compare_exchange(
0, 1, AtomicOrdering::SeqCst,
AtomicOrdering::SeqCst,
);
if mutable_lock_result.is_err() {
return false;
}
if inner_ref.num_refs.load(AtomicOrdering::SeqCst) != 0 {
inner_ref.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
return false;
}
unsafe {
let old_ptr = (*inner)._internal_ptr;
let old_len = (*inner)._internal_len;
let old_layout_size = (*inner)._internal_layout_size;
let old_layout_align = (*inner)._internal_layout_align;
let old_destructor = (*inner).custom_destructor;
if old_len > 0 && !old_ptr.is_null() {
old_destructor(old_ptr.cast_mut());
}
if old_layout_size > 0 && !old_ptr.is_null() {
let old_layout = Layout::from_size_align(old_layout_size, old_layout_align)
.expect("replace_contents: stored old layout was invalid");
alloc::alloc::dealloc(old_ptr as *mut u8, old_layout);
}
let new_inner = new_value.sharing_info.downcast();
let new_ptr = new_inner._internal_ptr;
let new_len = new_inner._internal_len;
let new_layout_size = new_inner._internal_layout_size;
let new_layout_align = new_inner._internal_layout_align;
let allocated_ptr = if new_len == 0 {
ptr::null_mut()
} else {
let new_layout = Layout::from_size_align(new_len, new_layout_align)
.expect("Failed to create layout");
let heap_ptr = alloc::alloc::alloc(new_layout);
if heap_ptr.is_null() {
alloc::alloc::handle_alloc_error(new_layout);
}
ptr::copy_nonoverlapping(
new_ptr as *const u8,
heap_ptr,
new_len,
);
heap_ptr
};
(*inner)._internal_ptr = allocated_ptr as *const c_void;
(*inner)._internal_len = new_len;
(*inner)._internal_layout_size = new_layout_size;
(*inner)._internal_layout_align = new_layout_align;
(*inner).type_id = new_inner.type_id;
(*inner).type_name = new_inner.type_name.clone();
(*inner).custom_destructor = new_inner.custom_destructor;
(*inner).serialize_fn = new_inner.serialize_fn;
(*inner).deserialize_fn = new_inner.deserialize_fn;
(*inner).update_fn = new_inner.update_fn;
}
self.sharing_info.downcast().num_mutable_refs.store(0, AtomicOrdering::SeqCst);
#[allow(clippy::items_after_statements)]
const extern "C" fn noop_destructor(_: *mut c_void) {}
let new_inner = new_value.sharing_info.ptr.cast_mut();
if !new_inner.is_null() {
unsafe {
(*new_inner).custom_destructor = noop_destructor;
}
}
drop(new_value);
true
}
}
impl Clone for RefAny {
fn clone(&self) -> Self {
let inner = self.sharing_info.downcast();
let prev = inner.num_copies.fetch_add(1, AtomicOrdering::SeqCst);
let new_instance_id = (prev + 1) as u64;
Self {
sharing_info: RefCount {
ptr: self.sharing_info.ptr, run_destructor: true, },
instance_id: new_instance_id,
}
}
}
impl Drop for RefAny {
fn drop(&mut self) {
}
}
#[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)] mod audit_tests {
use super::*;
use core::sync::atomic::{AtomicUsize, Ordering};
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
struct DropCounter(#[allow(dead_code)] u32);
impl Drop for DropCounter {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn borrow_exclusion_and_recovery() {
let mut a = RefAny::new(7i32);
let mut b = a.clone();
{
let r = a.downcast_ref::<i32>().unwrap();
assert_eq!(*r, 7);
assert!(b.downcast_mut::<i32>().is_none());
assert!(b.downcast_ref::<i32>().is_some());
}
{
let mut m = a.downcast_mut::<i32>().unwrap();
*m = 42;
assert!(b.downcast_ref::<i32>().is_none());
}
assert_eq!(*a.downcast_ref::<i32>().unwrap(), 42);
}
#[test]
fn type_id_guard() {
let mut a = RefAny::new(1u64);
assert!(a.downcast_ref::<i32>().is_none());
assert!(a.downcast_ref::<u64>().is_some());
assert_eq!(
RefAny::get_type_id_static::<u64>(),
RefAny::get_type_id_static::<u64>()
);
assert_ne!(
RefAny::get_type_id_static::<u64>(),
RefAny::get_type_id_static::<i64>()
);
}
#[test]
fn replace_contents_drops_exactly_once() {
DROP_COUNT.store(0, Ordering::SeqCst);
{
let mut a = RefAny::new(DropCounter(1));
let b = RefAny::new(DropCounter(2));
assert!(a.replace_contents(b));
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 2);
}
#[test]
fn replace_contents_denied_while_borrowed() {
let mut a = RefAny::new(1i32);
let mut a2 = a.clone();
let r = a.downcast_ref::<i32>().unwrap();
assert!(!a2.replace_contents(RefAny::new(2i32)));
drop(r);
assert!(a2.replace_contents(RefAny::new(2i32)));
}
#[test]
fn miri_new_downcast_drop_roundtrip() {
DROP_COUNT.store(0, Ordering::SeqCst);
{
let mut a = RefAny::new(DropCounter(9));
assert!(a.downcast_ref::<DropCounter>().is_some());
assert!(a.downcast_ref::<u8>().is_none());
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
}
#[test]
fn miri_alignment_preserved() {
#[repr(align(16))]
#[derive(Debug)]
struct Over(u64);
let mut a = RefAny::new(Over(0xABCD));
let r = a.downcast_ref::<Over>().unwrap();
assert_eq!(r.0, 0xABCD);
assert_eq!((&raw const *r) as usize % 16, 0);
}
#[test]
fn miri_clone_refcount_increment_decrement() {
DROP_COUNT.store(0, Ordering::SeqCst);
{
let a = RefAny::new(DropCounter(1));
assert_eq!(a.get_ref_count(), 1);
let b = a.clone();
assert_eq!(a.get_ref_count(), 2);
assert_eq!(b.get_ref_count(), 2);
{
let c = b.clone();
assert_eq!(c.get_ref_count(), 3);
}
assert_eq!(a.get_ref_count(), 2);
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
}
#[test]
fn miri_downcast_mut_mutation_visible_across_clones() {
let mut a = RefAny::new(10u32);
let mut b = a.clone();
{
let mut m = a.downcast_mut::<u32>().unwrap();
*m += 5;
}
assert_eq!(*b.downcast_ref::<u32>().unwrap(), 15);
}
#[test]
fn miri_borrow_counter_transitions_and_underflow_guard() {
let a = RefAny::new(0i32);
let rc = &a.sharing_info;
assert!(rc.can_be_shared());
assert!(rc.can_be_shared_mut());
rc.increase_ref();
assert!(rc.can_be_shared()); assert!(!rc.can_be_shared_mut()); rc.decrease_ref();
assert!(rc.can_be_shared_mut());
rc.increase_refmut();
assert!(!rc.can_be_shared()); assert!(!rc.can_be_shared_mut());
rc.decrease_refmut();
assert!(rc.can_be_shared_mut());
rc.decrease_ref();
rc.decrease_refmut();
assert!(rc.can_be_shared());
assert!(rc.can_be_shared_mut());
}
#[test]
fn miri_type_id_static_stable_and_distinct() {
assert_eq!(
RefAny::get_type_id_static::<(u8, u64)>(),
RefAny::get_type_id_static::<(u8, u64)>()
);
assert_ne!(
RefAny::get_type_id_static::<u32>(),
RefAny::get_type_id_static::<[u32; 2]>()
);
}
#[test]
fn miri_zst_roundtrip_and_destructor() {
DROP_COUNT.store(0, Ordering::SeqCst);
struct ZstDrop;
impl Drop for ZstDrop {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
{
let mut a = RefAny::new(ZstDrop);
assert_eq!(a.get_data_len(), 0);
assert!(a.downcast_ref::<ZstDrop>().is_none());
let _b = a.clone();
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
}
#[test]
fn miri_replace_contents_alloc_paths() {
DROP_COUNT.store(0, Ordering::SeqCst);
{
let mut a = RefAny::new(DropCounter(1));
assert!(a.replace_contents(RefAny::new(DropCounter(2))));
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1); assert_eq!(a.downcast_ref::<DropCounter>().unwrap().0, 2u32);
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 2);
}
#[test]
fn miri_replace_contents_changes_layout() {
let mut a = RefAny::new(7u8);
assert!(a.replace_contents(RefAny::new(0x1122_3344_5566_7788u64)));
{
let r = a.downcast_ref::<u64>().unwrap();
assert_eq!(*r, 0x1122_3344_5566_7788u64);
assert_eq!((&raw const *r) as usize % core::mem::align_of::<u64>(), 0);
}
assert!(a.downcast_ref::<u8>().is_none());
}
#[test]
fn miri_refcount_clone_keeps_inner_alive() {
let a = RefAny::new(5usize);
let rc0 = a.sharing_info.clone(); let rc1 = rc0.clone(); assert_eq!(a.get_ref_count(), 3);
drop(rc1);
drop(rc0);
assert_eq!(a.get_ref_count(), 1);
assert_eq!(*a.clone().downcast_ref::<usize>().unwrap(), 5);
}
}
#[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
)] mod autotest_generated {
use alloc::{string::String, vec::Vec};
use core::{
ffi::c_void,
sync::atomic::{AtomicUsize, Ordering},
};
use super::*;
extern "C" fn noop_destructor(_: *mut c_void) {}
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);
}
#[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,
));
}
#[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, RefAny::get_type_id_static::<u32>(),
AzString::from_const_str("autotest::BadAlign"),
noop_destructor,
0,
0,
));
}
#[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,
));
}
#[test]
fn new_c_zero_len_null_ptr_is_a_clean_zst() {
let mut a = RefAny::new_c(
core::ptr::null(),
0,
0, 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::<()>()));
assert!(a.downcast_ref::<()>().is_none());
assert!(a.downcast_mut::<()>().is_none());
assert!(a.sharing_info.can_be_shared_mut());
}
#[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);
}
#[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, 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());
assert!(a.sharing_info.can_be_shared_mut());
}
#[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);
}
#[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(), "");
}
#[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);
}
#[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_none());
assert_eq!(a.get_ref_count(), 1);
}
#[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));
}
#[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);
round_trip(u128::MAX);
round_trip(i128::MIN);
round_trip(i128::MAX);
round_trip(usize::MAX);
round_trip(isize::MIN);
round_trip(0usize);
}
#[test]
fn float_extremes_round_trip_bit_exact() {
let mut nan = RefAny::new(f64::NAN);
assert!(nan.downcast_ref::<f64>().unwrap().is_nan());
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);
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);
}
#[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 mut v: Vec<String> = Vec::new();
v.push(String::from("a"));
v.push(String::from("🎉"));
v.push(String::new());
round_trip(v);
}
#[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,
});
}
#[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());
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());
}
#[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);
assert!(a.downcast_mut::<u32>().is_some());
}
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>() {
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);
assert_eq!(UPDATE_PRE_VALUE.load(Ordering::SeqCst), 7);
assert!(a.downcast_mut::<u64>().is_none());
assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
assert_eq!(*a.downcast_ref::<u32>().unwrap(), 9);
assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
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);
b.set_update_fn(0);
assert!(b.downcast_mut::<u32>().is_some());
assert_eq!(UPDATE_CALLS.load(Ordering::SeqCst), 1);
}
#[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)); 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()); assert!(!b.has_no_copies());
}
assert!(a.has_no_copies());
{
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());
}
#[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);
{
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);
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));
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");
}
#[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());
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);
}
#[test]
fn borrow_counters_saturate_at_zero_and_stay_usable() {
let mut a = RefAny::new(3i64);
{
let rc = &a.sharing_info;
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());
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());
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);
}
assert_eq!(*a.downcast_ref::<i64>().unwrap(), 3);
assert!(a.downcast_mut::<i64>().is_some());
}
#[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}");
}
}
assert_eq!(RefAny::get_type_id_static::<Vec<u8>>(), ids[18]);
assert_eq!(RefAny::get_type_id_static::<u8>(), ids[0]);
}
#[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);
}
#[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());
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);
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_none());
assert!(a.downcast_mut::<Zst>().is_none());
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);
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();
assert!(!b.replace_contents(RefAny::new(2u32)));
drop(m);
assert!(b.replace_contents(RefAny::new(2u32)));
assert_eq!(*b.downcast_ref::<u32>().unwrap(), 2);
}
#[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());
}
#[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");
}
#[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); }
#[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();
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);
}
}