diff --git a/crates/objc2/src/topics/about_generated/CHANGELOG.md b/crates/objc2/src/topics/about_generated/CHANGELOG.md index 5e079f9c5..9495d36d3 100644 --- a/crates/objc2/src/topics/about_generated/CHANGELOG.md +++ b/crates/objc2/src/topics/about_generated/CHANGELOG.md @@ -64,7 +64,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). no longer need to use `mut` to mutate them, but it also means that they are no longer `Send + Sync`. * **BREAKING**: Renamed the `bytes[_mut]` methods on `NSData` to - `as[_mut]_slice_unchecked`, and made them `unsafe`, since the data can no + `as[_mut]_bytes_unchecked`, and made them `unsafe`, since the data can no longer ensure that it is not mutated while the bytes are in use. * No longer require `Eq + Hash` for `NSDictionary` keys and `NSSet` values, since it was overly restrictive. diff --git a/framework-crates/objc2-core-foundation/src/data.rs b/framework-crates/objc2-core-foundation/src/data.rs new file mode 100644 index 000000000..147184b3d --- /dev/null +++ b/framework-crates/objc2-core-foundation/src/data.rs @@ -0,0 +1,106 @@ +use core::slice; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +use crate::{CFData, CFDataGetBytePtr, CFDataGetLength}; + +impl CFData { + /// Creates a new `CFData` from a byte slice. + #[inline] + #[cfg(feature = "CFBase")] + #[doc(alias = "CFDataCreate")] + pub fn from_bytes(bytes: &[u8]) -> crate::CFRetained { + let len = bytes.len().try_into().expect("buffer too large"); + unsafe { crate::CFDataCreate(None, bytes.as_ptr(), len) }.expect("failed creating CFData") + } + + /// Creates a new `CFData` from a `'static` byte slice. + /// + /// This may be slightly more efficient than [`CFData::from_bytes`], as it + /// may be able to re-use the existing buffer (since we know it won't be + /// deallocated). + #[inline] + #[cfg(feature = "CFBase")] + #[doc(alias = "CFDataCreateWithBytesNoCopy")] + pub fn from_static_bytes(bytes: &'static [u8]) -> crate::CFRetained { + let len = bytes.len().try_into().expect("buffer too large"); + // SAFETY: Same as `CFString::from_static_str`. + unsafe { + crate::CFDataCreateWithBytesNoCopy(None, bytes.as_ptr(), len, crate::kCFAllocatorNull) + } + .expect("failed creating CFData") + } + + /// The number of bytes contained by the `CFData`. + #[inline] + #[doc(alias = "CFDataGetLength")] + pub fn len(&self) -> usize { + unsafe { CFDataGetLength(self) as _ } + } + + /// Whether the `CFData` is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// The underlying bytes in the `CFData`. + /// + /// + /// # Safety + /// + /// The `CFData` must not be mutated for the lifetime of the returned + /// string. Consider using [`to_vec`] instead if this requirement is a bit + /// difficult to uphold. + /// + /// [`to_vec`]: Self::to_vec + #[inline] + #[doc(alias = "CFDataGetBytePtr")] + pub unsafe fn as_bytes_unchecked(&self) -> &[u8] { + let ptr = unsafe { CFDataGetBytePtr(self) }; + if !ptr.is_null() { + // SAFETY: The pointer is valid, and caller ensures that the + // `CFData` is not mutated for the lifetime of it. + // + // Same as + unsafe { slice::from_raw_parts(ptr, self.len()) } + } else { + // The bytes pointer may be null for length zero + &[] + } + } + + /// Copy the contents of the `CFData` into a new [`Vec`]. + #[cfg(feature = "alloc")] + #[doc(alias = "CFDataGetBytePtr")] + pub fn to_vec(&self) -> Vec { + // NOTE: We don't do `Vec::from`, as that will call the allocator + // while the buffer is active, and we don't know if that allocator + // uses a CFMutableData under the hood (though very theoretical). + + let mut vec = Vec::with_capacity(self.len()); + // SAFETY: We've pre-allocated the Vec, so it won't call the allocator + // while the byte slice is alive (and hence it won't ). + vec.extend_from_slice(unsafe { self.as_bytes_unchecked() }); + vec + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn roundtrip() { + let data = CFData::from_bytes(&[1, 2, 3]); + assert_eq!(data.to_vec(), [1, 2, 3]); + } + + #[test] + fn empty() { + let data = CFData::from_bytes(&[]); + assert!(data.is_empty()); + assert_eq!(data.to_vec(), []); + } +} diff --git a/framework-crates/objc2-core-foundation/src/lib.rs b/framework-crates/objc2-core-foundation/src/lib.rs index 8452a0c14..d67c6b06b 100644 --- a/framework-crates/objc2-core-foundation/src/lib.rs +++ b/framework-crates/objc2-core-foundation/src/lib.rs @@ -25,6 +25,8 @@ mod base; #[cfg(feature = "CFBundle")] mod bundle; mod cf_type; +#[cfg(feature = "CFData")] +mod data; #[cfg(feature = "CFDate")] mod date; mod generated; diff --git a/framework-crates/objc2-core-foundation/src/string.rs b/framework-crates/objc2-core-foundation/src/string.rs index 1856ad156..eb2688ed2 100644 --- a/framework-crates/objc2-core-foundation/src/string.rs +++ b/framework-crates/objc2-core-foundation/src/string.rs @@ -91,7 +91,7 @@ impl CFString { // NOTE: This is NOT public, since it's completely broken for differently // encoded strings, see the `as_str_broken` test below. #[allow(dead_code)] - unsafe fn as_str(&self) -> Option<&str> { + unsafe fn as_str_unchecked(&self) -> Option<&str> { let bytes = unsafe { CFStringGetCStringPtr(self, CFStringEncoding::UTF8) }; NonNull::new(bytes as *mut c_char).map(|bytes| { // SAFETY: The pointer is valid for as long as the CFString is not @@ -314,7 +314,7 @@ mod tests { // But `CFStringGetCStringPtr` completely ignores the UTF-8 conversion // we asked it to do, i.e. a huge correctness footgun! - assert_eq!(unsafe { s.as_str() }, Some("e&")); + assert_eq!(unsafe { s.as_str_unchecked() }, Some("e&")); } #[test] diff --git a/framework-crates/objc2-foundation/src/data.rs b/framework-crates/objc2-foundation/src/data.rs index c681b2860..d1da3b848 100644 --- a/framework-crates/objc2-foundation/src/data.rs +++ b/framework-crates/objc2-foundation/src/data.rs @@ -33,6 +33,7 @@ extern_methods!( ); impl NSData { + // TODO: Rename to `from_bytes` to match `CFData::from_bytes`. pub fn with_bytes(bytes: &[u8]) -> Retained { let bytes_ptr = bytes.as_ptr() as *mut c_void; unsafe { Self::initWithBytes_length(Self::alloc(), bytes_ptr, bytes.len()) } @@ -77,7 +78,7 @@ impl NSData { /// difficult to uphold. /// /// [`to_vec`]: Self::to_vec - pub unsafe fn as_slice_unchecked(&self) -> &[u8] { + pub unsafe fn as_bytes_unchecked(&self) -> &[u8] { let ptr = self.bytes_raw(); if !ptr.is_null() { let ptr: *const u8 = ptr.cast(); @@ -103,8 +104,15 @@ impl NSData { /// Copy the contents of the data into a new [`Vec`]. pub fn to_vec(&self) -> Vec { - // SAFETY: The bytes are immediately copied to a new `Vec`. - unsafe { self.as_slice_unchecked() }.to_vec() + // NOTE: We don't do `Vec::from`, as that will call the allocator + // while the buffer is active, and we don't know if that allocator + // uses a CFMutableData under the hood (though very theoretical). + + let mut vec = Vec::with_capacity(self.len()); + // SAFETY: We've pre-allocated the Vec, so it won't call the allocator + // while the byte slice is alive (and hence it won't ). + vec.extend_from_slice(unsafe { self.as_bytes_unchecked() }); + vec } /// Iterate over the bytes of the data. @@ -122,7 +130,7 @@ impl NSMutableData { /// slice is alive. #[doc(alias = "mutableBytes")] #[allow(clippy::mut_from_ref)] - pub unsafe fn as_mut_slice_unchecked(&self) -> &mut [u8] { + pub unsafe fn as_mut_bytes_unchecked(&self) -> &mut [u8] { let ptr = self.mutable_bytes_raw(); // &Cell<[u8]> is safe because the slice length is not actually in the // cell diff --git a/framework-crates/objc2-foundation/src/tests/data.rs b/framework-crates/objc2-foundation/src/tests/data.rs index 6bc39912a..8e78af823 100644 --- a/framework-crates/objc2-foundation/src/tests/data.rs +++ b/framework-crates/objc2-foundation/src/tests/data.rs @@ -24,7 +24,7 @@ fn test_from_vec() { let bytes_ptr = bytes.as_ptr(); let data = NSData::from_vec(bytes); - assert_eq!(unsafe { data.as_slice_unchecked() }.as_ptr(), bytes_ptr); + assert_eq!(unsafe { data.as_bytes_unchecked() }.as_ptr(), bytes_ptr); } #[test] diff --git a/framework-crates/objc2-foundation/src/tests/mutable_data.rs b/framework-crates/objc2-foundation/src/tests/mutable_data.rs index 34c26f093..786a53668 100644 --- a/framework-crates/objc2-foundation/src/tests/mutable_data.rs +++ b/framework-crates/objc2-foundation/src/tests/mutable_data.rs @@ -5,7 +5,7 @@ use crate::{NSData, NSMutableData}; #[test] fn test_bytes_mut() { let data = NSMutableData::with_bytes(&[7, 16]); - unsafe { data.as_mut_slice_unchecked()[0] = 3 }; + unsafe { data.as_mut_bytes_unchecked()[0] = 3 }; assert_eq!(data.to_vec(), [3, 16]); }