diff --git a/crates/objc2/src/__macros/msg_send/mod.rs b/crates/objc2/src/__macros/msg_send/mod.rs index 2f2e02fc2..db54650a6 100644 --- a/crates/objc2/src/__macros/msg_send/mod.rs +++ b/crates/objc2/src/__macros/msg_send/mod.rs @@ -415,11 +415,11 @@ pub use self::retained::*; /// ```ignore /// use objc2_foundation::{NSNumber, NSString, NSURLComponents}; /// -/// let components = unsafe { NSURLComponents::new() }; -/// unsafe { components.setPort(Some(&NSNumber::new_i32(8080))) }; -/// unsafe { components.setHost(Some(&NSString::from_str("example.com"))) }; -/// unsafe { components.setScheme(Some(&NSString::from_str("http"))) }; -/// let string = unsafe { components.string() }; +/// let components = NSURLComponents::new(); +/// components.setPort(Some(&NSNumber::new_i32(8080))); +/// components.setHost(Some(&NSString::from_str("example.com"))); +/// components.setScheme(Some(&NSString::from_str("http"))); +/// let string = components.string(); /// /// assert_eq!(string.unwrap().to_string(), "http://example.com:8080"); /// ``` diff --git a/crates/objc2/src/topics/FRAMEWORKS_CHANGELOG.md b/crates/objc2/src/topics/FRAMEWORKS_CHANGELOG.md index 3f3954c3c..febe6575f 100644 --- a/crates/objc2/src/topics/FRAMEWORKS_CHANGELOG.md +++ b/crates/objc2/src/topics/FRAMEWORKS_CHANGELOG.md @@ -25,6 +25,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `OpenGLES` / `objc2-open-gl-es`. - `XCTest` / `objc2-xc-test`. - `XCUIAutomation` / `objc2-xc-ui-automation`. +* Automatically marked a bunch of functions safe in: + - `Foundation` / `objc2-foundation`. ### Changed * Updated SDK from Xcode 16.3 to 26.0. diff --git a/crates/objc2/src/topics/run_loop.md b/crates/objc2/src/topics/run_loop.md index 023b39da1..0a2602c49 100644 --- a/crates/objc2/src/topics/run_loop.md +++ b/crates/objc2/src/topics/run_loop.md @@ -15,11 +15,11 @@ In non-graphical applications, you get the thread's current `NSRunLoop`, and run use objc2_foundation::{NSDate, NSDefaultRunLoopMode, NSRunLoop}; fn main() { - let run_loop = unsafe { NSRunLoop::currentRunLoop() }; + let run_loop = NSRunLoop::currentRunLoop(); // Set up timers, sources, etc. - let mut date = unsafe { NSDate::now() }; + let mut date = NSDate::now(); // Run for roughly 10 seconds for i in 0..10 { date = unsafe { date.dateByAddingTimeInterval(1.0) }; diff --git a/crates/tests/src/backtrace.rs b/crates/tests/src/backtrace.rs index 809520967..ebd368535 100644 --- a/crates/tests/src/backtrace.rs +++ b/crates/tests/src/backtrace.rs @@ -17,8 +17,8 @@ fn merge_objc_symbols(exc: &NSException) -> Vec { // demangled symbols. let mut demangled_symbols = vec![]; - let nssymbols = unsafe { exc.callStackSymbols() }; - let return_addrs = unsafe { exc.callStackReturnAddresses() }; + let nssymbols = exc.callStackSymbols(); + let return_addrs = exc.callStackReturnAddresses(); for (nssymbol, addr) in nssymbols.iter().zip(return_addrs) { let addr = addr.as_usize() as *mut c_void; diff --git a/crates/tests/src/enumerator.rs b/crates/tests/src/enumerator.rs new file mode 100644 index 000000000..a97e8516f --- /dev/null +++ b/crates/tests/src/enumerator.rs @@ -0,0 +1,11 @@ +use objc2_foundation::{NSEnumerator, NSObject}; + +// We mark `new` safe, but it actually panics. +#[test] +#[cfg_attr(not(feature = "catch-all"), ignore = "aborts the test")] +#[should_panic = "NSInvalidArgumentException"] +fn empty() { + let enumerator = NSEnumerator::::new(); + // *** -[NSEnumerator nextObject]: method sent to an instance (0xcafebabe) of an abstract class. Create a concrete instance! + assert_eq!(enumerator.iter().count(), 0); +} diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs index 70f8a281f..79125c0b7 100644 --- a/crates/tests/src/lib.rs +++ b/crates/tests/src/lib.rs @@ -17,6 +17,8 @@ extern crate std; mod backtrace; #[cfg(test)] mod block; +#[cfg(test)] +mod enumerator; #[cfg(all(test, feature = "exception"))] mod exception; mod rc_test_object; diff --git a/examples/app/default_xcode_app/app_delegate.rs b/examples/app/default_xcode_app/app_delegate.rs index 9c51d2d63..c2d9b0894 100644 --- a/examples/app/default_xcode_app/app_delegate.rs +++ b/examples/app/default_xcode_app/app_delegate.rs @@ -38,7 +38,8 @@ define_class!( let mtm = self.mtm(); let view_controller = ViewController::new(mtm); - let app = unsafe { notification.object() } + let app = notification + .object() .unwrap() .downcast::() .unwrap(); diff --git a/examples/app/hello_world_app.rs b/examples/app/hello_world_app.rs index b1ad0a8b5..37550a414 100644 --- a/examples/app/hello_world_app.rs +++ b/examples/app/hello_world_app.rs @@ -38,7 +38,8 @@ define_class!( fn did_finish_launching(&self, notification: &NSNotification) { let mtm = self.mtm(); - let app = unsafe { notification.object() } + let app = notification + .object() .unwrap() .downcast::() .unwrap(); diff --git a/examples/metal/default_xcode_game/app_delegate.rs b/examples/metal/default_xcode_game/app_delegate.rs index 8149237a2..72eb31f70 100644 --- a/examples/metal/default_xcode_game/app_delegate.rs +++ b/examples/metal/default_xcode_game/app_delegate.rs @@ -38,7 +38,8 @@ define_class!( let mtm = self.mtm(); let view_controller = GameViewController::new(mtm); - let app = unsafe { notification.object() } + let app = notification + .object() .unwrap() .downcast::() .unwrap(); diff --git a/examples/metal/default_xcode_game/renderer.rs b/examples/metal/default_xcode_game/renderer.rs index 88efe7d17..a6cd2a250 100644 --- a/examples/metal/default_xcode_game/renderer.rs +++ b/examples/metal/default_xcode_game/renderer.rs @@ -381,7 +381,7 @@ impl Renderer { let vertex_buffers = unsafe { self.ivars().mesh.vertexBuffers() }; for (i, vertex_buffer) in vertex_buffers.into_iter().enumerate() { - if **vertex_buffer == **unsafe { NSNull::null() } { + if **vertex_buffer == **NSNull::null() { eprintln!("got null vertex_buffer"); continue; } diff --git a/examples/metal/triangle/main.rs b/examples/metal/triangle/main.rs index 93b234362..06dba1aa0 100644 --- a/examples/metal/triangle/main.rs +++ b/examples/metal/triangle/main.rs @@ -195,7 +195,7 @@ define_class!( // compute the scene properties let scene_properties_data = &SceneProperties { - time: unsafe { self.ivars().start_date.timeIntervalSinceNow() } as f32, + time: self.ivars().start_date.timeIntervalSinceNow() as f32, }; // write the scene properties to the vertex shader argument buffer at index 0 let scene_properties_bytes = NonNull::from(scene_properties_data); @@ -280,7 +280,7 @@ impl Delegate { fn new(mtm: MainThreadMarker) -> Retained { let this = Self::alloc(mtm); let this = this.set_ivars(Ivars { - start_date: unsafe { NSDate::now() }, + start_date: NSDate::now(), command_queue: OnceCell::default(), pipeline_state: OnceCell::default(), #[cfg(target_os = "macos")] diff --git a/framework-crates/objc2-foundation/src/geometry.rs b/framework-crates/objc2-foundation/src/geometry.rs index 3b71af9f1..63b298a2f 100644 --- a/framework-crates/objc2-foundation/src/geometry.rs +++ b/framework-crates/objc2-foundation/src/geometry.rs @@ -103,18 +103,18 @@ mod tests { for case in cases { let point_a = NSPoint::new(case.0, case.1); let point_b = NSPoint::new(case.0, case.1); - let actual = unsafe { NSEqualPoints(point_a, point_b) }; + let actual = NSEqualPoints(point_a, point_b); assert_eq!(point_a == point_b, actual); if case.0 >= 0.0 && case.1 >= 0.0 { let size_a = NSSize::new(case.0, case.1); let size_b = NSSize::new(case.0, case.1); - let actual = unsafe { NSEqualSizes(size_a, size_b) }; + let actual = NSEqualSizes(size_a, size_b); assert_eq!(size_a == size_b, actual); let rect_a = NSRect::new(point_a, size_a); let rect_b = NSRect::new(point_b, size_b); - let actual = unsafe { NSEqualRects(rect_a, rect_b) }; + let actual = NSEqualRects(rect_a, rect_b); assert_eq!(rect_a == rect_b, actual); } } diff --git a/framework-crates/objc2-foundation/src/tests/decimal_number.rs b/framework-crates/objc2-foundation/src/tests/decimal_number.rs index 6a063b376..7a35c93f9 100644 --- a/framework-crates/objc2-foundation/src/tests/decimal_number.rs +++ b/framework-crates/objc2-foundation/src/tests/decimal_number.rs @@ -18,6 +18,6 @@ fn test_decimal_encoding() { _mantissa: [0; 8], }; - let obj = unsafe { NSDecimalNumber::initWithDecimal(NSDecimalNumber::alloc(), decimal) }; - assert_eq!(decimal, unsafe { obj.decimalValue() }); + let obj = NSDecimalNumber::initWithDecimal(NSDecimalNumber::alloc(), decimal); + assert_eq!(decimal, obj.decimalValue()); } diff --git a/framework-crates/objc2-foundation/src/tests/lock.rs b/framework-crates/objc2-foundation/src/tests/lock.rs index 9c6fd7e23..a2b783406 100644 --- a/framework-crates/objc2-foundation/src/tests/lock.rs +++ b/framework-crates/objc2-foundation/src/tests/lock.rs @@ -4,6 +4,7 @@ use crate::{NSLock, NSLocking}; #[test] fn lock_unlock() { let lock = NSLock::new(); + // SAFETY: Unlocked from the same thread that locked. unsafe { lock.lock(); assert!(!lock.tryLock()); diff --git a/framework-crates/objc2-foundation/src/tests/measurement.rs b/framework-crates/objc2-foundation/src/tests/measurement.rs index f03bc8d2f..61ea2f8db 100644 --- a/framework-crates/objc2-foundation/src/tests/measurement.rs +++ b/framework-crates/objc2-foundation/src/tests/measurement.rs @@ -4,6 +4,6 @@ use crate::{NSMeasurement, NSUnitMass, NSUnitPower}; #[test] fn create() { - let mass = unsafe { NSMeasurement::::new() }; + let mass = NSMeasurement::::new(); let _power = unsafe { mass.cast_unchecked::() }; } diff --git a/framework-crates/objc2-foundation/src/tests/task.rs b/framework-crates/objc2-foundation/src/tests/task.rs index 0b2cb428c..369875abb 100644 --- a/framework-crates/objc2-foundation/src/tests/task.rs +++ b/framework-crates/objc2-foundation/src/tests/task.rs @@ -12,6 +12,6 @@ fn class_cluster_and_wait_method() { let method = NSTask::class().instance_method(sel); assert!(method.is_none(), "class does not have method"); - let task = unsafe { NSTask::new() }; + let task = NSTask::new(); assert!(task.respondsToSelector(sel), "object has method"); } diff --git a/framework-crates/objc2-foundation/src/url.rs b/framework-crates/objc2-foundation/src/url.rs index 02fd46931..d6ff7293f 100644 --- a/framework-crates/objc2-foundation/src/url.rs +++ b/framework-crates/objc2-foundation/src/url.rs @@ -15,6 +15,7 @@ const PATH_MAX: usize = 1024; /// [`Path`] conversion. impl NSURL { + // FIXME(breaking): Make this private. pub fn from_path( path: &Path, is_directory: bool, @@ -228,7 +229,7 @@ mod tests { fn special_paths() { use crate::{NSData, NSFileManager}; - let manager = unsafe { NSFileManager::defaultManager() }; + let manager = NSFileManager::defaultManager(); let path = Path::new(OsStr::from_bytes(b"\xf8")); // Foundation is broken, needs a different encoding to work. @@ -236,11 +237,8 @@ mod tests { // Create, read and remove file, using different APIs. fs::write(path, "").unwrap(); - assert_eq!( - unsafe { NSData::dataWithContentsOfURL(&url) }, - Some(NSData::new()) - ); - unsafe { manager.removeItemAtURL_error(&url).unwrap() }; + assert_eq!(NSData::dataWithContentsOfURL(&url), Some(NSData::new())); + manager.removeItemAtURL_error(&url).unwrap(); } // Useful when testing HFS+ and non-UTF-8: diff --git a/framework-crates/objc2-foundation/translation-config.toml b/framework-crates/objc2-foundation/translation-config.toml index dcb67fcdb..e55650866 100644 --- a/framework-crates/objc2-foundation/translation-config.toml +++ b/framework-crates/objc2-foundation/translation-config.toml @@ -21,6 +21,12 @@ fn.NSMakeCollectable.skipped = true fn.NSFreeMapTable.skipped = true protocol.NSKeyedUnarchiverDelegate.methods."unarchiver:didDecodeObject:".skipped = true +# TODO(breaking): Memory-management affecting, should be skipped instead. +fn.NSDeallocateObject.unsafe = true +fn.NSIncrementExtraRefCount.unsafe = true +fn.NSDecrementExtraRefCountWasZero.unsafe = true +fn.NSExtraRefCount.unsafe = true + # ns_consumes_self / NS_REPLACES_RECEIVER class.NSObject.methods."awakeAfterUsingCoder:".skipped = true @@ -270,7 +276,7 @@ class.NSMutableURLRequest.counterpart = "ImmutableSuperclass(Foundation.NSURLReq # The implementation can be viewed as-if the enumerator internally retains a # `NSMutableArray`, and removed an element from that on each # iteration. -# class.NSEnumerator.senable = false +# class.NSEnumerator.sendable = false ### ### Bridging @@ -312,197 +318,204 @@ class.NSURL.bridged-to = "CoreFoundation.CFURL.CFURL" ### Safety ### -class.NSArray.methods.init.unsafe = false -class.NSArray.methods.new.unsafe = false -class.NSArray.methods.count.unsafe = false -class.NSArray.methods."objectAtIndex:".unsafe = false -class.NSArray.methods.firstObject.unsafe = false -class.NSArray.methods.lastObject.unsafe = false -class.NSMutableArray.methods.init.unsafe = false -class.NSMutableArray.methods.new.unsafe = false -class.NSMutableArray.methods."initWithCapacity:".unsafe = false -class.NSMutableArray.methods."insertObject:atIndex:".unsafe = false -class.NSMutableArray.methods."removeObjectAtIndex:".unsafe = false -class.NSMutableArray.methods."removeObject:".unsafe = false -class.NSMutableArray.methods."addObject:".unsafe = false -class.NSMutableArray.methods.removeLastObject.unsafe = false -class.NSMutableArray.methods."replaceObjectAtIndex:withObject:".unsafe = false -class.NSMutableArray.methods.removeAllObjects.unsafe = false -# The array may not be mutated while enumerating. +# Foundation's properties are well-behaved. +unsafe-default-safety.property-getters = true +unsafe-default-safety.property-setters = true +# Methods are mostly well-behaved, apart from a few exceptions described below. +unsafe-default-safety.instance-methods = true +unsafe-default-safety.class-methods = true +# Foundation's functions are well-behaved, apart from a few related to memory +# management that we've skipped above. +unsafe-default-safety.functions = true +# Foundation's methods are bounds-checked and throw exceptions. +unsafe-default-safety.not-bounds-affecting = false + +# Collections may not be mutated while enumerating. class.NSArray.methods.objectEnumerator.unsafe = true class.NSArray.methods.reverseObjectEnumerator.unsafe = true - -class.NSString.methods.init.unsafe = false -class.NSString.methods.new.unsafe = false -class.NSString.methods."compare:".unsafe = false -class.NSString.methods."hasPrefix:".unsafe = false -class.NSString.methods."hasSuffix:".unsafe = false -# The other string is non-null, and won't be retained -class.NSString.methods."stringByAppendingString:".unsafe = false -class.NSString.methods."stringByAppendingPathComponent:".unsafe = false -# Assuming `NSStringEncoding` can be made safe -class.NSString.methods."lengthOfBytesUsingEncoding:".unsafe = false -class.NSString.methods.length.unsafe = false -# Safe to call, but the returned pointer may not be safe to use -class.NSString.methods.UTF8String.unsafe = false -class.NSString.methods."initWithString:".unsafe = false -class.NSString.methods."stringWithString:".unsafe = false -class.NSMutableString.methods.init.unsafe = false -class.NSMutableString.methods.new.unsafe = false -class.NSMutableString.methods."initWithCapacity:".unsafe = false -class.NSMutableString.methods."stringWithCapacity:".unsafe = false -class.NSMutableString.methods."initWithString:".unsafe = false -class.NSMutableString.methods."stringWithString:".unsafe = false -class.NSMutableString.methods."appendString:".unsafe = false -class.NSMutableString.methods."setString:".unsafe = false -fn.NSStringFromClass.unsafe = false - -class.NSAttributedString.methods.init.unsafe = false -class.NSAttributedString.methods.new.unsafe = false -class.NSAttributedString.methods."initWithString:".unsafe = false -class.NSAttributedString.methods."initWithAttributedString:".unsafe = false -class.NSAttributedString.methods.string.unsafe = false -class.NSAttributedString.methods.length.unsafe = false -class.NSMutableAttributedString.methods.init.unsafe = false -class.NSMutableAttributedString.methods.new.unsafe = false -class.NSMutableAttributedString.methods."initWithString:".unsafe = false -class.NSMutableAttributedString.methods."initWithAttributedString:".unsafe = false -class.NSMutableAttributedString.methods."setAttributedString:".unsafe = false - -class.NSBundle.methods.mainBundle.unsafe = false -class.NSBundle.methods.infoDictionary.unsafe = false - -class.NSData.methods.init.unsafe = false -class.NSData.methods.new.unsafe = false -class.NSData.methods."initWithData:".unsafe = false -class.NSData.methods."dataWithData:".unsafe = false -class.NSData.methods.length.unsafe = false -class.NSData.methods.bytes.unsafe = false -class.NSMutableData.methods.init.unsafe = false -class.NSMutableData.methods.new.unsafe = false -class.NSMutableData.methods."dataWithData:".unsafe = false -class.NSMutableData.methods."initWithCapacity:".unsafe = false -class.NSMutableData.methods."dataWithCapacity:".unsafe = false -class.NSMutableData.methods."setLength:".unsafe = false -class.NSMutableData.methods.mutableBytes.unsafe = false - -class.NSDictionary.methods.init.unsafe = false -class.NSDictionary.methods.new.unsafe = false -class.NSDictionary.methods.count.unsafe = false -class.NSDictionary.methods."objectForKey:".unsafe = false -class.NSDictionary.methods.allValues.unsafe = false -class.NSDictionary.methods.allKeys.unsafe = false -class.NSMutableDictionary.methods.init.unsafe = false -class.NSMutableDictionary.methods.new.unsafe = false -class.NSMutableDictionary.methods."initWithCapacity:".unsafe = false -class.NSMutableDictionary.methods."removeObjectForKey:".unsafe = false -class.NSMutableDictionary.methods.removeAllObjects.unsafe = false -# The dictionary may not be mutated while enumerating. -class.NSDictionary.methods.keyEnumerator.unsafe = true class.NSDictionary.methods.objectEnumerator.unsafe = true - -# Enumerators are safe to use, we move unsoundness to their creation. +class.NSDictionary.methods.keyEnumerator.unsafe = true +class.NSSet.methods.objectEnumerator.unsafe = true +class.NSCountedSet.methods.objectEnumerator.unsafe = true +class.NSOrderedSet.methods.objectEnumerator.unsafe = true +class.NSOrderedSet.methods.reverseObjectEnumerator.unsafe = true +class.NSMapTable.methods.objectEnumerator.unsafe = true +class.NSMapTable.methods.keyEnumerator.unsafe = true +class.NSHashTable.methods.objectEnumerator.unsafe = true +# Enumerators themselves are safe to use, we move unsoundness to their creation above. class.NSEnumerator.methods.nextObject.unsafe = false class.NSEnumerator.methods.allObjects.unsafe = false -class.NSError.methods.domain.unsafe = false -class.NSError.methods.code.unsafe = false -class.NSError.methods.userInfo.unsafe = false -class.NSError.methods.localizedDescription.unsafe = false - -class.NSException.methods.name.unsafe = false -class.NSException.methods.reason.unsafe = false -class.NSException.methods.userInfo.unsafe = false +# Taking `&AnyClass` is not always safe, but this specific instance is +# fine, since it only converts the class name to NSString. +fn.NSStringFromClass.unsafe = false +# `NSAutoreleasePool` interferes with `objc2::rc::autoreleasepool`, and may +# unexpectedly shorten the lifetime of objects. +class.NSAutoreleasePool.unsafe = true + +# Unarchival may be unsafe if not requiring secure encoding, so let's +# be conservative in what we mark safe for now. +class.NSCoder.unsafe = true +class.NSUnarchiver.unsafe = true +class.NSKeyedUnarchiver.unsafe = true +class.NSPortCoder.unsafe = true +class.NSXPCCoder.unsafe = true +class.NSObject.methods."setVersion:".unsafe = true # On `NSCoderMethods` category. + +# Key-value coding breaks type-safety when writing (maybe?). +class.NSObject.methods."setValue:forKey:".unsafe = true +class.NSObject.methods."setValue:forKeyPath:".unsafe = true +class.NSObject.methods."validateValue:forKey:error:".unsafe = true +class.NSObject.methods."validateValue:forKeyPath:error:".unsafe = true +class.NSObject.methods."setValue:forUndefinedKey:".unsafe = true +class.NSObject.methods."setNilValueForKey:".unsafe = true +class.NSObject.methods."setValuesForKeysWithDictionary:".unsafe = true +class.NSArray.methods."setValue:forKey:".unsafe = true +class.NSMutableDictionary.methods."setValue:forKey:".unsafe = true +class.NSOrderedSet.methods."setValue:forKey:".unsafe = true +class.NSSet.methods."setValue:forKey:".unsafe = true + +# Key-value observing requires that the observer is removed. +class.NSObject.methods."addObserver:forKeyPath:options:context:".unsafe = true +class.NSObject.methods."removeObserver:forKeyPath:context:".unsafe = true +class.NSObject.methods."removeObserver:forKeyPath:".unsafe = true +class.NSArray.methods."addObserver:toObjectsAtIndexes:forKeyPath:options:context:".unsafe = true +class.NSArray.methods."removeObserver:fromObjectsAtIndexes:forKeyPath:context:".unsafe = true +class.NSArray.methods."removeObserver:fromObjectsAtIndexes:forKeyPath:".unsafe = true +class.NSArray.methods."addObserver:forKeyPath:options:context:".unsafe = true +class.NSArray.methods."removeObserver:forKeyPath:context:".unsafe = true +class.NSArray.methods."removeObserver:forKeyPath:".unsafe = true +class.NSOrderedSet.methods."addObserver:forKeyPath:options:context:".unsafe = true +class.NSOrderedSet.methods."removeObserver:forKeyPath:context:".unsafe = true +class.NSOrderedSet.methods."removeObserver:forKeyPath:".unsafe = true +class.NSSet.methods."addObserver:forKeyPath:options:context:".unsafe = true +class.NSSet.methods."removeObserver:forKeyPath:context:".unsafe = true +class.NSSet.methods."removeObserver:forKeyPath:".unsafe = true + +# Breaks type-safety (assumes PLists). +class.NSArray.methods."arrayWithContentsOfFile:".unsafe = true +class.NSArray.methods."arrayWithContentsOfURL:".unsafe = true +class.NSArray.methods."arrayWithContentsOfURL:error:".unsafe = true +class.NSArray.methods."initWithContentsOfFile:".unsafe = true +class.NSArray.methods."initWithContentsOfURL:".unsafe = true +class.NSArray.methods."initWithContentsOfURL:error:".unsafe = true +class.NSArray.methods."writeToFile:atomically:".unsafe = true +class.NSArray.methods."writeToURL:atomically:".unsafe = true +class.NSArray.methods."writeToURL:error:".unsafe = true +class.NSMutableArray.methods."arrayWithContentsOfFile:".unsafe = true +class.NSMutableArray.methods."arrayWithContentsOfURL:".unsafe = true +class.NSMutableArray.methods."initWithContentsOfFile:".unsafe = true +class.NSMutableArray.methods."initWithContentsOfURL:".unsafe = true +class.NSDictionary.methods."dictionaryWithContentsOfFile:".unsafe = true +class.NSDictionary.methods."dictionaryWithContentsOfURL:".unsafe = true +class.NSDictionary.methods."dictionaryWithContentsOfURL:error:".unsafe = true +class.NSDictionary.methods."initWithContentsOfFile:".unsafe = true +class.NSDictionary.methods."initWithContentsOfURL:".unsafe = true +class.NSDictionary.methods."initWithContentsOfURL:error:".unsafe = true +class.NSDictionary.methods."writeToFile:atomically:".unsafe = true +class.NSDictionary.methods."writeToURL:atomically:".unsafe = true +class.NSDictionary.methods."writeToURL:error:".unsafe = true +class.NSMutableDictionary.methods."dictionaryWithContentsOfFile:".unsafe = true +class.NSMutableDictionary.methods."dictionaryWithContentsOfURL:".unsafe = true +class.NSMutableDictionary.methods."initWithContentsOfFile:".unsafe = true +class.NSMutableDictionary.methods."initWithContentsOfURL:".unsafe = true + +# Breaks type-safety by returning an untyped mutable collection, +# which is invariant. +# +# TODO: Detect these cases in the translator. +class.NSObject.methods."mutableArrayValueForKey:".unsafe = true # + KVC constraints +class.NSObject.methods."mutableArrayValueForKeyPath:".unsafe = true # + KVC constraints +class.NSObject.methods."mutableOrderedSetValueForKey:".unsafe = true # + KVC constraints +class.NSObject.methods."mutableOrderedSetValueForKeyPath:".unsafe = true # + KVC constraints +class.NSObject.methods."mutableSetValueForKey:".unsafe = true # + KVC constraints +class.NSObject.methods."mutableSetValueForKeyPath:".unsafe = true # + KVC constraints +class.NSThread.methods.threadDictionary.unsafe = true + +# Breaks type-safety by copying without the copy helper. +class.NSArray.methods."initWithArray:copyItems:".unsafe = true +class.NSDictionary.methods."initWithDictionary:copyItems:".unsafe = true +class.NSSet.methods."initWithSet:copyItems:".unsafe = true +class.NSOrderedSet.methods."orderedSetWithOrderedSet:range:copyItems:".unsafe = true +class.NSOrderedSet.methods."orderedSetWithArray:range:copyItems:".unsafe = true +class.NSOrderedSet.methods."orderedSetWithSet:copyItems:".unsafe = true +class.NSOrderedSet.methods."initWithOrderedSet:copyItems:".unsafe = true +class.NSOrderedSet.methods."initWithOrderedSet:range:copyItems:".unsafe = true +class.NSOrderedSet.methods."initWithArray:copyItems:".unsafe = true +class.NSOrderedSet.methods."initWithArray:range:copyItems:".unsafe = true +class.NSOrderedSet.methods."initWithSet:copyItems:".unsafe = true +class.NSCache.unsafe = true + +# Breaks type-safety by specifying NSCopying instead of the +# correct key (which ends up being the value after copying). +class.NSDictionary.methods."initWithObjects:forKeys:count:".unsafe = true +class.NSDictionary.methods."dictionaryWithObject:forKey:".unsafe = true +class.NSDictionary.methods."dictionaryWithObjects:forKeys:count:".unsafe = true +class.NSDictionary.methods."dictionaryWithObjects:forKeys:".unsafe = true +class.NSDictionary.methods."initWithObjects:forKeys:".unsafe = true +class.NSMutableDictionary.methods."setObject:forKey:".unsafe = true +class.NSMutableDictionary.methods."setObject:forKeyedSubscript:".unsafe = true +class.NSDictionary.methods."sharedKeySetForKeys:".unsafe = true + +# Breaks lifetime safety unless you call `retainArguments`. +class.NSInvocation.unsafe = true + +# Getting it from anywhere else is thus also perilous. +class.NSDistantObjectRequest.methods.invocation.unsafe = true +class.NSInvocationOperation.methods."initWithInvocation:".unsafe = true +class.NSInvocationOperation.methods.invocation.unsafe = true +class.NSTimer.methods."timerWithTimeInterval:invocation:repeats:".unsafe = true +class.NSTimer.methods."scheduledTimerWithTimeInterval:invocation:repeats:".unsafe = true + +# Breaks type-safety, you must check encodings before accessing a value. +# +# Note that `NSNumber` is fine, the conversions it does are all documented. +class.NSValue.unsafe = true +class.NSValue.methods.objCType.unsafe = false +class.NSValue.methods."isEqualToValue:".unsafe = false +class.NSValue.methods."valueWithNonretainedObject:".unsafe = true # Also breaks lifetime safety +class.NSValue.methods.nonretainedObjectValue.unsafe = true # Also breaks lifetime safety + +# NSLock is Send + Sync, but must be unlocked on the same thread that locked it. +protocol.NSLocking.unsafe = true +class.NSLock.unsafe = true +class.NSRecursiveLock.unsafe = true +class.NSConditionLock.unsafe = true +class.NSCondition.unsafe = true class.NSLock.methods.init.unsafe = false class.NSLock.methods.new.unsafe = false class.NSLock.methods.name.unsafe = false class.NSLock.methods."setName:".unsafe = false - -class.NSUUID.methods.UUID.unsafe = false -class.NSUUID.methods.init.unsafe = false -class.NSUUID.methods.new.unsafe = false -class.NSUUID.methods."initWithUUIDString:".unsafe = false -class.NSUUID.methods.UUIDString.unsafe = false - -class.NSThread.methods.init.unsafe = false -class.NSThread.methods.new.unsafe = false -class.NSThread.methods.currentThread.unsafe = false -class.NSThread.methods.mainThread.unsafe = false -class.NSThread.methods.name.unsafe = false -class.NSThread.methods.isMultiThreaded.unsafe = false -class.NSThread.methods.isMainThread.unsafe = false - -class.NSProcessInfo.methods.processInfo.unsafe = false -class.NSProcessInfo.methods.processName.unsafe = false -class.NSProcessInfo.methods.operatingSystemVersion.unsafe = false - -class.NSSet.methods.init.unsafe = false -class.NSSet.methods.new.unsafe = false -class.NSSet.methods.count.unsafe = false -class.NSSet.methods.anyObject.unsafe = false -class.NSSet.methods."member:".unsafe = false -class.NSSet.methods.allObjects.unsafe = false -class.NSSet.methods."containsObject:".unsafe = false -class.NSSet.methods."isSubsetOfSet:".unsafe = false -class.NSSet.methods."intersectsSet:".unsafe = false -class.NSMutableSet.methods.init.unsafe = false -class.NSMutableSet.methods.new.unsafe = false -class.NSMutableSet.methods."initWithCapacity:".unsafe = false -class.NSMutableSet.methods."addObject:".unsafe = false -class.NSMutableSet.methods."removeObject:".unsafe = false -class.NSMutableSet.methods.removeAllObjects.unsafe = false -# The set may not be mutated while enumerating. -class.NSSet.methods.objectEnumerator.unsafe = true - -class.NSValue.methods.objCType.unsafe = false -class.NSValue.methods."isEqualToValue:".unsafe = false - -class.NSNumber.methods."initWithChar:".unsafe = false -class.NSNumber.methods."initWithUnsignedChar:".unsafe = false -class.NSNumber.methods."initWithShort:".unsafe = false -class.NSNumber.methods."initWithUnsignedShort:".unsafe = false -class.NSNumber.methods."initWithInt:".unsafe = false -class.NSNumber.methods."initWithUnsignedInt:".unsafe = false -class.NSNumber.methods."initWithLong:".unsafe = false -class.NSNumber.methods."initWithUnsignedLong:".unsafe = false -class.NSNumber.methods."initWithLongLong:".unsafe = false -class.NSNumber.methods."initWithUnsignedLongLong:".unsafe = false -class.NSNumber.methods."initWithFloat:".unsafe = false -class.NSNumber.methods."initWithDouble:".unsafe = false -class.NSNumber.methods."initWithBool:".unsafe = false -class.NSNumber.methods."initWithInteger:".unsafe = false -class.NSNumber.methods."initWithUnsignedInteger:".unsafe = false -class.NSNumber.methods."numberWithChar:".unsafe = false -class.NSNumber.methods."numberWithUnsignedChar:".unsafe = false -class.NSNumber.methods."numberWithShort:".unsafe = false -class.NSNumber.methods."numberWithUnsignedShort:".unsafe = false -class.NSNumber.methods."numberWithInt:".unsafe = false -class.NSNumber.methods."numberWithUnsignedInt:".unsafe = false -class.NSNumber.methods."numberWithLong:".unsafe = false -class.NSNumber.methods."numberWithUnsignedLong:".unsafe = false -class.NSNumber.methods."numberWithLongLong:".unsafe = false -class.NSNumber.methods."numberWithUnsignedLongLong:".unsafe = false -class.NSNumber.methods."numberWithFloat:".unsafe = false -class.NSNumber.methods."numberWithDouble:".unsafe = false -class.NSNumber.methods."numberWithBool:".unsafe = false -class.NSNumber.methods."numberWithInteger:".unsafe = false -class.NSNumber.methods."numberWithUnsignedInteger:".unsafe = false -class.NSNumber.methods."compare:".unsafe = false -class.NSNumber.methods."isEqualToNumber:".unsafe = false -class.NSNumber.methods.charValue.unsafe = false -class.NSNumber.methods.unsignedCharValue.unsafe = false -class.NSNumber.methods.shortValue.unsafe = false -class.NSNumber.methods.unsignedShortValue.unsafe = false -class.NSNumber.methods.intValue.unsafe = false -class.NSNumber.methods.unsignedIntValue.unsafe = false -class.NSNumber.methods.longValue.unsafe = false -class.NSNumber.methods.unsignedLongValue.unsafe = false -class.NSNumber.methods.longLongValue.unsafe = false -class.NSNumber.methods.unsignedLongLongValue.unsafe = false -class.NSNumber.methods.floatValue.unsafe = false -class.NSNumber.methods.doubleValue.unsafe = false -class.NSNumber.methods.boolValue.unsafe = false -class.NSNumber.methods.integerValue.unsafe = false -class.NSNumber.methods.unsignedIntegerValue.unsafe = false -class.NSNumber.methods.stringValue.unsafe = false +# NSDistributedLock is safe, it works with the filesystem instead. + +# NSPurgeableData is safe, checked by exceptions. + +# Maybe not be thread-safe? +class.NSBundle.methods.allBundles.unsafe = true +class.NSBundle.methods.allFrameworks.unsafe = true + +# Same requirements as libloading +class.NSBundle.methods.load.unsafe = true +class.NSBundle.methods."loadAndReturnError:".unsafe = true +class.NSBundle.methods.unload.unsafe = true + +# Unsure about thread-safety, see: +# https://github.com/madsmtm/objc2/issues/696 +class.NSRunLoop.methods."addTimer:forMode:".unsafe = true +class.NSRunLoop.methods."addPort:forMode:".unsafe = true +class.NSRunLoop.methods."removePort:forMode:".unsafe = true + +# These are safe, they call `CFRunLoopRunInMode` on the current +# thread's run loop (even if called on a runloop that is no the current). +class.NSRunLoop.methods."limitDateForMode:".unsafe = false +class.NSRunLoop.methods.run.unsafe = false +class.NSRunLoop.methods."runMode:beforeDate:".unsafe = false +class.NSRunLoop.methods."runUntilDate:".unsafe = false +class.NSRunLoop.methods."acceptInputForMode:beforeDate:".unsafe = false + +# Wrong to call (though unsure if unsound). +class.NSThread.methods.main.unsafe = true + +# Unclear memory management semantics (and probably a no-op anyhow). +class.NSGarbageCollector.unsafe = true diff --git a/framework-crates/objc2-metal/tests/null_error_handling.rs b/framework-crates/objc2-metal/tests/null_error_handling.rs index 2868637d3..5161b8a23 100644 --- a/framework-crates/objc2-metal/tests/null_error_handling.rs +++ b/framework-crates/objc2-metal/tests/null_error_handling.rs @@ -23,7 +23,7 @@ fn test() { .newBinaryArchiveWithDescriptor_error(&MTLBinaryArchiveDescriptor::new()) .unwrap(); - let metal_lib = unsafe { NSURL::URLWithString(ns_string!("file://missing.metallib")) }.unwrap(); + let metal_lib = NSURL::URLWithString(ns_string!("file://missing.metallib")).unwrap(); let err = binary_archive.serializeToURL_error(&metal_lib).unwrap_err(); if err.domain().to_string() == "__objc2.missingError" { assert_eq!(err.code(), 0); diff --git a/framework-crates/objc2-xc-test/translation-config.toml b/framework-crates/objc2-xc-test/translation-config.toml index a46052e32..241fb9af5 100644 --- a/framework-crates/objc2-xc-test/translation-config.toml +++ b/framework-crates/objc2-xc-test/translation-config.toml @@ -16,3 +16,9 @@ class.XCTMutableIssue.counterpart = "ImmutableSuperclass(XCTest.XCTIssue)" # Uses va_list class.XCTestLog.methods."testLogWithFormat:arguments:".skipped = true + +# Using NSInvocation is perilous, since it doesn't retain its target. +class.XCTestCase.methods."testCaseWithInvocation:".unsafe = true +class.XCTestCase.methods."initWithInvocation:".unsafe = true +class.XCTestCase.methods.invocation.unsafe = true +class.XCTestCase.methods."setInvocation:".unsafe = true diff --git a/generated b/generated index 909ba4522..362013ea1 160000 --- a/generated +++ b/generated @@ -1 +1 @@ -Subproject commit 909ba452275026bdae4e61ef18db7fd94577dde0 +Subproject commit 362013ea1f773a1e41d038e617c547fb8231a7a1