Skip to content

Commit 0a6644a

Browse files
authored
Merge pull request #156 from madsmtm/block-debug
Implement `Debug` for all block structures
2 parents 736dc75 + 1189f6b commit 0a6644a

File tree

7 files changed

+382
-2
lines changed

7 files changed

+382
-2
lines changed

block2/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## Unreleased - YYYY-MM-DD
88

9+
### Added
10+
* Implemented `Debug` for `Block`, `ConcreteBlock`, `RcBlock` and
11+
`GlobalBlock`.
12+
913

1014
## 0.2.0-alpha.4 - 2022-06-13
1115

block2/src/debug.rs

+242
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
use alloc::format;
2+
use core::ffi::c_void;
3+
use core::fmt::{Debug, DebugStruct, Error, Formatter};
4+
use core::ptr;
5+
use std::ffi::CStr;
6+
7+
use crate::{ffi, Block, ConcreteBlock, GlobalBlock, RcBlock};
8+
9+
#[derive(Clone, Copy, PartialEq, Eq)]
10+
struct Isa(*const ffi::Class);
11+
12+
impl Isa {
13+
fn is_global(self) -> bool {
14+
ptr::eq(unsafe { &ffi::_NSConcreteGlobalBlock }, self.0)
15+
}
16+
17+
fn is_stack(self) -> bool {
18+
ptr::eq(unsafe { &ffi::_NSConcreteStackBlock }, self.0)
19+
}
20+
21+
fn is_malloc(self) -> bool {
22+
ptr::eq(unsafe { &ffi::_NSConcreteMallocBlock }, self.0)
23+
}
24+
}
25+
26+
impl Debug for Isa {
27+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
28+
if self.is_global() {
29+
f.write_str("_NSConcreteGlobalBlock")
30+
} else if self.is_stack() {
31+
f.write_str("_NSConcreteStackBlock")
32+
} else if self.is_malloc() {
33+
f.write_str("_NSConcreteMallocBlock")
34+
} else {
35+
write!(f, "{:?}", self.0)
36+
}
37+
}
38+
}
39+
40+
fn debug_block_layout(layout: &ffi::Block_layout, f: &mut DebugStruct<'_, '_>) {
41+
f.field("isa", &Isa(layout.isa));
42+
f.field("flags", &BlockFlags(layout.flags));
43+
f.field("reserved", &layout.reserved);
44+
f.field("invoke", &layout.invoke);
45+
f.field(
46+
"descriptor",
47+
&BlockDescriptor {
48+
has_copy_dispose: layout.flags & ffi::BLOCK_HAS_COPY_DISPOSE != 0,
49+
has_signature: layout.flags & ffi::BLOCK_HAS_SIGNATURE != 0,
50+
descriptor: layout.descriptor,
51+
},
52+
);
53+
}
54+
55+
impl<A, R> Debug for Block<A, R> {
56+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
57+
let mut f = f.debug_struct("Block");
58+
let ptr: *const Self = self;
59+
let layout = unsafe { ptr.cast::<ffi::Block_layout>().as_ref().unwrap() };
60+
debug_block_layout(layout, &mut f);
61+
f.finish_non_exhaustive()
62+
}
63+
}
64+
65+
impl<A, R> Debug for RcBlock<A, R> {
66+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
67+
let mut f = f.debug_struct("RcBlock");
68+
let layout = unsafe { self.ptr.cast::<ffi::Block_layout>().as_ref().unwrap() };
69+
debug_block_layout(layout, &mut f);
70+
f.finish_non_exhaustive()
71+
}
72+
}
73+
74+
impl<A, R, F: Debug> Debug for ConcreteBlock<A, R, F> {
75+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
76+
let mut f = f.debug_struct("ConcreteBlock");
77+
debug_block_layout(&self.layout, &mut f);
78+
f.field("closure", &self.closure);
79+
f.finish()
80+
}
81+
}
82+
83+
impl<A, R> Debug for GlobalBlock<A, R> {
84+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
85+
let mut f = f.debug_struct("GlobalBlock");
86+
debug_block_layout(&self.layout, &mut f);
87+
f.finish_non_exhaustive()
88+
}
89+
}
90+
91+
#[derive(Clone, Copy, PartialEq, Eq)]
92+
struct BlockFlags(ffi::block_flags);
93+
94+
impl Debug for BlockFlags {
95+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
96+
let mut f = f.debug_struct("BlockFlags");
97+
f.field("value", &format!("{:032b}", self.0));
98+
99+
macro_rules! test_flags {
100+
{$(
101+
$(#[$m:meta])?
102+
$name:ident: $flag:ident
103+
);* $(;)?} => ($(
104+
$(#[$m])?
105+
f.field(stringify!($name), &(self.0 & ffi::$flag != 0));
106+
)*)
107+
}
108+
test_flags! {
109+
#[cfg(feature = "apple")]
110+
deallocating: BLOCK_DEALLOCATING;
111+
#[cfg(feature = "apple")]
112+
inline_layout_string: BLOCK_INLINE_LAYOUT_STRING;
113+
#[cfg(feature = "apple")]
114+
small_descriptor: BLOCK_SMALL_DESCRIPTOR;
115+
#[cfg(feature = "apple")]
116+
is_noescape: BLOCK_IS_NOESCAPE;
117+
#[cfg(feature = "apple")]
118+
needs_free: BLOCK_NEEDS_FREE;
119+
has_copy_dispose: BLOCK_HAS_COPY_DISPOSE;
120+
has_ctor: BLOCK_HAS_CTOR;
121+
#[cfg(feature = "apple")]
122+
is_gc: BLOCK_IS_GC;
123+
is_global: BLOCK_IS_GLOBAL;
124+
use_stret: BLOCK_USE_STRET;
125+
has_signature: BLOCK_HAS_SIGNATURE;
126+
#[cfg(feature = "apple")]
127+
has_extended_layout: BLOCK_HAS_EXTENDED_LAYOUT;
128+
}
129+
130+
f.field(
131+
"over_referenced",
132+
&(self.0 & ffi::BLOCK_REFCOUNT_MASK == ffi::BLOCK_REFCOUNT_MASK),
133+
);
134+
f.field(
135+
"reference_count",
136+
&((self.0 & ffi::BLOCK_REFCOUNT_MASK) >> 1),
137+
);
138+
f.finish_non_exhaustive()
139+
}
140+
}
141+
142+
#[derive(Clone, Copy, PartialEq, Eq)]
143+
struct BlockDescriptor {
144+
has_copy_dispose: bool,
145+
has_signature: bool,
146+
descriptor: *const c_void,
147+
}
148+
149+
impl Debug for BlockDescriptor {
150+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
151+
if self.descriptor.is_null() {
152+
return f.write_str("(null)");
153+
}
154+
155+
let mut f = f.debug_struct("BlockDescriptor");
156+
157+
let header = unsafe {
158+
self.descriptor
159+
.cast::<ffi::Block_descriptor_header>()
160+
.as_ref()
161+
.unwrap()
162+
};
163+
164+
f.field("reserved", &header.reserved);
165+
f.field("size", &header.size);
166+
167+
match (self.has_copy_dispose, self.has_signature) {
168+
(false, false) => {}
169+
(true, false) => {
170+
let descriptor = unsafe {
171+
self.descriptor
172+
.cast::<ffi::Block_descriptor>()
173+
.as_ref()
174+
.unwrap()
175+
};
176+
f.field("copy", &descriptor.copy);
177+
f.field("dispose", &descriptor.dispose);
178+
}
179+
(false, true) => {
180+
let descriptor = unsafe {
181+
self.descriptor
182+
.cast::<ffi::Block_descriptor_basic>()
183+
.as_ref()
184+
.unwrap()
185+
};
186+
f.field(
187+
"encoding",
188+
&if descriptor.encoding.is_null() {
189+
None
190+
} else {
191+
Some(unsafe { CStr::from_ptr(descriptor.encoding) })
192+
},
193+
);
194+
}
195+
(true, true) => {
196+
let descriptor = unsafe {
197+
self.descriptor
198+
.cast::<ffi::Block_descriptor_with_signature>()
199+
.as_ref()
200+
.unwrap()
201+
};
202+
f.field("copy", &descriptor.copy);
203+
f.field("dispose", &descriptor.dispose);
204+
f.field(
205+
"encoding",
206+
&if descriptor.encoding.is_null() {
207+
None
208+
} else {
209+
Some(unsafe { CStr::from_ptr(descriptor.encoding) })
210+
},
211+
);
212+
}
213+
}
214+
215+
f.finish()
216+
}
217+
}
218+
219+
#[cfg(test)]
220+
mod tests {
221+
use super::*;
222+
223+
#[test]
224+
fn test_isa() {
225+
let isa = Isa(unsafe { &ffi::_NSConcreteGlobalBlock });
226+
assert!(isa.is_global());
227+
assert!(!isa.is_stack());
228+
assert!(!isa.is_malloc());
229+
let isa = Isa(unsafe { &ffi::_NSConcreteStackBlock });
230+
assert!(!isa.is_global());
231+
assert!(isa.is_stack());
232+
assert!(!isa.is_malloc());
233+
let isa = Isa(unsafe { &ffi::_NSConcreteMallocBlock });
234+
assert!(!isa.is_global());
235+
assert!(!isa.is_stack());
236+
assert!(isa.is_malloc());
237+
let isa = Isa(ptr::null());
238+
assert!(!isa.is_global());
239+
assert!(!isa.is_stack());
240+
assert!(!isa.is_malloc());
241+
}
242+
}

