Skip to content

Commit 7d31030

Browse files
authored
Merge pull request #410 from madsmtm/track-caller
Add `#[track_caller]` test
2 parents 6e6ac03 + 91e23d9 commit 7d31030

File tree

4 files changed

+221
-19
lines changed

4 files changed

+221
-19
lines changed

crates/objc2/src/__macro_helpers.rs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ pub const fn retain_semantics(selector: &str) -> u8 {
105105
}
106106

107107
pub trait MsgSendId<T, U> {
108+
#[track_caller]
108109
unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<Input = U>>(
109110
obj: T,
110111
sel: Sel,
@@ -164,7 +165,6 @@ unsafe fn encountered_error<E: Message>(err: *mut E) -> Id<E, Shared> {
164165

165166
impl<T: MessageReceiver, U: ?Sized + Message, O: Ownership> MsgSendId<T, Id<U, O>> for New {
166167
#[inline]
167-
#[track_caller]
168168
unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<Input = Id<U, O>>>(
169169
obj: T,
170170
sel: Sel,
@@ -184,7 +184,6 @@ impl<T: MessageReceiver, U: ?Sized + Message, O: Ownership> MsgSendId<T, Id<U, O
184184

185185
impl<T: ?Sized + Message> MsgSendId<&'_ Class, Allocated<T>> for Alloc {
186186
#[inline]
187-
#[track_caller]
188187
unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<Input = Allocated<T>>>(
189188
cls: &Class,
190189
sel: Sel,
@@ -200,7 +199,6 @@ impl<T: ?Sized + Message> MsgSendId<&'_ Class, Allocated<T>> for Alloc {
200199

201200
impl<T: ?Sized + Message, O: Ownership> MsgSendId<Option<Allocated<T>>, Id<T, O>> for Init {
202201
#[inline]
203-
#[track_caller]
204202
unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<Input = Id<T, O>>>(
205203
obj: Option<Allocated<T>>,
206204
sel: Sel,
@@ -224,7 +222,6 @@ impl<T: MessageReceiver, U: ?Sized + Message, O: Ownership> MsgSendId<T, Id<U, O
224222
for CopyOrMutCopy
225223
{
226224
#[inline]
227-
#[track_caller]
228225
unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<Input = Id<U, O>>>(
229226
obj: T,
230227
sel: Sel,
@@ -241,7 +238,6 @@ impl<T: MessageReceiver, U: ?Sized + Message, O: Ownership> MsgSendId<T, Id<U, O
241238

242239
impl<T: MessageReceiver, U: Message, O: Ownership> MsgSendId<T, Id<U, O>> for Other {
243240
#[inline]
244-
#[track_caller]
245241
unsafe fn send_message_id<A: MessageArguments, R: MaybeUnwrap<Input = Id<U, O>>>(
246242
obj: T,
247243
sel: Sel,
@@ -265,14 +261,14 @@ impl<T: MessageReceiver, U: Message, O: Ownership> MsgSendId<T, Id<U, O>> for Ot
265261

266262
pub trait MaybeUnwrap {
267263
type Input;
264+
#[track_caller]
268265
fn maybe_unwrap<'a, F: MsgSendIdFailed<'a>>(obj: Option<Self::Input>, args: F::Args) -> Self;
269266
}
270267

271268
impl<T: ?Sized, O: Ownership> MaybeUnwrap for Option<Id<T, O>> {
272269
type Input = Id<T, O>;
273270

274271
#[inline]
275-
#[track_caller]
276272
fn maybe_unwrap<'a, F: MsgSendIdFailed<'a>>(obj: Option<Id<T, O>>, _args: F::Args) -> Self {
277273
obj
278274
}
@@ -282,7 +278,6 @@ impl<T: ?Sized, O: Ownership> MaybeUnwrap for Id<T, O> {
282278
type Input = Id<T, O>;
283279

284280
#[inline]
285-
#[track_caller]
286281
fn maybe_unwrap<'a, F: MsgSendIdFailed<'a>>(obj: Option<Id<T, O>>, args: F::Args) -> Self {
287282
match obj {
288283
Some(obj) => obj,
@@ -295,7 +290,6 @@ impl<T: ?Sized> MaybeUnwrap for Option<Allocated<T>> {
295290
type Input = Allocated<T>;
296291

297292
#[inline]
298-
#[track_caller]
299293
fn maybe_unwrap<'a, F: MsgSendIdFailed<'a>>(obj: Option<Allocated<T>>, _args: F::Args) -> Self {
300294
obj
301295
}
@@ -305,7 +299,6 @@ impl<T: ?Sized> MaybeUnwrap for Allocated<T> {
305299
type Input = Allocated<T>;
306300

307301
#[inline]
308-
#[track_caller]
309302
fn maybe_unwrap<'a, F: MsgSendIdFailed<'a>>(obj: Option<Allocated<T>>, args: F::Args) -> Self {
310303
match obj {
311304
Some(obj) => obj,
@@ -323,14 +316,14 @@ impl<T: ?Sized> MaybeUnwrap for Allocated<T> {
323316
pub trait MsgSendIdFailed<'a> {
324317
type Args;
325318

319+
#[track_caller]
326320
fn failed(args: Self::Args) -> !;
327321
}
328322

329323
impl<'a> MsgSendIdFailed<'a> for New {
330324
type Args = (Option<&'a Object>, Sel);
331325

332326
#[cold]
333-
#[track_caller]
334327
fn failed((obj, sel): Self::Args) -> ! {
335328
if let Some(obj) = obj {
336329
let cls = obj.class();
@@ -353,7 +346,6 @@ impl<'a> MsgSendIdFailed<'a> for Alloc {
353346
type Args = (&'a Class, Sel);
354347

355348
#[cold]
356-
#[track_caller]
357349
fn failed((cls, sel): Self::Args) -> ! {
358350
if sel == alloc_sel() {
359351
panic!("failed allocating {:?}", cls)
@@ -367,7 +359,6 @@ impl MsgSendIdFailed<'_> for Init {
367359
type Args = (*const Object, Sel);
368360

369361
#[cold]
370-
#[track_caller]
371362
fn failed((ptr, sel): Self::Args) -> ! {
372363
if ptr.is_null() {
373364
panic!("failed allocating object")
@@ -387,7 +378,6 @@ impl MsgSendIdFailed<'_> for CopyOrMutCopy {
387378
type Args = ();
388379

389380
#[cold]
390-
#[track_caller]
391381
fn failed(_: Self::Args) -> ! {
392382
panic!("failed copying object")
393383
}
@@ -397,7 +387,6 @@ impl<'a> MsgSendIdFailed<'a> for Other {
397387
type Args = (Option<&'a Object>, Sel);
398388

399389
#[cold]
400-
#[track_caller]
401390
fn failed((obj, sel): Self::Args) -> ! {
402391
if let Some(obj) = obj {
403392
let cls = obj.class();

crates/objc2/src/declare.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,7 @@ mod tests {
985985
let _cls = Custom::class();
986986
}
987987

988-
// Proof-of-concept how we could make declare_class! accept generic.
988+
// Proof-of-concept how we could make declare_class! accept generic types.
989989
#[test]
990990
fn test_generic() {
991991
struct GenericDeclareClass<T>(T);

crates/objc2/src/macros/declare_class.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
///
6969
/// If the `#[method_id(...)]` attribute is used, the return type must be
7070
/// `Option<Id<T, O>>` or `Id<T, O>`. Additionally, if the selector is in the
71-
/// "init"-family, the "self"/"this" argument must be `Allocated<Self>`.
71+
/// "init"-family, the `self`/`this` argument must be `Allocated<Self>`.
7272
///
7373
/// Putting other attributes on the method such as `cfg`, `allow`, `doc`,
7474
/// `deprecated` and so on is supported. However, note that `cfg_attr` may not
@@ -232,11 +232,11 @@
232232
/// }
233233
///
234234
/// unsafe impl NSCopying for MyCustomObject {
235-
/// #[method(copyWithZone:)]
236-
/// fn copy_with_zone(&self, _zone: *const NSZone) -> *mut Self {
235+
/// #[method_id(copyWithZone:)]
236+
/// fn copy_with_zone(&self, _zone: *const NSZone) -> Id<Self, Owned> {
237237
/// let mut obj = Self::new(*self.foo);
238238
/// *obj.bar = *self.bar;
239-
/// obj.autorelease_return()
239+
/// obj
240240
/// }
241241
///
242242
/// // If we have tried to add other methods here, or had forgotten

crates/objc2/tests/track_caller.rs

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
//! Test that our use of #[track_caller] is making the correct line number
2+
//! show up.
3+
use std::panic;
4+
use std::process::abort;
5+
use std::ptr;
6+
use std::sync::Mutex;
7+
8+
use objc2::encode::Encode;
9+
use objc2::rc::{Allocated, Id, Shared, __RcTestObject};
10+
use objc2::runtime::{NSObject, Object};
11+
use objc2::{class, declare_class, msg_send, msg_send_id, ClassType};
12+
13+
static EXPECTED_MESSAGE: Mutex<String> = Mutex::new(String::new());
14+
static EXPECTED_LINE: Mutex<u32> = Mutex::new(0);
15+
16+
pub struct PanicChecker(());
17+
18+
impl PanicChecker {
19+
fn new() -> Self {
20+
panic::set_hook(Box::new(|info| {
21+
let expected_message = EXPECTED_MESSAGE.lock().unwrap();
22+
let expected_line = EXPECTED_LINE.lock().unwrap();
23+
24+
let payload = info.payload();
25+
let message = if let Some(payload) = payload.downcast_ref::<&'static str>() {
26+
payload.to_string()
27+
} else if let Some(payload) = payload.downcast_ref::<String>() {
28+
payload.clone()
29+
} else {
30+
format!("could not extract message: {payload:?}")
31+
};
32+
let location = info.location().expect("location");
33+
34+
if !message.contains(&*expected_message) {
35+
eprintln!("expected {expected_message:?}, got: {message:?}");
36+
abort();
37+
}
38+
if location.file() != file!() {
39+
eprintln!("expected file {:?}, got: {:?}", file!(), location.file());
40+
abort();
41+
}
42+
if location.line() != *expected_line {
43+
eprintln!("expected line {expected_line}, got: {}", location.line());
44+
abort();
45+
}
46+
}));
47+
Self(())
48+
}
49+
50+
fn assert_panics(&self, message: &str, line: u32, f: impl FnOnce()) {
51+
*EXPECTED_MESSAGE.lock().unwrap() = message.to_string();
52+
*EXPECTED_LINE.lock().unwrap() = line;
53+
54+
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
55+
f();
56+
}));
57+
assert!(res.is_err());
58+
59+
*EXPECTED_MESSAGE.lock().unwrap() = "unknown".to_string();
60+
*EXPECTED_LINE.lock().unwrap() = 0;
61+
}
62+
}
63+
64+
impl Drop for PanicChecker {
65+
fn drop(&mut self) {
66+
let _ = panic::take_hook();
67+
}
68+
}
69+
70+
#[test]
71+
fn test_track_caller() {
72+
let checker = PanicChecker::new();
73+
74+
#[cfg(debug_assertions)]
75+
{
76+
test_nil(&checker);
77+
test_verify(&checker);
78+
test_error_methods(&checker);
79+
}
80+
81+
test_id_unwrap(&checker);
82+
83+
#[cfg(feature = "catch-all")]
84+
test_catch_all(&checker);
85+
86+
test_unwind(&checker);
87+
}
88+
89+
pub fn test_nil(checker: &PanicChecker) {
90+
let nil: *mut Object = ptr::null_mut();
91+
92+
let msg = "messsaging description to nil";
93+
checker.assert_panics(msg, line!() + 1, || {
94+
let _: *mut Object = unsafe { msg_send![nil, description] };
95+
});
96+
checker.assert_panics(msg, line!() + 1, || {
97+
let _: *mut Object = unsafe { msg_send![super(nil, NSObject::class()), description] };
98+
});
99+
checker.assert_panics(msg, line!() + 1, || {
100+
let _: Option<Id<Object, Shared>> = unsafe { msg_send_id![nil, description] };
101+
});
102+
}
103+
104+
pub fn test_verify(checker: &PanicChecker) {
105+
let obj = NSObject::new();
106+
107+
let msg = "invalid message send to -[NSObject description]: expected return to have type code '@', but found 'v'";
108+
checker.assert_panics(msg, line!() + 1, || {
109+
let _: () = unsafe { msg_send![&obj, description] };
110+
});
111+
112+
let msg = format!("invalid message send to -[NSObject hash]: expected return to have type code '{}', but found '@'", usize::ENCODING);
113+
checker.assert_panics(&msg, line!() + 1, || {
114+
let _: Option<Id<Object, Shared>> = unsafe { msg_send_id![&obj, hash] };
115+
});
116+
}
117+
118+
pub fn test_error_methods(checker: &PanicChecker) {
119+
let nil: *mut Object = ptr::null_mut();
120+
121+
let msg = "messsaging someSelectorWithError: to nil";
122+
checker.assert_panics(msg, line!() + 2, || {
123+
let _: Result<(), Id<NSObject, Shared>> =
124+
unsafe { msg_send![nil, someSelectorWithError: _] };
125+
});
126+
checker.assert_panics(msg, line!() + 2, || {
127+
let _: Result<(), Id<NSObject, Shared>> =
128+
unsafe { msg_send![super(nil, NSObject::class()), someSelectorWithError: _] };
129+
});
130+
checker.assert_panics(msg, line!() + 2, || {
131+
let _: Result<Id<Object, Shared>, Id<NSObject, Shared>> =
132+
unsafe { msg_send_id![nil, someSelectorWithError: _] };
133+
});
134+
135+
let msg = "invalid message send to -[NSObject someSelectorWithError:]: method not found";
136+
checker.assert_panics(msg, line!() + 3, || {
137+
let obj = __RcTestObject::new();
138+
let _: Result<(), Id<NSObject, Shared>> =
139+
unsafe { msg_send![super(&obj), someSelectorWithError: _] };
140+
});
141+
}
142+
143+
pub fn test_id_unwrap(checker: &PanicChecker) {
144+
let cls = __RcTestObject::class();
145+
let obj = __RcTestObject::new();
146+
147+
let msg = "failed creating new instance using +[__RcTestObject newReturningNull]";
148+
checker.assert_panics(msg, line!() + 1, || {
149+
let _obj: Id<__RcTestObject, Shared> = unsafe { msg_send_id![cls, newReturningNull] };
150+
});
151+
152+
let msg = "failed allocating with +[__RcTestObject allocReturningNull]";
153+
checker.assert_panics(msg, line!() + 1, || {
154+
let _obj: Allocated<__RcTestObject> = unsafe { msg_send_id![cls, allocReturningNull] };
155+
});
156+
157+
let msg = "failed initializing object with -initReturningNull";
158+
checker.assert_panics(msg, line!() + 2, || {
159+
let _obj: Id<__RcTestObject, Shared> =
160+
unsafe { msg_send_id![__RcTestObject::alloc(), initReturningNull] };
161+
});
162+
163+
let msg = "failed copying object";
164+
checker.assert_panics(msg, line!() + 1, || {
165+
let _obj: Id<__RcTestObject, Shared> = unsafe { msg_send_id![&obj, copyReturningNull] };
166+
});
167+
168+
let msg = "unexpected NULL returned from -[__RcTestObject methodReturningNull]";
169+
checker.assert_panics(msg, line!() + 1, || {
170+
let _obj: Id<__RcTestObject, Shared> = unsafe { msg_send_id![&obj, methodReturningNull] };
171+
});
172+
}
173+
174+
pub fn test_catch_all(checker: &PanicChecker) {
175+
let obj: Id<NSObject, Shared> = unsafe { msg_send_id![class!(NSArray), new] };
176+
177+
let msg = "NSRangeException";
178+
checker.assert_panics(msg, line!() + 1, || {
179+
let _: *mut Object = unsafe { msg_send![&obj, objectAtIndex: 0usize] };
180+
});
181+
182+
let msg = "NSRangeException";
183+
checker.assert_panics(msg, line!() + 1, || {
184+
let _: Id<Object, Shared> = unsafe { msg_send_id![&obj, objectAtIndex: 0usize] };
185+
});
186+
}
187+
188+
declare_class!(
189+
struct PanickingClass;
190+
191+
unsafe impl ClassType for PanickingClass {
192+
type Super = NSObject;
193+
const NAME: &'static str = "PanickingClass";
194+
}
195+
196+
unsafe impl PanickingClass {
197+
#[method(panic)]
198+
fn _panic() -> *mut Self {
199+
panic!("panic in PanickingClass")
200+
}
201+
}
202+
);
203+
204+
pub fn test_unwind(checker: &PanicChecker) {
205+
let msg = "panic in PanickingClass";
206+
let line = line!() - 7;
207+
checker.assert_panics(msg, line, || {
208+
let _: *mut Object = unsafe { msg_send![PanickingClass::class(), panic] };
209+
});
210+
checker.assert_panics(msg, line, || {
211+
let _: Id<Object, Shared> = unsafe { msg_send_id![PanickingClass::class(), panic] };
212+
});
213+
}

0 commit comments

Comments
 (0)