objc2_core_foundation/
array.rs

1#[cfg(feature = "alloc")]
2use alloc::vec::Vec;
3use core::ffi::c_void;
4use core::{borrow::Borrow, mem};
5
6use crate::{kCFTypeArrayCallBacks, CFArray, CFIndex, CFMutableArray, CFRetained, Type};
7
8#[inline]
9fn get_len<T>(objects: &[T]) -> CFIndex {
10    // An allocation in Rust cannot be larger than isize::MAX, so this will
11    // never fail.
12    //
13    // Note that `CFArray::new` documents:
14    // > If this parameter is negative, [...] the behavior is undefined.
15    let len = objects.len();
16    debug_assert!(len < CFIndex::MAX as usize);
17    len as CFIndex
18}
19
20/// Reading the source code:
21/// <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFArray.c#L391>
22/// <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFRuntime.c#L323>
23///
24/// It is clear that creating arrays can only realistically fail if allocating
25/// failed. So we choose to panic/abort in those cases, to roughly match
26/// `Vec`'s behaviour.
27#[cold]
28fn failed_creating_array(len: CFIndex) -> ! {
29    #[cfg(feature = "alloc")]
30    {
31        use alloc::alloc::{handle_alloc_error, Layout};
32        use core::mem::align_of;
33
34        // The layout here is not correct, only best-effort (CFArray adds
35        // extra padding when allocating).
36        let layout = Layout::array::<*const ()>(len as usize).unwrap_or_else(|_| unsafe {
37            Layout::from_size_align_unchecked(0, align_of::<*const ()>())
38        });
39
40        handle_alloc_error(layout)
41    }
42    #[cfg(not(feature = "alloc"))]
43    {
44        panic!("failed allocating CFArray holding {len} elements")
45    }
46}
47
48/// Convenience creation methods.
49impl<T: ?Sized> CFArray<T> {
50    /// Create a new empty `CFArray` capable of holding CoreFoundation
51    /// objects.
52    #[inline]
53    #[doc(alias = "CFArray::new")]
54    pub fn empty() -> CFRetained<Self>
55    where
56        T: Type,
57    {
58        // It may not strictly be necessary to use correct array callbacks
59        // here, though it's good to know that it's correct for use in e.g.
60        // `CFMutableArray::newCopy`.
61        Self::from_objects(&[])
62    }
63
64    /// Create a new `CFArray` with the given CoreFoundation objects.
65    #[inline]
66    #[doc(alias = "CFArray::new")]
67    pub fn from_objects(objects: &[&T]) -> CFRetained<Self>
68    where
69        T: Type,
70    {
71        let len = get_len(objects);
72        // `&T` has the same layout as `*const c_void`, and are non-NULL.
73        let ptr = objects.as_ptr().cast::<*const c_void>().cast_mut();
74
75        // SAFETY: The objects are CFTypes (`T: Type` bound), and the array
76        // callbacks are thus correct.
77        //
78        // The objects are retained internally by the array, so we do not need
79        // to keep them alive ourselves after this.
80        let array = unsafe { CFArray::new(None, ptr, len, &kCFTypeArrayCallBacks) }
81            .unwrap_or_else(|| failed_creating_array(len));
82
83        // SAFETY: The objects came from `T`.
84        unsafe { CFRetained::cast_unchecked::<Self>(array) }
85    }
86
87    /// Alias for easier transition from the `core-foundation` crate.
88    #[inline]
89    #[allow(non_snake_case)]
90    #[deprecated = "renamed to CFArray::from_objects"]
91    pub fn from_CFTypes(objects: &[&T]) -> CFRetained<Self>
92    where
93        T: Type,
94    {
95        Self::from_objects(objects)
96    }
97
98    /// Create a new `CFArray` with the given retained CoreFoundation objects.
99    #[inline]
100    #[doc(alias = "CFArray::new")]
101    pub fn from_retained_objects(objects: &[CFRetained<T>]) -> CFRetained<Self>
102    where
103        T: Type,
104    {
105        let len = get_len(objects);
106        // `CFRetained<T>` has the same layout as `*const c_void`.
107        let ptr = objects.as_ptr().cast::<*const c_void>().cast_mut();
108
109        // SAFETY: Same as in `from_objects`.
110        let array = unsafe { CFArray::new(None, ptr, len, &kCFTypeArrayCallBacks) }
111            .unwrap_or_else(|| failed_creating_array(len));
112
113        // SAFETY: The objects came from `T`.
114        unsafe { CFRetained::cast_unchecked::<Self>(array) }
115    }
116}
117
118/// Convenience creation methods.
119impl<T: ?Sized> CFMutableArray<T> {
120    /// Create a new empty mutable array.
121    #[inline]
122    #[doc(alias = "CFMutableArray::new")]
123    pub fn empty() -> CFRetained<Self>
124    where
125        T: Type,
126    {
127        Self::with_capacity(0)
128    }
129
130    /// Create a new mutable array with the given capacity.
131    #[inline]
132    #[doc(alias = "CFMutableArray::new")]
133    pub fn with_capacity(capacity: usize) -> CFRetained<Self>
134    where
135        T: Type,
136    {
137        // User can pass wrong value here, we must check.
138        let capacity = capacity.try_into().expect("capacity too high");
139
140        // SAFETY: The objects are CFTypes (`T: Type` bound), and the array
141        // callbacks are thus correct.
142        let array = unsafe { CFMutableArray::new(None, capacity, &kCFTypeArrayCallBacks) }
143            .unwrap_or_else(|| failed_creating_array(capacity));
144
145        // SAFETY: The array contains no objects yet, and thus it's safe to
146        // cast them to `T` (as the array callbacks are matching).
147        unsafe { CFRetained::cast_unchecked::<Self>(array) }
148    }
149}
150
151// TODO: Do we want to pass NULL callbacks or `CFArrayEqualCallBack`.
152// impl CFArray<()> {
153//     /// Create a new `CFArray` with the given retained CoreFoundation objects.
154//     pub fn from_usize(objects: &[usize]) -> CFRetained<Self> {
155//         let len = get_len(objects);
156//         // `CFRetained<T>` has the same layout as `*const c_void`.
157//         let ptr: *const c_void = objects.as_ptr().cast();
158//
159//         // SAFETY: Same as in `from_objects`.
160//         let array = unsafe { CFArray::new(None, ptr, len, null) }
161//             .unwrap_or(|| failed_creating_array(len));
162//
163//         // SAFETY: The objects came from `T`.
164//         unsafe { CFRetained::cast_unchecked::<Self>(array) }
165//     }
166// }
167
168/// Direct, unsafe object accessors.
169///
170/// CFArray stores its values directly, and you can get references to said
171/// values data without having to retain it first - but only if the array
172/// isn't mutated while doing so - otherwise, we might end up accessing a
173/// deallocated object.
174impl<T: ?Sized> CFArray<T> {
175    /// Get a direct reference to one of the array's objects.
176    ///
177    /// Consider using the [`get`](Self::get) method instead, unless you're
178    /// seeing performance issues from the retaining.
179    ///
180    /// # Safety
181    ///
182    /// - The index must not be negative, and must be in bounds of the array.
183    /// - The array must not be mutated while the returned reference is live.
184    #[inline]
185    #[doc(alias = "CFArrayGetValueAtIndex")]
186    pub unsafe fn get_unchecked(&self, index: CFIndex) -> &T
187    where
188        T: Type + Sized,
189    {
190        // SAFETY: Caller ensures that `index` is in bounds.
191        let ptr = unsafe { self.as_opaque().value_at_index(index) };
192        // SAFETY: The array's values are of type `T`, and the objects are
193        // CoreFoundation types (and thus cannot be NULL).
194        //
195        // Caller ensures that the array isn't mutated for the lifetime of the
196        // reference.
197        unsafe { &*ptr.cast::<T>() }
198    }
199
200    /// A vector containing direct references to the array's objects.
201    ///
202    /// Consider using the [`to_vec`](Self::to_vec) method instead, unless
203    /// you're seeing performance issues from the retaining.
204    ///
205    /// # Safety
206    ///
207    /// The array must not be mutated while the returned references are alive.
208    #[cfg(feature = "alloc")]
209    #[doc(alias = "CFArrayGetValues")]
210    pub unsafe fn to_vec_unchecked(&self) -> Vec<&T>
211    where
212        T: Type,
213    {
214        let len = self.len();
215        let range = crate::CFRange {
216            location: 0,
217            // Fine to cast, it came from CFIndex
218            length: len as CFIndex,
219        };
220        let mut vec = Vec::<&T>::with_capacity(len);
221
222        // `&T` has the same layout as `*const c_void`.
223        let ptr = vec.as_mut_ptr().cast::<*const c_void>();
224        // SAFETY: The range is in bounds
225        unsafe { self.as_opaque().values(range, ptr) };
226        // SAFETY: Just initialized the Vec above.
227        unsafe { vec.set_len(len) };
228
229        vec
230    }
231
232    /// Iterate over the array without touching the elements.
233    ///
234    /// Consider using the [`iter`](Self::iter) method instead, unless you're
235    /// seeing performance issues from the retaining.
236    ///
237    /// # Safety
238    ///
239    /// The array must not be mutated for the lifetime of the iterator or for
240    /// the lifetime of the elements the iterator returns.
241    #[inline]
242    pub unsafe fn iter_unchecked(&self) -> CFArrayIterUnchecked<'_, T>
243    where
244        T: Type,
245    {
246        CFArrayIterUnchecked {
247            array: self,
248            index: 0,
249            len: self.len() as CFIndex,
250        }
251    }
252}
253
254/// Various accessor methods.
255impl<T: ?Sized> CFArray<T> {
256    /// Convert to the opaque/untyped variant.
257    pub fn as_opaque(&self) -> &CFArray {
258        // SAFETY: The array stores objects behind a reference, and can all be
259        // represented by `crate::opaque::Opaque`.
260        unsafe { mem::transmute::<&CFArray<T>, &CFArray>(self) }
261    }
262
263    /// The amount of elements in the array.
264    #[inline]
265    #[doc(alias = "CFArrayGetCount")]
266    pub fn len(&self) -> usize {
267        // Fine to cast here, the count is never negative.
268        self.as_opaque().count() as _
269    }
270
271    /// Whether the array is empty or not.
272    #[inline]
273    pub fn is_empty(&self) -> bool {
274        self.len() == 0
275    }
276
277    /// Retrieve the object at the given index.
278    ///
279    /// Returns `None` if the index was out of bounds.
280    #[doc(alias = "CFArrayGetValueAtIndex")]
281    pub fn get(&self, index: usize) -> Option<CFRetained<T>>
282    where
283        T: Type + Sized,
284    {
285        if index < self.len() {
286            // Index is `usize` and just compared below the length (which is
287            // at max `CFIndex::MAX`), so a cast is safe here.
288            let index = index as CFIndex;
289            // SAFETY:
290            // - Just checked that the index is in bounds.
291            // - We retain the value right away, so that the reference is not
292            //   used while the array is mutated.
293            //
294            // Note that this is _technically_ wrong; the user _could_ have
295            // implemented a `retain` method that mutates the array. We're
296            // going to rule this out though, as that's basically never going
297            // to happen, and will make a lot of other things unsound too.
298            Some(unsafe { self.get_unchecked(index) }.retain())
299        } else {
300            None
301        }
302    }
303
304    /// Convert the array to a `Vec` of the array's objects.
305    #[cfg(feature = "alloc")]
306    #[doc(alias = "CFArrayGetValues")]
307    pub fn to_vec(&self) -> Vec<CFRetained<T>>
308    where
309        T: Type + Sized,
310    {
311        // SAFETY: We retain the elements below, so we know that the array
312        // isn't mutated while the references are alive.
313        let vec = unsafe { self.to_vec_unchecked() };
314        vec.into_iter().map(T::retain).collect()
315    }
316
317    /// Iterate over the array's elements.
318    #[inline]
319    pub fn iter(&self) -> CFArrayIter<'_, T> {
320        CFArrayIter {
321            array: self,
322            index: 0,
323        }
324    }
325}
326
327/// Various accessor methods.
328impl<T: ?Sized> CFMutableArray<T> {
329    /// Convert to the opaque/untyped variant.
330    pub fn as_opaque(&self) -> &CFMutableArray {
331        // SAFETY: Same as `CFArray::as_opaque`.
332        unsafe { mem::transmute::<&CFMutableArray<T>, &CFMutableArray>(self) }
333    }
334}
335
336/// Convenience mutation methods.
337impl<T> CFMutableArray<T> {
338    /// Push an object to the end of the array.
339    #[inline]
340    #[doc(alias = "CFArrayAppendValue")]
341    pub fn append(&self, obj: &T) {
342        let ptr: *const T = obj;
343        let ptr: *const c_void = ptr.cast();
344        // SAFETY: The pointer is valid.
345        unsafe { CFMutableArray::append_value(Some(self.as_opaque()), ptr) }
346    }
347
348    /// Insert an object into the array at the given index.
349    ///
350    /// # Panics
351    ///
352    /// Panics if the index is out of bounds.
353    #[doc(alias = "CFArrayInsertValueAtIndex")]
354    pub fn insert(&self, index: usize, obj: &T) {
355        // TODO: Replace this check with catching the thrown NSRangeException
356        let len = self.len();
357        if index <= len {
358            let ptr: *const T = obj;
359            let ptr: *const c_void = ptr.cast();
360            // SAFETY: The pointer is valid, and just checked that the index
361            // is in bounds.
362            unsafe {
363                CFMutableArray::insert_value_at_index(Some(self.as_opaque()), index as CFIndex, ptr)
364            }
365        } else {
366            panic!(
367                "insertion index (is {}) should be <= len (is {})",
368                index, len
369            );
370        }
371    }
372}
373
374/// An iterator over retained objects of an array.
375#[derive(Debug)]
376pub struct CFArrayIter<'a, T: ?Sized + 'a> {
377    array: &'a CFArray<T>,
378    index: usize,
379}
380
381impl<T: Type> Iterator for CFArrayIter<'_, T> {
382    type Item = CFRetained<T>;
383
384    fn next(&mut self) -> Option<CFRetained<T>> {
385        // We _must_ re-check the length on every loop iteration, since the
386        // array could have come from `CFMutableArray` and have been mutated
387        // while we're iterating.
388        let value = self.array.get(self.index)?;
389        self.index += 1;
390        Some(value)
391    }
392
393    fn size_hint(&self) -> (usize, Option<usize>) {
394        let len = self.array.len().saturating_sub(self.index);
395        (len, Some(len))
396    }
397}
398
399impl<T: Type> ExactSizeIterator for CFArrayIter<'_, T> {}
400
401// TODO:
402// impl<T: Type> DoubleEndedIterator for CFArrayIter<'_, T> { ... }
403
404// Fused unless someone mutates the array, so we won't guarantee that (for now).
405// impl<T: Type> FusedIterator for CFArrayIter<'_, T> {}
406
407/// A retained iterator over the items of an array.
408#[derive(Debug)]
409pub struct CFArrayIntoIter<T: ?Sized> {
410    array: CFRetained<CFArray<T>>,
411    index: usize,
412}
413
414impl<T: Type> Iterator for CFArrayIntoIter<T> {
415    type Item = CFRetained<T>;
416
417    fn next(&mut self) -> Option<CFRetained<T>> {
418        // Same as `CFArrayIter::next`.
419        let value = self.array.get(self.index)?;
420        self.index += 1;
421        Some(value)
422    }
423
424    fn size_hint(&self) -> (usize, Option<usize>) {
425        let len = self.array.len().saturating_sub(self.index);
426        (len, Some(len))
427    }
428}
429
430impl<T: Type> ExactSizeIterator for CFArrayIntoIter<T> {}
431
432impl<'a, T: Type> IntoIterator for &'a CFArray<T> {
433    type Item = CFRetained<T>;
434    type IntoIter = CFArrayIter<'a, T>;
435
436    #[inline]
437    fn into_iter(self) -> Self::IntoIter {
438        self.iter()
439    }
440}
441
442impl<'a, T: Type> IntoIterator for &'a CFMutableArray<T> {
443    type Item = CFRetained<T>;
444    type IntoIter = CFArrayIter<'a, T>;
445
446    #[inline]
447    fn into_iter(self) -> Self::IntoIter {
448        self.iter()
449    }
450}
451
452impl<T: Type> IntoIterator for CFRetained<CFArray<T>> {
453    type Item = CFRetained<T>;
454    type IntoIter = CFArrayIntoIter<T>;
455
456    #[inline]
457    fn into_iter(self) -> Self::IntoIter {
458        CFArrayIntoIter {
459            array: self,
460            index: 0,
461        }
462    }
463}
464
465impl<T: Type> IntoIterator for CFRetained<CFMutableArray<T>> {
466    type Item = CFRetained<T>;
467    type IntoIter = CFArrayIntoIter<T>;
468
469    #[inline]
470    fn into_iter(self) -> Self::IntoIter {
471        // SAFETY: Upcasting `CFMutableArray<T>` to `CFArray<T>`.
472        let array = unsafe { CFRetained::cast_unchecked::<CFArray<T>>(self) };
473        CFArrayIntoIter { array, index: 0 }
474    }
475}
476
477/// An iterator over raw items of an array.
478///
479/// # Safety
480///
481/// The array must not be mutated while this is alive.
482#[derive(Debug)]
483pub struct CFArrayIterUnchecked<'a, T: ?Sized + 'a> {
484    array: &'a CFArray<T>,
485    index: CFIndex,
486    len: CFIndex,
487}
488
489impl<'a, T: Type> Iterator for CFArrayIterUnchecked<'a, T> {
490    type Item = &'a T;
491
492    #[inline]
493    fn next(&mut self) -> Option<&'a T> {
494        debug_assert_eq!(
495            self.array.len(),
496            self.len as usize,
497            "array was mutated while iterating"
498        );
499        if self.index < self.len {
500            // SAFETY:
501            // - That the array isn't mutated while iterating is upheld by the
502            //   caller of `CFArray::iter_unchecked`.
503            // - Index in bounds is ensured by the check above (which uses a
504            //   pre-computed length, and thus also assumes that the array
505            //   isn't mutated while iterating).
506            let value = unsafe { self.array.get_unchecked(self.index) };
507            self.index += 1;
508            Some(value)
509        } else {
510            None
511        }
512    }
513
514    #[inline]
515    fn size_hint(&self) -> (usize, Option<usize>) {
516        let len = (self.len - self.index) as usize;
517        (len, Some(len))
518    }
519}
520
521impl<T: Type> ExactSizeIterator for CFArrayIterUnchecked<'_, T> {}
522
523// Allow easy conversion from `&CFArray<T>` to `&CFArray`.
524// Requires `T: Type` because of reflexive impl in `cf_type!`.
525impl<T: ?Sized + Type> AsRef<CFArray> for CFArray<T> {
526    fn as_ref(&self) -> &CFArray {
527        self.as_opaque()
528    }
529}
530impl<T: ?Sized + Type> AsRef<CFMutableArray> for CFMutableArray<T> {
531    fn as_ref(&self) -> &CFMutableArray {
532        self.as_opaque()
533    }
534}
535
536// `Eq`, `Ord` and `Hash` have the same semantics.
537impl<T: ?Sized + Type> Borrow<CFArray> for CFArray<T> {
538    fn borrow(&self) -> &CFArray {
539        self.as_opaque()
540    }
541}
542impl<T: ?Sized + Type> Borrow<CFMutableArray> for CFMutableArray<T> {
543    fn borrow(&self) -> &CFMutableArray {
544        self.as_opaque()
545    }
546}
547
548#[cfg(test)]
549mod tests {
550    use super::*;
551    #[cfg(feature = "CFString")]
552    use crate::CFString;
553    use core::ptr::null;
554
555    #[test]
556    fn array_with_invalid_pointers() {
557        // without_provenance
558        let ptr = [0 as _, 1 as _, 2 as _, 3 as _, usize::MAX as _].as_mut_ptr();
559        let array = unsafe { CFArray::new(None, ptr, 1, null()) }.unwrap();
560        let value = unsafe { array.value_at_index(0) };
561        assert!(value.is_null());
562    }
563
564    #[test]
565    #[should_panic]
566    #[ignore = "aborts (as expected)"]
567    fn object_array_cannot_contain_null() {
568        let ptr = [null()].as_mut_ptr();
569        let _array = unsafe { CFArray::new(None, ptr, 1, &kCFTypeArrayCallBacks) };
570    }
571
572    #[test]
573    #[cfg(feature = "CFString")]
574    fn correct_retain_count() {
575        let objects = [
576            CFString::from_str("some long string that doesn't get small-string optimized"),
577            CFString::from_str("another long string that doesn't get small-string optimized"),
578        ];
579        let array = CFArray::from_retained_objects(&objects);
580
581        // Creating array retains elements.
582        assert_eq!(array.retain_count(), 1);
583        assert_eq!(unsafe { array.get_unchecked(0) }.retain_count(), 2);
584        assert_eq!(unsafe { array.get_unchecked(1) }.retain_count(), 2);
585
586        drop(objects);
587        assert_eq!(unsafe { array.get_unchecked(0) }.retain_count(), 1);
588        assert_eq!(unsafe { array.get_unchecked(1) }.retain_count(), 1);
589
590        // Retaining array doesn't affect retain count of elements.
591        let _array2 = array.retain();
592        assert_eq!(unsafe { array.get_unchecked(0) }.retain_count(), 1);
593        assert_eq!(unsafe { array.get_unchecked(1) }.retain_count(), 1);
594
595        // Using retaining API changes retain count.
596        assert_eq!(array.get(0).unwrap().retain_count(), 2);
597    }
598
599    #[test]
600    #[cfg(feature = "CFString")]
601    fn iter() {
602        use alloc::vec::Vec;
603
604        let s1 = CFString::from_str("a");
605        let s2 = CFString::from_str("b");
606        let array = CFArray::from_objects(&[&*s1, &*s2, &*s1]);
607
608        assert_eq!(
609            array.iter().collect::<Vec<_>>(),
610            [s1.clone(), s2.clone(), s1.clone()]
611        );
612
613        assert_eq!(
614            unsafe { array.iter_unchecked() }.collect::<Vec<_>>(),
615            [&*s1, &*s2, &*s1]
616        );
617    }
618
619    #[test]
620    #[cfg(feature = "CFString")]
621    fn iter_fused() {
622        let s1 = CFString::from_str("a");
623        let s2 = CFString::from_str("b");
624        let array = CFArray::from_objects(&[&*s1, &*s2]);
625
626        let mut iter = array.iter();
627        assert_eq!(iter.next(), Some(s1.clone()));
628        assert_eq!(iter.next(), Some(s2.clone()));
629        assert_eq!(iter.next(), None);
630        assert_eq!(iter.next(), None);
631        assert_eq!(iter.next(), None);
632        assert_eq!(iter.next(), None);
633        assert_eq!(iter.next(), None);
634        assert_eq!(iter.next(), None);
635        assert_eq!(iter.next(), None);
636
637        let mut iter = unsafe { array.iter_unchecked() };
638        assert_eq!(iter.next(), Some(&*s1));
639        assert_eq!(iter.next(), Some(&*s2));
640        assert_eq!(iter.next(), None);
641        assert_eq!(iter.next(), None);
642        assert_eq!(iter.next(), None);
643        assert_eq!(iter.next(), None);
644        assert_eq!(iter.next(), None);
645        assert_eq!(iter.next(), None);
646        assert_eq!(iter.next(), None);
647    }
648
649    #[test]
650    #[cfg(feature = "CFString")]
651    fn mutate() {
652        let array = CFMutableArray::<CFString>::with_capacity(10);
653        array.insert(0, &CFString::from_str("a"));
654        array.append(&CFString::from_str("c"));
655        array.insert(1, &CFString::from_str("b"));
656        assert_eq!(
657            array.to_vec(),
658            [
659                CFString::from_str("a"),
660                CFString::from_str("b"),
661                CFString::from_str("c"),
662            ]
663        );
664    }
665
666    #[test]
667    #[cfg(feature = "CFString")]
668    #[cfg_attr(
669        not(debug_assertions),
670        ignore = "not detected when debug assertions are off"
671    )]
672    #[should_panic = "array was mutated while iterating"]
673    fn mutate_while_iter_unchecked() {
674        let array = CFMutableArray::<CFString>::with_capacity(10);
675        assert_eq!(array.len(), 0);
676
677        let mut iter = unsafe { array.iter_unchecked() };
678        array.append(&CFString::from_str("a"));
679        // Should panic
680        let _ = iter.next();
681    }
682}