use core::{cell::Cell, mem::MaybeUninit};
use crate::slot::Slot;
pub(crate) struct SlotStorageTracker {
initialized: Cell<bool>,
released: Cell<bool>,
references: Cell<usize>,
}
impl SlotStorageTracker {
#[inline]
pub const fn new() -> Self {
return Self {
initialized: Cell::new(false),
released: Cell::new(false),
references: Cell::new(0),
};
}
#[inline]
pub const fn status(&self) -> SlotStorageStatus<'_> {
return SlotStorageStatus {
initialized: &self.initialized,
released: &self.released,
references: &self.references, };
}
}
#[derive(Clone, Copy)]
pub(crate) struct SlotStorageStatus<'frame> {
initialized: &'frame Cell<bool>,
released: &'frame Cell<bool>,
references: &'frame Cell<usize>,
}
impl<'frame> SlotStorageStatus<'frame> {
#[inline]
pub(crate) fn initialize(&self) {
debug_assert!(!self.is_initialized());
self.initialized.set(true);
self.increment();
}
#[inline]
pub(crate) fn increment(&self) {
debug_assert!(self.is_reference_zeroed());
self.references.set(self.references.get() + 1);
}
#[inline]
pub(crate) fn decrement(&self) {
debug_assert!(!self.is_reference_zeroed());
self.references.set(self.references.get() - 1);
}
#[inline]
pub(crate) unsafe fn release(&self) {
self.released.set(true);
}
#[inline]
pub(crate) fn terminate(&self) {
self.decrement();
debug_assert!(self.is_reference_zeroed());
}
#[inline]
pub(crate) fn is_initialized(&self) -> bool {
return self.initialized.get();
}
#[inline]
pub(crate) fn is_uninitialized(&self) -> bool {
return self.is_reference_zeroed() && !self.is_initialized();
}
#[inline]
pub(crate) fn is_released(&self) -> bool {
return self.released.get();
}
#[inline]
pub(crate) fn is_leaking(&self) -> bool {
return !self.is_released() && self.is_initialized() && !self.is_reference_zeroed();
}
#[inline]
pub(crate) fn is_reference_zeroed(&self) -> bool {
return self.references.get() == 0;
}
}
#[allow(clippy::module_name_repetitions)]
#[derive(Copy, Clone, Debug)]
pub enum SlotStorageKind {
Drop,
Keep,
}
pub struct SlotStorage<T> {
kind: SlotStorageKind,
memory: MaybeUninit<T>,
tracker: SlotStorageTracker,
#[cfg(debug_assertions)]
location: &'static core::panic::Location<'static>,
}
impl<T> Drop for SlotStorage<T> {
fn drop(&mut self) {
let status = self.tracker.status();
if status.is_uninitialized() {
return; }
if status.is_leaking() {
self.non_unwinding_panic_abort();
}
if matches!(self.kind, SlotStorageKind::Drop) {
unsafe { self.memory.assume_init_drop() }
}
}
}
impl<T> SlotStorage<T> {
#[must_use]
#[inline]
pub const fn new(kind: SlotStorageKind) -> Self {
return Self {
kind, memory: MaybeUninit::uninit(), tracker: SlotStorageTracker::new(),
#[cfg(debug_assertions)] location: core::panic::Location::caller(),
};
}
#[inline]
pub fn slot(&mut self) -> Slot<'_, T> {
let memory = &mut self.memory;
let status = self.tracker.status();
return Slot { memory, status };
}
#[inline]
pub fn display_location(&self) -> &dyn core::fmt::Display {
const UNKNOWN: &str = "<unknown>";
if cfg!(debug_assertions) {
return self.location;
}
return &UNKNOWN; }
fn non_unwinding_panic_abort(&self) {
struct DropAndPanic;
#[rustfmt::skip]
impl Drop for DropAndPanic { #[inline]
fn drop(&mut self) {
#[allow(clippy::manual_assert)] if cfg!(not(test)) {
panic!("initiating double-panic to trigger an LLVM abort") }
}
}
let _first_panic_trigger = DropAndPanic;
panic!(
"a critical reference counter at {} was not zeroed!",
self.display_location() );
}
}
#[cfg(test)]
mod test {
use crate::*;
mod coverage {
use super::*;
#[test]
fn slot_storage_drop() {
let kind = SlotStorageKind::Drop;
let storage = SlotStorage::<()>::new(kind);
drop(storage);
}
}
}