block2/src/global.rs

+59-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const GLOBAL_DESCRIPTOR: ffi::Block_descriptor_header = ffi::Block_descriptor_he
2828
/// [`global_block!`]: crate::global_block
2929
#[repr(C)]
3030
pub struct GlobalBlock<A, R = ()> {
31-
layout: ffi::Block_layout,
31+
pub(crate) layout: ffi::Block_layout,
3232
p: PhantomData<(A, R)>,
3333
}
3434

@@ -188,6 +188,9 @@ macro_rules! global_block {
188188

189189
#[cfg(test)]
190190
mod tests {
191+
use super::*;
192+
use alloc::format;
193+
191194
global_block! {
192195
/// Test comments and visibility
193196
pub(super) static NOOP_BLOCK = || {};
@@ -213,4 +216,59 @@ mod tests {
213216
});
214217
assert_eq!(unsafe { MY_BLOCK.call(()) }, 42);
215218
}
219+
220+
#[cfg(feature = "apple")]
221+
const DEBUG_BLOCKFLAGS: &str = r#"BlockFlags {
222+
value: "00110000000000000000000000000000",
223+
deallocating: false,
224+
inline_layout_string: false,
225+
small_descriptor: false,
226+
is_noescape: false,
227+
needs_free: false,
228+
has_copy_dispose: false,
229+
has_ctor: false,
230+
is_gc: false,
231+
is_global: true,
232+
use_stret: true,
233+
has_signature: false,
234+
has_extended_layout: false,
235+
over_referenced: false,
236+
reference_count: 0,
237+
..
238+
}"#;
239+
240+
#[cfg(not(feature = "apple"))]
241+
const DEBUG_BLOCKFLAGS: &str = r#"BlockFlags {
242+
value: "00110000000000000000000000000000",
243+
has_copy_dispose: false,
244+
has_ctor: false,
245+
is_global: true,
246+
use_stret: true,
247+
has_signature: false,
248+
over_referenced: false,
249+
reference_count: 0,
250+
..
251+
}"#;
252+
253+
#[test]
254+
fn test_debug() {
255+
let invoke = NOOP_BLOCK.layout.invoke.unwrap();
256+
let size = mem::size_of::<ffi::Block_layout>();
257+
let expected = format!(
258+
"GlobalBlock {{
259+
isa: _NSConcreteGlobalBlock,
260+
flags: {DEBUG_BLOCKFLAGS},
261+
reserved: 0,
262+
invoke: Some(
263+
{invoke:#?},
264+
),
265+
descriptor: BlockDescriptor {{
266+
reserved: 0,
267+
size: {size},
268+
}},
269+
..
270+
}}"
271+
);
272+
assert_eq!(format!("{:#?}", NOOP_BLOCK), expected);
273+
}
216274
}

