objc2/runtime/
message_receiver.rs

1use core::ptr::NonNull;
2
3use crate::encode::{EncodeArguments, EncodeReturn, RefEncode};
4use crate::runtime::{AnyClass, AnyObject, Sel};
5use crate::Message;
6
7/// Wrap the given closure in `exception::catch` if the `catch-all` feature is
8/// enabled.
9///
10/// This is a macro to help with monomorphization when the feature is
11/// disabled, as well as improving the final stack trace (`#[track_caller]`
12/// doesn't really work on closures).
13#[cfg(not(feature = "catch-all"))]
14macro_rules! conditional_try {
15    (|| $expr:expr) => {
16        $expr
17    };
18}
19
20#[cfg(feature = "catch-all")]
21macro_rules! conditional_try {
22    (|| $expr:expr) => {{
23        let f = core::panic::AssertUnwindSafe(|| $expr);
24        match crate::exception::catch(f) {
25            Ok(r) => r,
26            Err(exception) => {
27                if let Some(exception) = exception {
28                    panic!("uncaught {exception:?}\n{}", exception.stack_trace())
29                } else {
30                    panic!("uncaught exception nil")
31                }
32            }
33        }
34    }};
35}
36
37// More information on how objc_msgSend works:
38// <https://web.archive.org/web/20200118080513/http://www.friday.com/bbum/2009/12/18/objc_msgsend-part-1-the-road-map/>
39// <https://www.mikeash.com/pyblog/objc_msgsends-new-prototype.html>
40// <https://www.mikeash.com/pyblog/friday-qa-2012-11-16-lets-build-objc_msgsend.html>
41#[cfg(all(target_vendor = "apple", not(feature = "gnustep-1-7")))]
42mod msg_send_primitive {
43    #[allow(unused_imports)]
44    use core::mem;
45
46    #[allow(unused_imports)]
47    use crate::encode::Encoding;
48    use crate::encode::{EncodeArguments, EncodeReturn};
49    use crate::ffi;
50    use crate::runtime::{AnyClass, AnyObject, Imp, Sel};
51
52    /// On the below architectures we can statically find the correct method to
53    /// call from the return type, by looking at its `EncodeReturn` impl.
54    #[allow(clippy::missing_safety_doc)]
55    unsafe trait MsgSendFn: EncodeReturn {
56        const MSG_SEND: Imp;
57        const MSG_SEND_SUPER: Imp;
58    }
59
60    #[cfg(target_arch = "aarch64")]
61    /// `objc_msgSend_stret` is not even available in arm64.
62    ///
63    /// <https://twitter.com/gparker/status/378079715824660480>
64    unsafe impl<T: EncodeReturn> MsgSendFn for T {
65        const MSG_SEND: Imp = ffi::objc_msgSend;
66        const MSG_SEND_SUPER: Imp = ffi::objc_msgSendSuper;
67    }
68
69    #[cfg(target_arch = "arm")]
70    /// Double-word sized fundamental data types don't use stret, but any
71    /// composite type larger than 4 bytes does.
72    ///
73    /// <https://web.archive.org/web/20191016000656/http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042f/IHI0042F_aapcs.pdf>
74    /// <https://developer.arm.com/documentation/ihi0042/latest>
75    /// <https://github.com/llvm/llvm-project/blob/llvmorg-17.0.6/clang/lib/CodeGen/Targets/ARM.cpp#L531>
76    unsafe impl<T: EncodeReturn> MsgSendFn for T {
77        const MSG_SEND: Imp = {
78            if let Encoding::LongLong | Encoding::ULongLong | Encoding::Double = T::ENCODING_RETURN
79            {
80                ffi::objc_msgSend
81            } else if mem::size_of::<T>() <= 4 {
82                ffi::objc_msgSend
83            } else {
84                ffi::objc_msgSend_stret
85            }
86        };
87        const MSG_SEND_SUPER: Imp = {
88            if let Encoding::LongLong | Encoding::ULongLong | Encoding::Double = T::ENCODING_RETURN
89            {
90                ffi::objc_msgSendSuper
91            } else if mem::size_of::<T>() <= 4 {
92                ffi::objc_msgSendSuper
93            } else {
94                ffi::objc_msgSendSuper_stret
95            }
96        };
97    }
98
99    #[cfg(target_arch = "x86")]
100    /// Structures 1 or 2 bytes in size are placed in EAX.
101    /// Structures 4 or 8 bytes in size are placed in: EAX and EDX.
102    /// Structures of other sizes are placed at the address supplied by the caller.
103    ///
104    /// <https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/130-IA-32_Function_Calling_Conventions/IA32.html>
105    /// <https://github.com/llvm/llvm-project/blob/llvmorg-17.0.6/clang/lib/CodeGen/Targets/X86.cpp#L472>
106    unsafe impl<T: EncodeReturn> MsgSendFn for T {
107        const MSG_SEND: Imp = {
108            // See https://github.com/apple-oss-distributions/objc4/blob/objc4-818.2/runtime/message.h#L156-L172
109            if let Encoding::Float | Encoding::Double | Encoding::LongDouble = T::ENCODING_RETURN {
110                ffi::objc_msgSend_fpret
111            } else if let 0 | 1 | 2 | 4 | 8 = mem::size_of::<T>() {
112                ffi::objc_msgSend
113            } else {
114                ffi::objc_msgSend_stret
115            }
116        };
117        const MSG_SEND_SUPER: Imp = {
118            if let 0 | 1 | 2 | 4 | 8 = mem::size_of::<T>() {
119                ffi::objc_msgSendSuper
120            } else {
121                ffi::objc_msgSendSuper_stret
122            }
123        };
124    }
125
126    #[cfg(target_arch = "x86_64")]
127    /// If the size of an object is larger than two eightbytes, it has class
128    /// MEMORY. If the type has class MEMORY, then the caller provides space for
129    /// the return value and passes the address of this storage.
130    ///
131    /// <https://www.uclibc.org/docs/psABI-x86_64.pdf>
132    /// <https://github.com/llvm/llvm-project/blob/llvmorg-17.0.6/clang/lib/CodeGen/Targets/X86.cpp#L2532>
133    unsafe impl<T: EncodeReturn> MsgSendFn for T {
134        const MSG_SEND: Imp = {
135            // See https://github.com/apple-oss-distributions/objc4/blob/objc4-818.2/runtime/message.h#L156-L172
136            if let Encoding::LongDouble = T::ENCODING_RETURN {
137                ffi::objc_msgSend_fpret
138            } else if let Encoding::LongDoubleComplex = T::ENCODING_RETURN {
139                ffi::objc_msgSend_fp2ret
140            } else if mem::size_of::<T>() <= 16 {
141                ffi::objc_msgSend
142            } else {
143                ffi::objc_msgSend_stret
144            }
145        };
146        const MSG_SEND_SUPER: Imp = {
147            if mem::size_of::<T>() <= 16 {
148                ffi::objc_msgSendSuper
149            } else {
150                ffi::objc_msgSendSuper_stret
151            }
152        };
153    }
154
155    #[inline]
156    #[track_caller]
157    pub(crate) unsafe fn send<A: EncodeArguments, R: EncodeReturn>(
158        receiver: *mut AnyObject,
159        sel: Sel,
160        args: A,
161    ) -> R {
162        let msg_send_fn = R::MSG_SEND;
163        // Note: Modern Objective-C compilers have a workaround to ensure that
164        // messages to `nil` with a struct return produces `mem::zeroed()`,
165        // see:
166        // <https://www.sealiesoftware.com/blog/archive/2012/2/29/objc_explain_return_value_of_message_to_nil.html>
167        //
168        // We _could_ technically do something similar, but since we're
169        // disallowing messages to `nil` with `debug_assertions` enabled
170        // anyhow, and since Rust has a much stronger type-system that
171        // disallows NULL/nil in most cases, we won't bother supporting it.
172        unsafe { A::__invoke(msg_send_fn, receiver, sel, args) }
173    }
174
175    #[inline]
176    #[track_caller]
177    pub(crate) unsafe fn send_super<A: EncodeArguments, R: EncodeReturn>(
178        receiver: *mut AnyObject,
179        super_class: &AnyClass,
180        sel: Sel,
181        args: A,
182    ) -> R {
183        let mut sup = ffi::objc_super {
184            receiver,
185            super_class,
186        };
187        let receiver: *mut ffi::objc_super = &mut sup;
188        let receiver = receiver.cast();
189
190        let msg_send_fn = R::MSG_SEND_SUPER;
191        unsafe { A::__invoke(msg_send_fn, receiver, sel, args) }
192    }
193}
194
195#[cfg(feature = "gnustep-1-7")]
196mod msg_send_primitive {
197    use core::mem;
198
199    use crate::encode::{EncodeArguments, EncodeReturn};
200    use crate::ffi;
201    use crate::runtime::{AnyClass, AnyObject, Imp, Sel};
202
203    #[inline]
204    fn unwrap_msg_send_fn(msg_send_fn: Option<Imp>) -> Imp {
205        match msg_send_fn {
206            Some(msg_send_fn) => msg_send_fn,
207            None => {
208                // SAFETY: This will never be NULL, even if the selector is not
209                // found a callable function pointer will still be returned!
210                //
211                // `clang` doesn't insert a NULL check here either.
212                unsafe { core::hint::unreachable_unchecked() }
213            }
214        }
215    }
216
217    #[track_caller]
218    pub(crate) unsafe fn send<A: EncodeArguments, R: EncodeReturn>(
219        receiver: *mut AnyObject,
220        sel: Sel,
221        args: A,
222    ) -> R {
223        // If `receiver` is NULL, objc_msg_lookup will return a standard
224        // C-method taking two arguments, the receiver and the selector.
225        //
226        // Transmuting and calling such a function with multiple parameters is
227        // safe as long as the return value is a primitive (and e.g. not a big
228        // struct or array).
229        //
230        // However, when the return value is a floating point value, the float
231        // will end up as some undefined value, usually NaN, which is
232        // incompatible with Apple's platforms. As such, we insert this extra
233        // NULL check here.
234        if receiver.is_null() {
235            // SAFETY: Caller guarantees that messages to NULL-receivers only
236            // return pointers or primitive values, and a mem::zeroed pointer
237            // / primitive is just a NULL-pointer or a zeroed primitive.
238            return unsafe { mem::zeroed() };
239        }
240
241        let msg_send_fn = unsafe { ffi::objc_msg_lookup(receiver, sel) };
242        let msg_send_fn = unwrap_msg_send_fn(msg_send_fn);
243        unsafe { A::__invoke(msg_send_fn, receiver, sel, args) }
244    }
245
246    #[track_caller]
247    pub(crate) unsafe fn send_super<A: EncodeArguments, R: EncodeReturn>(
248        receiver: *mut AnyObject,
249        super_class: &AnyClass,
250        sel: Sel,
251        args: A,
252    ) -> R {
253        if receiver.is_null() {
254            // SAFETY: Same as in `send`.
255            return unsafe { mem::zeroed() };
256        }
257
258        let sup = ffi::objc_super {
259            receiver,
260            super_class,
261        };
262        let msg_send_fn = unsafe { ffi::objc_msg_lookup_super(&sup, sel) };
263        let msg_send_fn = unwrap_msg_send_fn(msg_send_fn);
264        unsafe { A::__invoke(msg_send_fn, receiver, sel, args) }
265    }
266}
267
268#[cfg(all(not(target_vendor = "apple"), not(feature = "gnustep-1-7")))]
269mod msg_send_primitive {
270    use crate::encode::{EncodeArguments, EncodeReturn};
271    use crate::runtime::{AnyClass, AnyObject, Sel};
272
273    #[track_caller]
274    pub(crate) unsafe fn send<A: EncodeArguments, R: EncodeReturn>(
275        _receiver: *mut AnyObject,
276        _sel: Sel,
277        _args: A,
278    ) -> R {
279        unimplemented!("no runtime chosen")
280    }
281
282    #[track_caller]
283    pub(crate) unsafe fn send_super<A: EncodeArguments, R: EncodeReturn>(
284        _receiver: *mut AnyObject,
285        _superclass: &AnyClass,
286        _sel: Sel,
287        _args: A,
288    ) -> R {
289        unimplemented!("no runtime chosen")
290    }
291}
292
293/// Help with monomorphizing in framework crates
294#[cfg(debug_assertions)]
295#[track_caller]
296fn msg_send_check(
297    obj: Option<&AnyObject>,
298    sel: Sel,
299    args: &[crate::encode::Encoding],
300    ret: &crate::encode::Encoding,
301) {
302    let cls = if let Some(obj) = obj {
303        obj.class()
304    } else {
305        panic_null(sel)
306    };
307
308    msg_send_check_class(cls, sel, args, ret);
309}
310
311#[cfg(debug_assertions)]
312#[track_caller]
313fn msg_send_check_class(
314    cls: &AnyClass,
315    sel: Sel,
316    args: &[crate::encode::Encoding],
317    ret: &crate::encode::Encoding,
318) {
319    if cfg!(feature = "disable-encoding-assertions") {
320        // These checks are disabled.
321        return;
322    }
323
324    use crate::verify::{verify_method_signature, Inner, VerificationError};
325
326    let err = if let Some(method) = cls.instance_method(sel) {
327        if let Err(err) = verify_method_signature(method, args, ret) {
328            err
329        } else {
330            return;
331        }
332    } else {
333        VerificationError::from(Inner::MethodNotFound)
334    };
335
336    panic_verify(cls, sel, &err);
337}
338
339#[cfg(debug_assertions)]
340#[track_caller]
341fn panic_null(sel: Sel) -> ! {
342    panic!("messsaging {sel} to nil")
343}
344
345#[cfg(debug_assertions)]
346#[track_caller]
347fn panic_verify(cls: &AnyClass, sel: Sel, err: &crate::runtime::VerificationError) -> ! {
348    panic!(
349        "invalid message send to {}[{cls} {sel}]: {err}",
350        if cls.is_metaclass() { "+" } else { "-" },
351    )
352}
353
354mod private {
355    pub trait Sealed {}
356}
357
358/// Types that can directly be used as the receiver of Objective-C messages.
359///
360/// Examples include objects pointers, class pointers, and block pointers.
361///
362///
363/// # Safety
364///
365/// This is a sealed trait, and should not need to be implemented. Open an
366/// issue if you know a use-case where this restrition should be lifted!
367pub unsafe trait MessageReceiver: private::Sealed + Sized {
368    #[doc(hidden)]
369    type __Inner: ?Sized + RefEncode;
370
371    #[doc(hidden)]
372    fn __as_raw_receiver(self) -> *mut AnyObject;
373
374    /// Sends a message to the receiver with the given selector and arguments.
375    ///
376    /// This should be used instead of the [`performSelector:`] family of
377    /// methods, as this is both more performant and flexible than that.
378    ///
379    /// The correct version of `objc_msgSend` will be chosen based on the
380    /// return type. For more information, see [the Messaging section in
381    /// Apple's Objective-C Runtime Programming Guide][guide-messaging].
382    ///
383    /// If the selector is known at compile-time, it is recommended to use the
384    /// [`msg_send!`] macro rather than this method.
385    ///
386    /// [`performSelector:`]: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418867-performselector?language=objc
387    /// [guide-messaging]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.html
388    ///
389    ///
390    /// # Safety
391    ///
392    /// This shares the same safety requirements as [`msg_send!`].
393    ///
394    /// The added invariant is that the selector must take the same number of
395    /// arguments as is given.
396    ///
397    /// [`msg_send!`]: crate::msg_send
398    ///
399    ///
400    /// # Example
401    ///
402    /// Call the `copy` method, but using a dynamic selector instead.
403    ///
404    /// ```no_run
405    /// use objc2::rc::Retained;
406    /// use objc2::runtime::MessageReceiver;
407    /// use objc2::sel;
408    /// # use objc2::runtime::NSObject as MyObject;
409    ///
410    /// let obj = MyObject::new();
411    /// // SAFETY: The `copy` method takes no arguments, and returns an object
412    /// let copy: *mut MyObject = unsafe { obj.send_message(sel!(copy), ()) };
413    /// // SAFETY: The `copy` method returns an object with +1 retain count
414    /// let copy = unsafe { Retained::from_raw(copy) }.unwrap();
415    /// ```
416    #[inline]
417    #[track_caller]
418    #[doc(alias = "performSelector")]
419    #[doc(alias = "performSelector:")]
420    #[doc(alias = "performSelector:withObject:")]
421    #[doc(alias = "performSelector:withObject:withObject:")]
422    unsafe fn send_message<A: EncodeArguments, R: EncodeReturn>(self, sel: Sel, args: A) -> R {
423        let receiver = self.__as_raw_receiver();
424        #[cfg(debug_assertions)]
425        {
426            // SAFETY: Caller ensures only valid or NULL pointers.
427            let obj = unsafe { receiver.as_ref() };
428            msg_send_check(obj, sel, A::ENCODINGS, &R::ENCODING_RETURN);
429        }
430
431        // SAFETY: Upheld by caller
432        conditional_try!(|| unsafe { msg_send_primitive::send(receiver, sel, args) })
433    }
434
435    /// Sends a message to a specific superclass with the given selector and
436    /// arguments.
437    ///
438    /// The correct version of `objc_msgSend_super` will be chosen based on the
439    /// return type. For more information, see the section on "Sending
440    /// Messages" in Apple's [documentation][runtime].
441    ///
442    /// If the selector is known at compile-time, it is recommended to use the
443    /// [`msg_send!(super(...), ...)`] macro rather than this method.
444    ///
445    /// [runtime]: https://developer.apple.com/documentation/objectivec/objective-c_runtime?language=objc
446    ///
447    ///
448    /// # Safety
449    ///
450    /// This shares the same safety requirements as
451    /// [`msg_send!(super(...), ...)`].
452    ///
453    /// The added invariant is that the selector must take the same number of
454    /// arguments as is given.
455    ///
456    /// [`msg_send!(super(...), ...)`]: crate::msg_send
457    #[inline]
458    #[track_caller]
459    unsafe fn send_super_message<A: EncodeArguments, R: EncodeReturn>(
460        self,
461        superclass: &AnyClass,
462        sel: Sel,
463        args: A,
464    ) -> R {
465        let receiver = self.__as_raw_receiver();
466        #[cfg(debug_assertions)]
467        {
468            if receiver.is_null() {
469                panic_null(sel);
470            }
471            msg_send_check_class(superclass, sel, A::ENCODINGS, &R::ENCODING_RETURN);
472        }
473
474        // SAFETY: Upheld by caller
475        conditional_try!(|| unsafe {
476            msg_send_primitive::send_super(receiver, superclass, sel, args)
477        })
478    }
479}
480
481// Note that we implement MessageReceiver for unsized types as well, this is
482// to support `extern type`s in the future, not because we want to allow DSTs.
483
484impl<T: ?Sized + Message> private::Sealed for *const T {}
485unsafe impl<T: ?Sized + Message> MessageReceiver for *const T {
486    type __Inner = T;
487
488    #[inline]
489    fn __as_raw_receiver(self) -> *mut AnyObject {
490        (self as *mut T).cast()
491    }
492}
493
494impl<T: ?Sized + Message> private::Sealed for *mut T {}
495unsafe impl<T: ?Sized + Message> MessageReceiver for *mut T {
496    type __Inner = T;
497
498    #[inline]
499    fn __as_raw_receiver(self) -> *mut AnyObject {
500        self.cast()
501    }
502}
503
504impl<T: ?Sized + Message> private::Sealed for NonNull<T> {}
505unsafe impl<T: ?Sized + Message> MessageReceiver for NonNull<T> {
506    type __Inner = T;
507
508    #[inline]
509    fn __as_raw_receiver(self) -> *mut AnyObject {
510        self.as_ptr().cast()
511    }
512}
513
514impl<T: ?Sized + Message> private::Sealed for &T {}
515unsafe impl<T: ?Sized + Message> MessageReceiver for &T {
516    type __Inner = T;
517
518    #[inline]
519    fn __as_raw_receiver(self) -> *mut AnyObject {
520        let ptr: *const T = self;
521        (ptr as *mut T).cast()
522    }
523}
524
525impl private::Sealed for &mut AnyObject {}
526/// `&mut AnyObject` is allowed as mutable, for easier transition from `objc`,
527/// even though it's basically always incorrect to hold `&mut AnyObject`.
528///
529/// Use `*mut AnyObject` instead if you know for certain you need mutability,
530/// and cannot make do with interior mutability.
531unsafe impl MessageReceiver for &mut AnyObject {
532    type __Inner = AnyObject;
533
534    #[inline]
535    fn __as_raw_receiver(self) -> *mut AnyObject {
536        self
537    }
538}
539
540#[cfg(test)]
541mod tests {
542    use core::ptr;
543
544    use super::*;
545    use crate::msg_send;
546    use crate::rc::{Allocated, Retained};
547    use crate::runtime::NSObject;
548    use crate::test_utils;
549
550    #[allow(unused)]
551    fn test_different_receivers(obj: &mut AnyObject) {
552        unsafe {
553            let x = &mut *obj;
554            let _: () = msg_send![x, mutable1];
555            // `x` is consumed by the above, so this won't work:
556            // let _: () = msg_send![x, mutable2];
557
558            // It is only possible if we reborrow:
559            let _: () = msg_send![&mut *obj, mutable1];
560            let _: () = msg_send![&mut *obj, mutable2];
561
562            // Test NonNull
563            let obj = NonNull::from(obj);
564            let _: () = msg_send![obj, mutable1];
565            let _: () = msg_send![obj, mutable2];
566
567            // And test raw pointers
568            let obj: *mut AnyObject = obj.as_ptr();
569            let _: () = msg_send![obj, mutable1];
570            let _: () = msg_send![obj, mutable2];
571        }
572    }
573
574    #[test]
575    fn test_send_message() {
576        let obj = test_utils::custom_object();
577        let _: () = unsafe { msg_send![&obj, setFoo: 4u32] };
578        let result: u32 = unsafe { msg_send![&obj, foo] };
579        assert_eq!(result, 4);
580    }
581
582    #[test]
583    fn test_send_message_stret() {
584        let obj = test_utils::custom_object();
585        let result: test_utils::CustomStruct = unsafe { msg_send![&obj, customStruct] };
586        let expected = test_utils::CustomStruct {
587            a: 1,
588            b: 2,
589            c: 3,
590            d: 4,
591        };
592        assert_eq!(result, expected);
593    }
594
595    #[test]
596    #[cfg_attr(debug_assertions, should_panic = "messsaging description to nil")]
597    fn test_send_message_nil() {
598        let nil: *mut NSObject = ::core::ptr::null_mut();
599
600        // This result should not be relied on
601        let result: Option<Retained<NSObject>> = unsafe { msg_send![nil, description] };
602        assert!(result.is_none());
603
604        // This result should not be relied on
605        let result: usize = unsafe { msg_send![nil, hash] };
606        assert_eq!(result, 0);
607
608        // This result should not be relied on
609        #[cfg(target_pointer_width = "16")]
610        let result: f32 = 0.0;
611        #[cfg(target_pointer_width = "32")]
612        let result: f32 = unsafe { msg_send![nil, floatValue] };
613        #[cfg(target_pointer_width = "64")]
614        let result: f64 = unsafe { msg_send![nil, doubleValue] };
615        assert_eq!(result, 0.0);
616
617        // This result should not be relied on
618        let result: Option<Retained<NSObject>> =
619            unsafe { msg_send![nil, multiple: 1u32, arguments: 2i8] };
620        assert!(result.is_none());
621
622        // This result should not be relied on
623        let obj = unsafe { Allocated::new(ptr::null_mut()) };
624        let result: Option<Retained<NSObject>> = unsafe { msg_send![obj, init] };
625        assert!(result.is_none());
626    }
627
628    #[test]
629    fn test_send_message_super() {
630        let obj = test_utils::custom_subclass_object();
631        let superclass = test_utils::custom_class();
632        unsafe {
633            let _: () = msg_send![&obj, setFoo: 4u32];
634            let foo: u32 = msg_send![super(&obj, superclass), foo];
635            assert_eq!(foo, 4);
636
637            // The subclass is overridden to return foo + 2
638            let foo: u32 = msg_send![&obj, foo];
639            assert_eq!(foo, 6);
640        }
641    }
642
643    #[test]
644    #[cfg_attr(
645        feature = "gnustep-1-7",
646        ignore = "GNUStep deadlocks here for some reason"
647    )]
648    fn test_send_message_class_super() {
649        let cls = test_utils::custom_subclass();
650        let superclass = test_utils::custom_class();
651        unsafe {
652            let foo: u32 = msg_send![super(cls, superclass.metaclass()), classFoo];
653            assert_eq!(foo, 7);
654
655            // The subclass is overridden to return + 2
656            let foo: u32 = msg_send![cls, classFoo];
657            assert_eq!(foo, 9);
658        }
659    }
660}