Skip to content

Commit 82f6605

Browse files
committed
Fix enumeration/iteration in icrate
1 parent de30882 commit 82f6605

39 files changed

+4589
-380
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/header-translator/src/data/Foundation.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,28 @@ data! {
1212
unsafe -removeAllObjects;
1313
}
1414

15+
// SAFETY: `NSEnumerator` and subclasses are safe as mutable because even
16+
// though the items it contains are not mutable, the enumerator itself is
17+
// (and it is important that the methods below are marked `&mut` as well).
18+
//
19+
// However, instances of this are only safe for others to create if
20+
// they're ready to pass ownership to the enumerator, or if they somehow
21+
// add a lifetime parameter (to prevent the original collection from
22+
// being modified).
23+
//
24+
// So e.g. `Id<NSMutableArray<T>> -> Id<NSEnumerator<T>>` is safe, as is
25+
// `&Id<NSArray<T: IsCloneable>> -> Id<NSEnumerator<T>>`, and so is
26+
// `&'a NSArray<T: IsCloneable> -> Id<NSEnumerator<T>> + 'a`.
27+
class NSEnumerator: Mutable {
28+
// SAFETY: This removes the object from the internal collection, so it
29+
// may safely return `Id<T>`.
30+
unsafe -nextObject;
31+
// SAFETY: The objects are removed from the internal collection and as
32+
// such are safe to give ownership over.
33+
unsafe -allObjects;
34+
}
35+
class NSDirectoryEnumerator: Mutable {}
36+
1537
class NSString: ImmutableWithMutableSubclass<Foundation::NSMutableString> {
1638
unsafe -init;
1739
unsafe -compare;

crates/header-translator/translation-config.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,10 @@ skipped = true
574574
[struct.NSDecimal]
575575
skipped = true
576576

577+
# Uses `c_ulong` which means we need to specify the encoding manually.
578+
[struct.NSFastEnumerationState]
579+
skipped = true
580+
577581
# Uses stuff from core Darwin libraries which we have not yet mapped
578582
[class.NSAppleEventDescriptor.methods]
579583
descriptorWithDescriptorType_bytes_length = { skipped = true }
@@ -1551,6 +1555,8 @@ definition-skipped = true
15511555
definition-skipped = true
15521556
[class.NSMutableOrderedSet]
15531557
definition-skipped = true
1558+
[class.NSEnumerator]
1559+
definition-skipped = true
15541560

15551561
# These protocol impls would return the wrong types
15561562
[class.NSSimpleCString]

crates/icrate/CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,46 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4545
* **BREAKING**: Renamed `NSMutableCopying::mutable_copy` to `::mutableCopy`.
4646
* **BREAKING**: The default value for `NSUUID` was changed from a nil UUID to
4747
a new random UUID.
48+
* **BREAKING**: Changed how iteration works.
49+
50+
Instead of the single `NSFastEnumerator`, we now have concrete types
51+
`array::Iter`, `array::IterMut`, `array::IterRetained` and
52+
`array::IntoIter`, which allows iterating over `NSArray` in different ways.
53+
54+
Combined with proper `IntoIterator` implementations for collection types,
55+
you can now do:
56+
```rust
57+
let mut array: Id<NSMutableArray<T>> = ...;
58+
59+
for item in &array {
60+
// item: &T
61+
}
62+
63+
// If T: IsMutable
64+
for item in &mut array {
65+
// item: &mut T
66+
}
67+
68+
// If T: IsIdCloneable
69+
for item in array.iter_retained() {
70+
// item: Id<T>
71+
}
72+
73+
for item in array {
74+
// item: Id<T>
75+
}
76+
```
77+
78+
(similar functionality exist for `NSSet` and `NSDictionary`).
79+
* **BREAKING**: Renamed `NSDictionary` methods:
80+
- `keys` -> `keys_vec`.
81+
- `values` -> `values_vec`.
82+
- `values_mut` -> `values_vec_mut`.
83+
- `keys_and_objects` -> `to_vecs`.
84+
- `iter_keys` -> `keys`.
85+
- `iter_values` -> `values`.
86+
* **BREAKING**: `NSDictionary::keys_retained` and
87+
`NSDictionary::values_retained` now return an iterator instead.
4888

4989
### Removed
5090
* **BREAKING**: Removed various redundant `NSProxy` methods.
@@ -56,6 +96,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5696
* **BREAKING**: Removed a few `init` methods on subclasses that were declared
5797
on categories on their superclass. These should be re-added at some point.
5898

99+
### Fixed
100+
* Soundness issues with enumeration / iteration over collection types.
101+
59102

60103
## icrate 0.0.2 - 2023-02-07
61104

crates/icrate/src/Foundation/additions/array.rs

Lines changed: 95 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1+
//! Utilities for the `NSArray` and `NSMutableArray` classes.
12
#![cfg(feature = "Foundation_NSArray")]
23
use alloc::vec::Vec;
34
use core::fmt;
45
use core::mem;
56
use core::ops::{Index, IndexMut, Range};
67
use core::panic::{RefUnwindSafe, UnwindSafe};
78

8-
use objc2::msg_send;
99
use objc2::mutability::{IsMutable, IsRetainable};
1010

11+
use super::iter;
1112
use super::util;
1213
use crate::common::*;
1314
#[cfg(feature = "Foundation_NSMutableArray")]
@@ -220,15 +221,6 @@ extern_methods!(
220221
);
221222

222223
impl<T: Message> NSArray<T> {
223-
#[doc(alias = "objectEnumerator")]
224-
#[cfg(feature = "Foundation_NSEnumerator")]
225-
pub fn iter(&self) -> Foundation::NSEnumerator2<'_, T> {
226-
unsafe {
227-
let result: *mut Object = msg_send![self, objectEnumerator];
228-
Foundation::NSEnumerator2::from_ptr(result)
229-
}
230-
}
231-
232224
unsafe fn objects_in_range_unchecked(&self, range: Range<usize>) -> Vec<&T> {
233225
let range = Foundation::NSRange::from(range);
234226
let mut vec: Vec<NonNull<T>> = Vec::with_capacity(range.length);
@@ -340,33 +332,109 @@ impl<T: Message> NSMutableArray<T> {
340332
}
341333
}
342334

343-
unsafe impl<T: Message> Foundation::NSFastEnumeration2 for NSArray<T> {
335+
impl<T: Message> NSArray<T> {
336+
#[doc(alias = "objectEnumerator")]
337+
#[inline]
338+
pub fn iter(&self) -> Iter<'_, T> {
339+
Iter(super::iter::Iter::new(self))
340+
}
341+
342+
#[doc(alias = "objectEnumerator")]
343+
#[inline]
344+
pub fn iter_mut(&mut self) -> IterMut<'_, T>
345+
where
346+
T: IsMutable,
347+
{
348+
IterMut(super::iter::IterMut::new(self))
349+
}
350+
351+
#[doc(alias = "objectEnumerator")]
352+
#[inline]
353+
pub fn iter_retained(&self) -> IterRetained<'_, T>
354+
where
355+
T: IsIdCloneable,
356+
{
357+
IterRetained(super::iter::IterRetained::new(self))
358+
}
359+
}
360+
361+
unsafe impl<T: Message> iter::FastEnumerationHelper for NSArray<T> {
344362
type Item = T;
363+
364+
#[inline]
365+
fn maybe_len(&self) -> Option<usize> {
366+
Some(self.len())
367+
}
345368
}
346369

347370
#[cfg(feature = "Foundation_NSMutableArray")]
348-
unsafe impl<T: Message> Foundation::NSFastEnumeration2 for NSMutableArray<T> {
371+
unsafe impl<T: Message> iter::FastEnumerationHelper for NSMutableArray<T> {
349372
type Item = T;
373+
374+
#[inline]
375+
fn maybe_len(&self) -> Option<usize> {
376+
Some(self.len())
377+
}
350378
}
351379

352-
impl<'a, T: Message> IntoIterator for &'a NSArray<T> {
353-
type Item = &'a T;
354-
type IntoIter = Foundation::NSFastEnumerator2<'a, NSArray<T>>;
380+
/// An iterator over the items of a `NSArray`.
381+
#[derive(Debug)]
382+
pub struct Iter<'a, T: Message>(iter::Iter<'a, NSArray<T>>);
355383

356-
fn into_iter(self) -> Self::IntoIter {
357-
use Foundation::NSFastEnumeration2;
358-
self.iter_fast()
359-
}
384+
__impl_iter! {
385+
impl<'a, T: Message> Iterator<Item = &'a T> for Iter<'a, T> { ... }
360386
}
361387

362-
#[cfg(feature = "Foundation_NSMutableArray")]
363-
impl<'a, T: Message> IntoIterator for &'a NSMutableArray<T> {
364-
type Item = &'a T;
365-
type IntoIter = Foundation::NSFastEnumerator2<'a, NSMutableArray<T>>;
388+
/// A mutable iterator over the items of a `NSArray`.
389+
#[derive(Debug)]
390+
pub struct IterMut<'a, T: Message>(iter::IterMut<'a, NSArray<T>>);
391+
392+
__impl_iter! {
393+
impl<'a, T: IsMutable> Iterator<Item = &'a mut T> for IterMut<'a, T> { ... }
394+
}
395+
396+
/// An iterator that retains the items of a `NSArray`.
397+
#[derive(Debug)]
398+
pub struct IterRetained<'a, T: Message>(iter::IterRetained<'a, NSArray<T>>);
399+
400+
__impl_iter! {
401+
impl<'a, T: IsIdCloneable> Iterator<Item = Id<T>> for IterRetained<'a, T> { ... }
402+
}
403+
404+
/// A consuming iterator over the items of a `NSArray`.
405+
#[derive(Debug)]
406+
pub struct IntoIter<T: Message>(iter::IntoIter<NSArray<T>>);
407+
408+
__impl_iter! {
409+
impl<'a, T: Message> Iterator<Item = Id<T>> for IntoIter<T> { ... }
410+
}
411+
412+
__impl_into_iter! {
413+
impl<T: Message> IntoIterator for &NSArray<T> {
414+
type IntoIter = Iter<'_, T>;
415+
}
416+
417+
#[cfg(feature = "Foundation_NSMutableArray")]
418+
impl<T: Message> IntoIterator for &NSMutableArray<T> {
419+
type IntoIter = Iter<'_, T>;
420+
}
421+
422+
impl<T: IsMutable> IntoIterator for &mut NSArray<T> {
423+
type IntoIter = IterMut<'_, T>;
424+
}
425+
426+
#[cfg(feature = "Foundation_NSMutableArray")]
427+
impl<T: IsMutable> IntoIterator for &mut NSMutableArray<T> {
428+
type IntoIter = IterMut<'_, T>;
429+
}
430+
431+
impl<T: IsIdCloneable> IntoIterator for Id<NSArray<T>> {
432+
type IntoIter = IntoIter<T>;
433+
}
366434

367-
fn into_iter(self) -> Self::IntoIter {
368-
use Foundation::NSFastEnumeration2;
369-
self.iter_fast()
435+
#[cfg(feature = "Foundation_NSMutableArray")]
436+
impl<T: Message> IntoIterator for Id<NSMutableArray<T>> {
437+
type IntoIter = IntoIter<T>;
370438
}
371439
}
372440

@@ -403,8 +471,7 @@ impl<T: IsMutable> IndexMut<usize> for NSMutableArray<T> {
403471
impl<T: fmt::Debug + Message> fmt::Debug for NSArray<T> {
404472
#[inline]
405473
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406-
use Foundation::NSFastEnumeration2;
407-
f.debug_list().entries(self.iter_fast()).finish()
474+
f.debug_list().entries(self).finish()
408475
}
409476
}
410477

0 commit comments

Comments
 (0)