block2/src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
// Update in Cargo.toml as well.
8686
#![doc(html_root_url = "https://docs.rs/block2/0.2.0-alpha.4")]
8787

88+
extern crate alloc;
8889
extern crate std;
8990

9091
#[cfg(doctest)]
@@ -101,6 +102,7 @@ use std::os::raw::c_ulong;
101102
pub use block_sys as ffi;
102103
use objc2_encode::{Encode, EncodeArguments, Encoding, RefEncode};
103104

105+
mod debug;
104106
#[macro_use]
105107
mod global;
106108

@@ -208,7 +210,7 @@ impl<A: BlockArguments + EncodeArguments, R: Encode> Block<A, R> {
208210

209211
/// A reference-counted Objective-C block.
210212
pub struct RcBlock<A, R> {
211-
ptr: *mut Block<A, R>,
213+
pub(crate) ptr: *mut Block<A, R>,
212214
}
213215

214216
impl<A, R> RcBlock<A, R> {

tests/build.rs

+8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ fn main() {
1212
builder.flag(flag);
1313
}
1414

15+
for flag in env::var("DEP_OBJC_0_2_CC_ARGS").unwrap().split(' ') {
16+
builder.flag(flag);
17+
}
18+
19+
builder.flag("-fno-objc-arc");
20+
21+
builder.flag("-xobjective-c");
22+
1523
builder.compile("libblock_utils.a");
1624

1725
let mut builder = cc::Build::new();

0 commit comments

Comments
 (0)