Slide 1

Slide 1 text

Russ Bishop PlanGrid http://russbishop.net Unsafe Swift For Fun & Profit

Slide 2

Slide 2 text

The Swift Language “Swift adopts safe programming patterns”

Slide 3

Slide 3 text

Why Safety?

Slide 4

Slide 4 text

Why Safety? • Memory safety • The Billion Dollar Mistake • Productivity • isa oops • Undefined Behavior • Why retainCount was removed

Slide 5

Slide 5 text

Why not just write wrappers in Objective-C? We’ve been down that dusty road before…

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

If you’re lucky:

Slide 8

Slide 8 text

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.

Slide 9

Slide 9 text

The Toolbox Let’s run with safety scissors first

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Less Dangerous Perfectly safe, so long as cs is truly NULL-terminated.
 static func fromCString(cs: UnsafePointer) -> String? Wait, what?
 func withCString(@noescape f:UnsafePointer -> Result) -> Result Strings

Slide 12

Slide 12 text

Lifetimes & Ownership

Slide 13

Slide 13 text

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.

Slide 14

Slide 14 text

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!

Slide 15

Slide 15 text

Unmanaged

Slide 16

Slide 16 text

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 • takeRetainedValue / takeUnretainedValue • Get objects back out of Unmanaged • retain / release / autorelease • Relive the glory days of Manual Reference Counting Unmanaged

Slide 17

Slide 17 text

Less Dangerous let o = OhMy() Unmanaged

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Less Dangerous let o = OhMy() //Unbalanced retain +1 let context = Unmanaged.passRetained(o).toOpaque() //UnsafeMutablePointer won't manage any memory let contextPointer = UnsafeMutablePointer(context) dispatch_queue_set_specific(queue, &Keys.SomeKey, contextPointer, nil) Unmanaged

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Unsafe Pointers

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Less Dangerous 
 let nullPtr = UnsafeMutablePointer(nil) UnsafePointer / UnsafeMutablePointer Null Pointer

Slide 26

Slide 26 text

Less Dangerous 
 let nullPtr = UnsafeMutablePointer(nil) println(nullPtr.memory) UnsafePointer / UnsafeMutablePointer EXC_BAD_ACCESS

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Less Dangerous 
 let nullPtr = UnsafeMutablePointer(nil) println(nullPtr.memory) let ptr = UnsafeMutablePointer.alloc(1) ptr.initialize(5) ptr.destroy() ptr.dealloc(1) UnsafePointer / UnsafeMutablePointer equivalent to free()

Slide 31

Slide 31 text

Yeah, but who cares? Remember what we said about ARC, Lifetimes, & Ownership

Slide 32

Slide 32 text

Lifetimes & Ownership • UnsafePointer / UnsafeMutablePointer 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)

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Less Dangerous class OhMy { deinit { println("I got deinit'd!") } } struct Fancy { var objectRef:AnyObject? } let ptr = UnsafeMutablePointer.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”

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Less Dangerous • We can also do pointer arithmetic
 var values = UnsafeMutablePointer()
 for i in 0..() for i in 0..

Slide 42

Slide 42 text

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 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, Swift would assume the caller was returning a +1 retained object and not bother retaining it again. AutoreleasingUnsafeMutablePointer (✨Magic✨)

Slide 43

Slide 43 text

UnsafePointer Example let host = "www.apple.com" let port = "80"

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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() if getaddrinfo(host, port, &hints, &firstResponse) == 0 {

Slide 46

Slide 46 text

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() 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 {

Slide 47

Slide 47 text

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() 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)

Slide 48

Slide 48 text

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() 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)")

Slide 49

Slide 49 text

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() 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) }

Slide 50

Slide 50 text

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() 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) }

Slide 51

Slide 51 text

ManagedBuffer / ManagedBufferPointer

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Less Dangerous struct CustomArray { private var _buffer: CustomArrayBuffer init(capacity:Int) { _buffer = CustomArrayBuffer.create(capacity, 
 initialValue: { (proto) -> Int in return capacity }) as! CustomArrayBuffer } 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

Slide 55

Slide 55 text

Less Dangerous class CustomArrayBuffer : ManagedBuffer { deinit { withUnsafeMutablePointers { vptr, eptr -> Void in eptr.destroy(self.count) vptr.destroy() } } var count: Int { return self.value } func copy() -> CustomArrayBuffer { let result = CustomArrayBuffer.create(self.count, 
 initialValue: { (proto) -> Int in return self.count }) as! CustomArrayBuffer 
 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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

Less Dangerous func copy() -> CustomArrayBuffer { let result = CustomArrayBuffer.create(self.count, 
 initialValue: { self.count }) as! CustomArrayBuffer 
 self.withUnsafeMutablePointerToElements { srcPtr -> Void in result.withUnsafeMutablePointerToElements { destPtr -> Void in destPtr.initializeFrom(srcPtr, count: self.count) } } return result } ManagedBuffer / ManagedBufferPointer Copy the elements

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

The Toolbox Handle with Caution

Slide 61

Slide 61 text

Less Dangerous • Just a bag-o-bits • Useful for shoe-horning Unmanaged pointers into UnsafePointers and vice-versa COpaquePointer

Slide 62

Slide 62 text

Variadic Arguments

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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) }

Slide 66

Slide 66 text

unsafeUnwrap / unsafeDowncast

Slide 67

Slide 67 text

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.

Slide 68

Slide 68 text

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!

Slide 69

Slide 69 text

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!

Slide 70

Slide 70 text

unsafeBitCast It’s brutal

Slide 71

Slide 71 text

unsafeBitCast It’s brutal (seriously)

Slide 72

Slide 72 text

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.

Slide 73

Slide 73 text

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?

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

The Toolbox Here there be dragons

Slide 76

Slide 76 text

@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

Slide 77

Slide 77 text

Tips

Slide 78

Slide 78 text

Tips • For APIs that want a key (or void *), just declare a static var initialized to a unique constant string, then pass &myStaticVar

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

Fin.