Sie sind auf Seite 1von 80

Unsafe Swift

For Fun & Profit

Russ Bishop
PlanGrid
http://russbishop.net
The Swift Language

“Swift adopts safe programming patterns”


Why Safety?
Why Safety?
• Memory safety

• The Billion Dollar Mistake

• Productivity

• isa oops

• Undefined Behavior

• Why retainCount was removed


Why not just write wrappers in
Objective-C?
We’ve been down that dusty road before…
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
If you’re lucky:
If you’re unlucky…

- (void) doBadThing
{
int *a = malloc(1); 1FC0 05
*a = 5;
[self printValue:a]; 00
} 00
- (void) printValue:(int *)a 1FC4 00
{
NSLog(@"Value is %d", *a);
}
No crash… yet.
Just corrupted memory.
The Toolbox
Let’s run with safety scissors first
Less Dangerous
Strings

Perfectly safe, so long as cs is truly NULL-terminated.


static func fromCString(cs: UnsafePointer<CChar>) -> String?


Less Dangerous
Strings

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
Lifetimes & Ownership
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.
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!


Unmanaged
Less Dangerous
Unmanaged
• 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


Less Dangerous
Unmanaged

let o = OhMy()
Less Dangerous
Unmanaged

let o = OhMy()

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

let o = OhMy()

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

//UnsafeMutablePointer<Void> won't manage any memory


let contextPointer = UnsafeMutablePointer<Void>(context)
Less Dangerous
Unmanaged

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)
Less Dangerous
Unmanaged
• 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
Unsafe Pointers
Less Dangerous
UnsafePointer / UnsafeMutablePointer

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


Less Dangerous
UnsafePointer / UnsafeMutablePointer

• 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
Less Dangerous
UnsafePointer / UnsafeMutablePointer


let nullPtr = UnsafeMutablePointer<Int>(nil)

Null Pointer
Less Dangerous
UnsafePointer / UnsafeMutablePointer


let nullPtr = UnsafeMutablePointer<Int>(nil)

println(nullPtr.memory)

EXC_BAD_ACCESS
Less Dangerous
UnsafePointer / UnsafeMutablePointer


let nullPtr = UnsafeMutablePointer<Int>(nil)

println(nullPtr.memory)

let ptr = UnsafeMutablePointer<Int>.alloc(1)

Equivalent to malloc(sizeof(Int))
Less Dangerous
UnsafePointer / UnsafeMutablePointer


let nullPtr = UnsafeMutablePointer<Int>(nil)

println(nullPtr.memory)

let ptr = UnsafeMutablePointer<Int>.alloc(1)

ptr.initialize(5) ptr’s destination now has a defined value


Less Dangerous
UnsafePointer / UnsafeMutablePointer


let nullPtr = UnsafeMutablePointer<Int>(nil)

println(nullPtr.memory)

let ptr = UnsafeMutablePointer<Int>.alloc(1)

ptr.initialize(5)

ptr.destroy()
ptr’s destination uninitialized,
but memory is still allocated
Less Dangerous
UnsafePointer / UnsafeMutablePointer


let nullPtr = UnsafeMutablePointer<Int>(nil)

println(nullPtr.memory)

let ptr = UnsafeMutablePointer<Int>.alloc(1)

ptr.initialize(5)

ptr.destroy()

ptr.dealloc(1) equivalent to free()


Yeah, but who cares?
Remember what we said about ARC, Lifetimes, & Ownership
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)
Less Dangerous
UnsafePointer / UnsafeMutablePointer

class OhMy { deinit { println("I got deinit'd!") } }


struct Fancy { var objectRef:AnyObject? }
Less Dangerous
UnsafePointer / UnsafeMutablePointer

class OhMy { deinit { println("I got deinit'd!") } }


struct Fancy { var objectRef:AnyObject? }

let ptr = UnsafeMutablePointer<Fancy>.alloc(1)


ptr.initialize(Fancy(objectRef: OhMy()))
Less Dangerous
UnsafePointer / UnsafeMutablePointer

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 🙊


Less Dangerous
UnsafePointer / UnsafeMutablePointer

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!


Less Dangerous
UnsafePointer / UnsafeMutablePointer

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
Less Dangerous
UnsafePointer / UnsafeMutablePointer

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

Memory is still uninitialized


so assignment could try to
free previous “garbage”
Less Dangerous
UnsafePointer / UnsafeMutablePointer

• 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.
Less Dangerous
UnsafePointer / UnsafeMutablePointer
• 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.


Less Dangerous
UnsafePointer / UnsafeMutablePointer

• 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]
}
Less Dangerous
AutoreleasingUnsafeMutablePointer (✨Magic✨)
• 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.
UnsafePointer Example
let host = "www.apple.com"
let port = "80"
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
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 {
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 {
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)
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)")
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)
}
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)
}
ManagedBuffer /
ManagedBufferPointer
Less Dangerous
ManagedBuffer / ManagedBufferPointer

• 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


Less Dangerous
ManagedBuffer / ManagedBufferPointer
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
}
}
}
Less Dangerous
ManagedBuffer / ManagedBufferPointer
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()
Only copy if necessary
}
_buffer[index] = newValue
}
}
}
Less Dangerous
ManagedBuffer / ManagedBufferPointer
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 }
}
}
}
Less Dangerous
ManagedBuffer / ManagedBufferPointer
deinit {
withUnsafeMutablePointers { vptr, eptr -> Void in
eptr.destroy(self.count)
vptr.destroy()
}
}
Less Dangerous
ManagedBuffer / ManagedBufferPointer
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 

}
}
}
Less Dangerous
ManagedBuffer / ManagedBufferPointer

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
}

Copy the elements


The Toolbox
Handle with Caution
Less Dangerous
COpaquePointer

• Just a bag-o-bits

• Useful for shoe-horning Unmanaged pointers into UnsafePointers and


vice-versa
Variadic Arguments
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
Variadic Arguments
typedef struct some_struct {
NSInteger a;
NSInteger b;
NSInteger c;
} some_struct_t;

void valistFunction(int count, va_list args);


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)
}
unsafeUnwrap /
unsafeDowncast
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.
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!


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!
unsafeBitCast
It’s brutal
unsafeBitCast
It’s brutal (seriously)
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.


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?
The Toolbox
Here there be dragons 🐲
@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
Tips
Tips

• For APIs that want a key (or void *), just declare a static var
initialized to a unique constant string, then pass &myStaticVar
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
Fin.

Das könnte Ihnen auch gefallen