Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unsafe Swift

Unsafe Swift

Realm

May 07, 2015
Tweet

More Decks by Realm

Other Decks in Programming

Transcript

  1. Why Safety? • Memory safety • The Billion Dollar Mistake

    • Productivity • isa oops • Undefined Behavior • Why retainCount was removed
  2. Bridging • In the C# world, we’ve been down that

    road before. • In theory unsafe{} blocks are no different than using C • In reality, it vastly limits the scope of potentially memory-unsafe operations. • When there are three functions in the whole project to audit, everyone says OK. • When there are 300 files to audit, everyone throws up their hands and says “we don’t have time”. • Definitely safe code is very distinct from potentially unsafe code • Calling “unsafe” code from Swift makes operations more explicit, easier to reason about
  3. If you’re unlucky… - (void) doBadThing { int *a =

    malloc(1); *a = 5; [self printValue:a]; } - (void) printValue:(int *)a { NSLog(@"Value is %d", *a); } 05 00 00 00 1FC0 1FC4 No crash… yet. Just corrupted memory.
  4. Less Dangerous Perfectly safe, so long as cs is truly

    NULL-terminated.
 static func fromCString(cs: UnsafePointer<CChar>) -> String? Strings
  5. Less Dangerous Perfectly safe, so long as cs is truly

    NULL-terminated.
 static func fromCString(cs: UnsafePointer<CChar>) -> String? Wait, what?
 func withCString<Result>(@noescape f:UnsafePointer<Int8> -> Result) -> Result Strings
  6. Lifetimes & Ownership • Swift uses ARC*, including for some

    structs and enums • You can include strong object references in them, something ARC disallows for C structs • A function expecting a C string needs the storage to be contiguous. Something not necessarily guaranteed by String (or Array!) • It also needs that storage to be alive. A pointer is dumb, says nothing of lifetime • withCString promises that the pointer will be contiguous and alive for the duration of the closure. • Also why it’s @noescape - because if the closure outlived the call then it would have a dangling pointer.
  7. Lifetimes & Ownership • Arrays look like structs and have

    value semantics • But really the structs are just headers, the item storage is on the heap • Shared when it can be, copy-on-mutate • Does adding two arrays copy them or just use a linked list of slices? Doesn’t matter, it’s invisible to us! • So pass them with &myArray or use withUnsafeBufferPointer!
  8. Less Dangerous • Only for reference types (inherits NSObject or

    Swift Object) • fromOpaque / toOpaque allow conversion from/to COpaquePointer (aka void*) • passRetained / passUnretained • Get objects into Unmanaged<T> • takeRetainedValue / takeUnretainedValue • Get objects back out of Unmanaged<T> • retain / release / autorelease • Relive the glory days of Manual Reference Counting Unmanaged
  9. Less Dangerous let o = OhMy() //Unbalanced retain +1 let

    context = Unmanaged.passRetained(o).toOpaque() Unmanaged
  10. Less Dangerous let o = OhMy() //Unbalanced retain +1 let

    context = Unmanaged.passRetained(o).toOpaque() //UnsafeMutablePointer<Void> won't manage any memory let contextPointer = UnsafeMutablePointer<Void>(context) Unmanaged
  11. Less Dangerous let o = OhMy() //Unbalanced retain +1 let

    context = Unmanaged.passRetained(o).toOpaque() //UnsafeMutablePointer<Void> won't manage any memory let contextPointer = UnsafeMutablePointer<Void>(context) dispatch_queue_set_specific(queue, &Keys.SomeKey, contextPointer, nil) Unmanaged
  12. Less Dangerous • Unfortunately in that last example, we’d really

    like to pass a function as the last parameter • Swift currently has no support for exposing C function pointers • You can receive them and pass them along, but you can’t provide them or call them** • ** If you get an UnsafePointer to a function it’s a wrapper struct with a function object that itself contains a function address but that’s just asking for random crashes and breakage. • But it does prove it would be possible for Swift to allow this in the future Unmanaged
  13. Less Dangerous • In many cases Swift now automatically converts

    for you • When in doubt, try passing as &myVar • Arrays, Ints, structs all work • You can cast pointers by passing them to init() UnsafePointer / UnsafeMutablePointer
  14. Less Dangerous • Pointers in swift are in one of

    three states: • Unallocated - pointer is NULL or location was deallocated • Allocated - pointer points at a memory location, location is uninitialized • Initialized - pointer points to a memory location and that location has a valid value UnsafePointer / UnsafeMutablePointer
  15. Less Dangerous 
 let nullPtr = UnsafeMutablePointer<Int>(nil) println(nullPtr.memory) let ptr

    = UnsafeMutablePointer<Int>.alloc(1) UnsafePointer / UnsafeMutablePointer Equivalent to malloc(sizeof(Int))
  16. Less Dangerous 
 let nullPtr = UnsafeMutablePointer<Int>(nil) println(nullPtr.memory) let ptr

    = UnsafeMutablePointer<Int>.alloc(1) ptr.initialize(5) UnsafePointer / UnsafeMutablePointer ptr’s destination now has a defined value
  17. Less Dangerous 
 let nullPtr = UnsafeMutablePointer<Int>(nil) println(nullPtr.memory) let ptr

    = UnsafeMutablePointer<Int>.alloc(1) ptr.initialize(5) ptr.destroy() UnsafePointer / UnsafeMutablePointer ptr’s destination uninitialized, but memory is still allocated
  18. Less Dangerous 
 let nullPtr = UnsafeMutablePointer<Int>(nil) println(nullPtr.memory) let ptr

    = UnsafeMutablePointer<Int>.alloc(1) ptr.initialize(5) ptr.destroy() ptr.dealloc(1) UnsafePointer / UnsafeMutablePointer equivalent to free()
  19. Lifetimes & Ownership • UnsafePointer<T> / UnsafeMutablePointer<T> participate in ARC

    lifetime rules (if T supports it) • It’s like pointers had __strong attributes by default • It also means assigning to the memory property releases any existing object and retains the new one (or any nested references for structs/enums)
  20. Less Dangerous class OhMy { deinit { println("I got deinit'd!")

    } } struct Fancy { var objectRef:AnyObject? } UnsafePointer / UnsafeMutablePointer
  21. Less Dangerous class OhMy { deinit { println("I got deinit'd!")

    } } struct Fancy { var objectRef:AnyObject? } let ptr = UnsafeMutablePointer<Fancy>.alloc(1) ptr.initialize(Fancy(objectRef: OhMy())) UnsafePointer / UnsafeMutablePointer
  22. Less Dangerous class OhMy { deinit { println("I got deinit'd!")

    } } struct Fancy { var objectRef:AnyObject? } let ptr = UnsafeMutablePointer<Fancy>.alloc(1) ptr.initialize(Fancy(objectRef: OhMy())) //Oops, deinit never runs… ever UnsafePointer / UnsafeMutablePointer
  23. Less Dangerous class OhMy { deinit { println("I got deinit'd!")

    } } struct Fancy { var objectRef:AnyObject? } let ptr = UnsafeMutablePointer<Fancy>.alloc(1) ptr.initialize(Fancy(objectRef: OhMy())) ptr.destroy() //deinit runs here! UnsafePointer / UnsafeMutablePointer
  24. Less Dangerous class OhMy { deinit { println("I got deinit'd!")

    } } struct Fancy { var objectRef:AnyObject? } let ptr = UnsafeMutablePointer<Fancy>.alloc(1) ptr.initialize(Fancy(objectRef: OhMy())) ptr.destroy() //deinit runs here! ptr.dealloc(1) //don't leak memory UnsafePointer / UnsafeMutablePointer
  25. Less Dangerous class OhMy { deinit { println("I got deinit'd!")

    } } struct Fancy { var objectRef:AnyObject? } let ptr = UnsafeMutablePointer<Fancy>.alloc(1) ptr.memory = Fancy(objectRef: OhMy()) ptr.destroy() //deinit runs here! ptr.dealloc(1) //don't leak memory UnsafePointer / UnsafeMutablePointer Memory is still uninitialized so assignment could try to free previous “garbage”
  26. Less Dangerous • You may notice that with UnsafePointers, Swift

    treats them similarly to C • Pretend they’re an array by subscripting • Only you know if the pointer is to a single element or an array of 10,000 elements. UnsafePointer / UnsafeMutablePointer
  27. Less Dangerous • move transfers ownership out of the current

    pointer. If the object isn’t retained elsewhere it will get cleaned up. • The source pointer becomes uninitialized in the process • moveInitializeFrom transfers ownership from some other pointer to this one, assuming this one is empty • If it isn’t then you’ll leak. • moveAssignFrom does the same but destroys any objects that already exist in the current pointer first • If it isn’t then you could crash. UnsafePointer / UnsafeMutablePointer
  28. Less Dangerous • We can also do pointer arithmetic
 var

    values = UnsafeMutablePointer<some_struct_t>()
 for i in 0..<getSomeStructs(&values) {
 let valuePtr = values + i
 ...
 } • Or just subscript var values = UnsafeMutablePointer<some_struct_t>() for i in 0..<getSomeStructs(&values) { let value: some_struct_t = values[i] } UnsafePointer / UnsafeMutablePointer
  29. Less Dangerous • Allows implicit conversions to cover in/out parameters.

    You’re already using it - anything that takes NSError**! • Nil turns into a NULL pointer • UnsafeMutablePointer<T> passed as-is • Object references (&myObj) are special • A temporary copy of the reference is allocated • On return, the reference is copied back into myObj which will retain it. • If it were a normal UnsafeMutablePointer<T>, Swift would assume the caller was returning a +1 retained object and not bother retaining it again. AutoreleasingUnsafeMutablePointer (✨Magic✨)
  30. UnsafePointer Example let host = "www.apple.com" let port = "80"

    var hints = addrinfo() hints.ai_family = PF_UNSPEC hints.ai_socktype = SOCK_STREAM hints.ai_protocol = IPPROTO_TCP
  31. UnsafePointer Example let host = "www.apple.com" let port = "80"

    var hints = addrinfo() hints.ai_family = PF_UNSPEC hints.ai_socktype = SOCK_STREAM hints.ai_protocol = IPPROTO_TCP var firstResponse = UnsafeMutablePointer<addrinfo>() if getaddrinfo(host, port, &hints, &firstResponse) == 0 {
  32. UnsafePointer Example let host = "www.apple.com" let port = "80"

    var hints = addrinfo() hints.ai_family = PF_UNSPEC hints.ai_socktype = SOCK_STREAM hints.ai_protocol = IPPROTO_TCP var firstResponse = UnsafeMutablePointer<addrinfo>() if getaddrinfo(host, port, &hints, &firstResponse) == 0 { for var response = firstResponse; response != nil; response = response.memory.ai_next { if response.memory.ai_family == AF_INET || response.memory.ai_family == AF_INET6 {
  33. UnsafePointer Example let host = "www.apple.com" let port = "80"

    var hints = addrinfo() hints.ai_family = PF_UNSPEC hints.ai_socktype = SOCK_STREAM hints.ai_protocol = IPPROTO_TCP var firstResponse = UnsafeMutablePointer<addrinfo>() if getaddrinfo(host, port, &hints, &firstResponse) == 0 { for var response = firstResponse; response != nil; response = response.memory.ai_next { if response.memory.ai_family == AF_INET || response.memory.ai_family == AF_INET6 { var buffer = [CChar](count:Int(INET6_ADDRSTRLEN), repeatedValue: 0)
  34. UnsafePointer Example let host = "www.apple.com" let port = "80"

    var hints = addrinfo() hints.ai_family = PF_UNSPEC hints.ai_socktype = SOCK_STREAM hints.ai_protocol = IPPROTO_TCP var firstResponse = UnsafeMutablePointer<addrinfo>() if getaddrinfo(host, port, &hints, &firstResponse) == 0 { for var response = firstResponse; response != nil; response = response.memory.ai_next { if response.memory.ai_family == AF_INET || response.memory.ai_family == AF_INET6 { var buffer = [CChar](count:Int(INET6_ADDRSTRLEN), repeatedValue: 0) if inet_ntop(response.memory.ai_family, response.memory.ai_addr, &buffer, socklen_t(buffer.count)) != nil { let ipString = String.fromCString(buffer) println("\(ipString)")
  35. UnsafePointer Example let host = "www.apple.com" let port = "80"

    var hints = addrinfo() hints.ai_family = PF_UNSPEC hints.ai_socktype = SOCK_STREAM hints.ai_protocol = IPPROTO_TCP var firstResponse = UnsafeMutablePointer<addrinfo>() if getaddrinfo(host, port, &hints, &firstResponse) == 0 { for var response = firstResponse; response != nil; response = response.memory.ai_next { if response.memory.ai_family == AF_INET || response.memory.ai_family == AF_INET6 { var buffer = [CChar](count:Int(INET6_ADDRSTRLEN), repeatedValue: 0) if inet_ntop(response.memory.ai_family, response.memory.ai_addr, &buffer, socklen_t(buffer.count)) != nil { let ipString = String.fromCString(buffer) println("\(ipString)") } } } freeaddrinfo(firstResponse) }
  36. UnsafePointer Example let host = "www.apple.com" let port = "80"

    var hints = addrinfo() hints.ai_family = PF_UNSPEC hints.ai_socktype = SOCK_STREAM hints.ai_protocol = IPPROTO_TCP var firstResponse = UnsafeMutablePointer<addrinfo>() if getaddrinfo(host, port, &hints, &firstResponse) == 0 { for var response = firstResponse; response != nil; response = response.memory.ai_next { if response.memory.ai_family == AF_INET || response.memory.ai_family == AF_INET6 { var buffer = [CChar](count:Int(INET6_ADDRSTRLEN), repeatedValue: 0) if inet_ntop(response.memory.ai_family, response.memory.ai_addr, &buffer, socklen_t(buffer.count)) != nil { let ipString = String.fromCString(buffer) println("\(ipString)") } } } freeaddrinfo(firstResponse) }
  37. Less Dangerous • Now you too can implement your own

    version of Array! (or any other fancy data structure your heart desires) • Define your own copy-on-write struct • Internally references a ManagedBuffer • On mutate, if isUniquelyReferenced then mutate in place • Otherwise make a copy and mutate that ManagedBuffer / ManagedBufferPointer
  38. Less Dangerous struct CustomArray <Element> { private var _buffer: CustomArrayBuffer<Int,Element>

    init(capacity:Int) { _buffer = CustomArrayBuffer<Int,Element>.create(capacity, 
 initialValue: { (proto) -> Int in return capacity }) as! CustomArrayBuffer<Int,Element> } subscript(index:Int) -> Element { get { return _buffer[index] } mutating set { if !isUniquelyReferenced(&_buffer) { _buffer = _buffer.copy() } _buffer[index] = newValue } } } ManagedBuffer / ManagedBufferPointer
  39. Less Dangerous struct CustomArray <Element> { private var _buffer: CustomArrayBuffer<Int,Element>

    init(capacity:Int) { _buffer = CustomArrayBuffer<Int,Element>.create(capacity, 
 initialValue: { (proto) -> Int in return capacity }) as! CustomArrayBuffer<Int,Element> } subscript(index:Int) -> Element { get { return _buffer[index] } mutating set { if !isUniquelyReferenced(&_buffer) { _buffer = _buffer.copy() } _buffer[index] = newValue } } } ManagedBuffer / ManagedBufferPointer Only copy if necessary
  40. Less Dangerous class CustomArrayBuffer<Value,Element> : ManagedBuffer<Int, Element> { deinit {

    withUnsafeMutablePointers { vptr, eptr -> Void in eptr.destroy(self.count) vptr.destroy() } } var count: Int { return self.value } func copy() -> CustomArrayBuffer<Int, Element> { let result = CustomArrayBuffer<Int, Element>.create(self.count, 
 initialValue: { (proto) -> Int in return self.count }) as! CustomArrayBuffer<Int, Element> 
 self.withUnsafeMutablePointerToElements { srcPtr -> Void in result.withUnsafeMutablePointerToElements { destPtr -> Void in destPtr.initializeFrom(srcPtr, count: self.count) } } return result } subscript(index:Int) -> Element { get { return withUnsafeMutablePointerToElements { $0.advancedBy(index).memory } } set { withUnsafeMutablePointerToElements { $0.advancedBy(index).memory = newValue } } } } ManagedBuffer / ManagedBufferPointer
  41. Less Dangerous deinit { withUnsafeMutablePointers { vptr, eptr -> Void

    in eptr.destroy(self.count) vptr.destroy() } } ManagedBuffer / ManagedBufferPointer
  42. Less Dangerous deinit { withUnsafeMutablePointers { vptr, eptr -> Void

    in eptr.destroy(self.count) vptr.destroy() } } subscript(index:Int) -> Element { get { return withUnsafeMutablePointerToElements {
 $0.advancedBy(index).memory } } set { withUnsafeMutablePointerToElements {
 $0.advancedBy(index).memory = newValue 
 } } } ManagedBuffer / ManagedBufferPointer
  43. Less Dangerous func copy() -> CustomArrayBuffer<Int, Element> { let result

    = CustomArrayBuffer<Int, Element>.create(self.count, 
 initialValue: { self.count }) as! CustomArrayBuffer<Int, Element> 
 self.withUnsafeMutablePointerToElements { srcPtr -> Void in result.withUnsafeMutablePointerToElements { destPtr -> Void in destPtr.initializeFrom(srcPtr, count: self.count) } } return result } ManagedBuffer / ManagedBufferPointer Copy the elements
  44. Less Dangerous • Just a bag-o-bits • Useful for shoe-horning

    Unmanaged pointers into UnsafePointers and vice-versa COpaquePointer
  45. Variadic Arguments • Requires a va_list version of the function

    • Use withVaList; life of CVaListPointer is tied to the closure • Must implement CVarArgType • getVaList for cases where use-before-init rules prevent you from doing it properly • Auto-released object so has a runtime cost • Otherwise safe because it isn’t legal to use the va_list after returning
  46. Variadic Arguments typedef struct some_struct { NSInteger a; NSInteger b;

    NSInteger c; } some_struct_t; void valistFunction(int count, va_list args);
  47. Variadic Arguments typedef struct some_struct { NSInteger a; NSInteger b;

    NSInteger c; } some_struct_t; void valistFunction(int count, va_list args); //Swift Code: extension some_struct_t : CVarArgType { public func encode() -> [Word] { return self.a.encode() + self.b.encode() + self.c.encode() } } let s1 = some_struct_t(a: 42, b: 42, c: 42) let s2 = some_struct_t(a: 99, b: 99, c: 99) withVaList([s1, s2]) { p -> Void in valistFunction(2, p) }
  48. unsafeUnwrap • In debug builds, works exactly like ‘!’ •

    In release builds, doesn’t bother checking for nil • If you’re wrong, crash-city • Profile first! Unless this is a performance problem, it’s not worth it.
  49. unsafeDowncast • In debug builds, works exactly like ‘as’ •

    In release builds, doesn’t bother checking the type • It’s wrong isa all over again • If you’re wrong: • Objective-C unrecognized selector • Swift maybe unrecognized selector, maybe just memory corruption (depends on optimization) • Again, profile first!
  50. The Toolbox Handle with Extreme Caution The use of any

    of these in production should be an automatic red flag and trigger the highest level of code review and tests!
  51. unsafeBitCast • Blindly treat bits of X as a given

    type • Beta documentation comments described it as “brutal” • Release takes a softer approach: 
 “There’s almost always a better way to do anything” • Personally, I agree. Nitroglycerin is extremely dangerous. • … but can be life-saving when you truly need it.
  52. unsafeAddressOf • Blindly grab the address of the storage for

    X. • No guarantees about lifetime • You can accomplish just about anything with UnsafeMutablePointer and Unmanaged, so why bother with this?
  53. @asmname • If you know a C function that exists

    (even if deprecated) • … or you know, private Swift functions • I highly recommend you understand the ABI and how Swift will marshal the parameters; you won’t get much compiler help here.
 @asmname("dispatch_get_current_queue") func _get_current_queue() -> dispatch_queue_t
  54. Tips • For APIs that want a key (or void

    *), just declare a static var initialized to a unique constant string, then pass &myStaticVar
  55. Resources • Apple Swift Blog - https://developer.apple.com/swift/blog/ • Mike Ash

    - https://www.mikeash.com/pyblog/ • NSHipster - http://nshipster.com • objc.io - http://www.objc.io • my blog: http://russbishop.net