use core::ffi::CStr; use core::fmt::{Debug, DebugStruct, Error, Formatter}; use core::ptr; use crate::abi::{BlockDescriptorPtr, BlockFlags, BlockHeader}; use crate::ffi; #[derive(Clone, Copy, PartialEq, Eq)] struct Isa(*const ffi::Class); impl Isa { #[allow(unused_unsafe)] fn is_global(self) -> bool { ptr::eq( unsafe { ptr::addr_of!(ffi::_NSConcreteGlobalBlock) }, self.0, ) } #[allow(unused_unsafe)] fn is_stack(self) -> bool { ptr::eq(unsafe { ptr::addr_of!(ffi::_NSConcreteStackBlock) }, self.0) } } impl Debug for Isa { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { if self.is_global() { f.write_str("_NSConcreteGlobalBlock") } else if self.is_stack() { f.write_str("_NSConcreteStackBlock") } else { write!(f, "{:?} (likely _NSConcreteMallocBlock)", self.0) } } } pub(crate) fn debug_block_header(header: &BlockHeader, f: &mut DebugStruct<'_, '_>) { f.field("isa", &Isa(header.isa)); f.field("flags", &header.flags); f.field("reserved", &header.reserved); f.field("invoke", &header.invoke); f.field( "descriptor", &BlockDescriptorHelper { has_copy_dispose: header.flags.has(BlockFlags::BLOCK_HAS_COPY_DISPOSE), has_signature: header.flags.has(BlockFlags::BLOCK_HAS_SIGNATURE), descriptor: header.descriptor, }, ); } #[derive(Clone, Copy)] struct BlockDescriptorHelper { has_copy_dispose: bool, has_signature: bool, descriptor: BlockDescriptorPtr, } impl Debug for BlockDescriptorHelper { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { if unsafe { self.descriptor.basic }.is_null() { return f.write_str("(null)"); } let mut f = f.debug_struct("BlockDescriptor"); let header = unsafe { self.descriptor.basic.as_ref().unwrap() }; f.field("reserved", &header.reserved); f.field("size", &header.size); match (self.has_copy_dispose, self.has_signature) { (false, false) => {} (true, false) => { let descriptor = unsafe { self.descriptor.with_copy_dispose.as_ref().unwrap() }; f.field("copy", &descriptor.copy); f.field("dispose", &descriptor.dispose); } (false, true) => { let descriptor = unsafe { self.descriptor.with_signature.as_ref().unwrap() }; f.field( "encoding", &if descriptor.encoding.is_null() { None } else { Some(unsafe { CStr::from_ptr(descriptor.encoding) }) }, ); } (true, true) => { let descriptor = unsafe { self.descriptor .with_copy_dispose_signature .as_ref() .unwrap() }; f.field("copy", &descriptor.copy); f.field("dispose", &descriptor.dispose); f.field( "encoding", &if descriptor.encoding.is_null() { None } else { Some(unsafe { CStr::from_ptr(descriptor.encoding) }) }, ); } } f.finish() } } #[cfg(test)] mod tests { use super::*; #[test] #[allow(unused_unsafe)] fn test_isa() { let isa = Isa(unsafe { ptr::addr_of!(ffi::_NSConcreteGlobalBlock) }); assert!(isa.is_global()); assert!(!isa.is_stack()); let isa = Isa(unsafe { ptr::addr_of!(ffi::_NSConcreteStackBlock) }); assert!(!isa.is_global()); assert!(isa.is_stack()); let isa = Isa(unsafe { ptr::addr_of!(ffi::private::_NSConcreteMallocBlock) }); assert!(!isa.is_global()); assert!(!isa.is_stack()); let isa = Isa(ptr::null()); assert!(!isa.is_global()); assert!(!isa.is_stack()); } }