Slide 1

Slide 1 text

SWIFT FUNTIME SWIFT.BERLIN #7 BORIS BÜGLING - @NEONACHO

Slide 2

Slide 2 text

COCOAPODS

Slide 3

Slide 3 text

CONTENTFUL

Slide 4

Slide 4 text

“Swift’s clean slate [...] is an opportunity to reimagine how software development works.”

Slide 5

Slide 5 text

AGENDA ▸ What is a Swift object? ▸ Objective-C runtime in the age of Swift ▸ Swift runtime

Slide 6

Slide 6 text

WHAT IS A SWIFT OBJECT?

Slide 7

Slide 7 text

IT DEPENDS

Slide 8

Slide 8 text

class MyObject : NSObject { }

Slide 9

Slide 9 text

▸ behaves like any old Objective-C object ▸ instance variables are properties ▸ fully interopable with ObjC

Slide 10

Slide 10 text

class MyObject { }

Slide 11

Slide 11 text

▸ has SwiftObject as superclass ▸ instance variables are ivars ▸ ivars have no type encoding ▸ methods are not ObjC methods ▸ not interoperable with ObjC

Slide 12

Slide 12 text

PLAYGROUND! import ObjectiveC.runtime but Playground execution failed: Error in auto-import: failed to get module 'runtime' from AST context (rdar://problem/18482380)

Slide 13

Slide 13 text

!"

Slide 14

Slide 14 text

SWIFTOBJECT Ivar: magic {SwiftObject_s="isa"^v"refCount"q} Protocol: NSObject NSOBJECT Ivar: isa # Protocol: NSObject

Slide 15

Slide 15 text

class MySwiftClass { var foo = "bar"; init() { } } import Foundation import ObjectiveC.runtime var ivar = class_getInstanceVariable(MySwiftClass().dynamicType, "foo") var value : AnyObject = object_getIvar(MySwiftClass(), ivar)! Segmentation fault: 11

Slide 16

Slide 16 text

#import #import @interface MyClass : NSObject @property (nonatomic, retain) NSString* foo; @end #pragma mark - @implementation MyClass -(instancetype)init { self = [super init]; if (self) { self.foo = @"bar"; } return self; } @end #pragma mark - int main(int argc, char *argv[]) { @autoreleasepool { MyClass* object = [MyClass new]; Ivar ivar = class_getInstanceVariable(object.class, "_foo"); id value = object_getIvar(object, ivar); NSLog(@"%@", value); return 0; } }

Slide 17

Slide 17 text

VALUE TYPES SHOULD BE STRUCTS struct MyObject { var a : String var b : Array }

Slide 18

Slide 18 text

IN PURE SWIFT, THERE'S NO INTROSPECTION !

Slide 19

Slide 19 text

THERE IS HOPE /// How children of this value should be presented in the IDE. enum MirrorDisposition { case Struct case Class case Enum case Tuple [...] } /// A protocol that provides a reflection interface to an underlying value. protocol MirrorType { [...] /// Get the number of logical children this value has. var count: Int { get } subscript (i: Int) -> (String, MirrorType) { get } /// Get a string description of this value. var summary: String { get } [...] }

Slide 20

Slide 20 text

// From: https://gist.github.com/peebsjs/9288f79322ed3119ece4 infix operator --> {} func --> (instance: Any, key: String) -> Any? { let mirror = reflect(instance) for index in 0 ..< mirror.count { let (childKey, childMirror) = mirror[index] if childKey == key { return childMirror.value } } return nil } //Example struct MyPoint { let x: Float let y: Float } let point = MyPoint(x: 1, y: 2) println(point --> "x") println(point --> "y")

Slide 21

Slide 21 text

#!/usr/bin/env xcrun swift func info(x: T) { println("\(x) is a \(_stdlib_getDemangledTypeName(x))") } let array = [0, 1, 2] // appending 'as AnyObject' here yields a compiler error info(array) import Foundation let objc_array: AnyObject = [0, 1, 2] as AnyObject info(objc_array) // comparing different array types => compiler error as well //let equal = objc_array == array

Slide 22

Slide 22 text

OBJECTIVE-C RUNTIME IN THE AGE OF SWIFT

Slide 23

Slide 23 text

INHERIT FROM NSObject AND IT JUST WORKS!

Slide 24

Slide 24 text

EVEN SWIZZLING !

Slide 25

Slide 25 text

import Foundation import ObjectiveC.runtime extension NSString { func swizzle_description() -> NSString { return "!" } } var myString = "foobar" as NSString println(myString.description) var originalMethod = class_getInstanceMethod(NSString.self, "description") var swizzledMethod = class_getInstanceMethod(NSString.self, "swizzle_description") method_exchangeImplementations(originalMethod, swizzledMethod) println(myString.description)

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

OR REPLACING METHODS import Foundation import ObjectiveC.runtime let myString = "foobar" as NSString println(myString.description) let myBlock : @objc_block (AnyObject!) -> String = { (sself : AnyObject!) -> (String) in "✋" } let myIMP = imp_implementationWithBlock(unsafeBitCast(myBlock, AnyObject.self)) let method = class_getInstanceMethod(myString.dynamicType, "description") method_setImplementation(method, myIMP) println(myString.description)

Slide 29

Slide 29 text

LET'S TAKE A STEP BACK

Slide 30

Slide 30 text

OBJECTS typedef struct objc_object { Class isa; } *id;

Slide 31

Slide 31 text

CLASSES struct objc_class { Class isa; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;

Slide 32

Slide 32 text

OBJECTS ▸ struct magic ▸ contains refCount and isa ▸ methods are in virtual table, like in C++ CLASSES ▸ have mangled names, which contain the module name

Slide 33

Slide 33 text

NAME MANGLING another C++ concept _TFV4test1eCfMS0_FT_S0_ ---> test.e.init (test.e.Type)() -> test.e _TMLCCC4test1a1b1c ---> lazy cache variable for type metadata for test.a.b.c _TMmCCC4test1a1b1c ---> metaclass for test.a.b.c _TMnCC4test1a1b ---> nominal type descriptor for test.a.b _TTWOV4test1e1fSs9EquatableFS2_oi2eeUS2___fMQPS2_FTS3_S3__Sb ---> protocol witness for Swift. Equatable.== infix (Swift.Equatable.Self.Type) (Swift.Equatable.Self, Swift.Equatable.Self) -> Swift.Bool in conformance test.e.f : Swift.Equatable _TWoFC4test1aCfMS0_FT_S0_ ---> witness table offset for test.a.__allocating_init (test.a.Type)() -> test.a _TWoFCCC4test1a1b1c1dfS2_FT1zS0_1xS1_1vFT1xSi_Si_OVS_1e1f ---> witness table offset for test.a.b.c.d (test.a.b.c)(z : test.a, x : test.a.b, v : (x : Swift.Int) -> Swift.Int) -> test.e.f

Slide 34

Slide 34 text

HOW ARE EMOJI FORMED? $ echo 'class ! {}'|xcrun swiftc -emit-library -o test - $ nm -g test ... 0000000000000db0 T __TFC4testX4ypIhD ... $ xcrun swift-demangle __TFC4testX4ypIhD _TFC4testX4ypIhD ---> test.!.__deallocating_deinit X4 ypIh ~ xn--yp8h

Slide 35

Slide 35 text

METHODS struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE;

Slide 36

Slide 36 text

METHOD IMPLEMENTATIONS typedef struct objc_selector *SEL; typedef id (*IMP)(id self, SEL _cmd ,...);

Slide 37

Slide 37 text

MESSAGE FORWARDING +(BOOL)resolveInstanceMethod:(SEL)aSEL; -(void)forwardInvocation:(NSInvocation*)anInvocation; -(NSMethodSignature*)methodSignatureForSelector:(SEL)selector; -(BOOL)respondsToSelector:(SEL)aSelector;

Slide 38

Slide 38 text

FROM UIVIEWCONTROLLER.H - (void)attentionClassDumpUser:(id)arg1 yesItsUsAgain:(id)arg2 althoughSwizzlingAndOverridingPrivateMethodsIsFun:(id)arg3 itWasntMuchFunWhenYourAppStoppedWorking:(id)arg4 pleaseRefrainFromDoingSoInTheFutureOkayThanksBye:(id)arg5;

Slide 39

Slide 39 text

CHANGE CLASSES AT RUNTIME ▸ method_setImplementation() ▸ class_addMethod() ▸ ...

Slide 40

Slide 40 text

NSINVOCATION DOES NOT EXIST

Slide 41

Slide 41 text

BUT WHAT CAN WE DO ABOUT PURE SWIFT?

Slide 42

Slide 42 text

SWROUTE ▸ PoC of function hooking in Swift ▸ Uses rd_route, a Mach specific injection library for C

Slide 43

Slide 43 text

#include #define kObjectFieldOffset sizeof(uintptr_t) struct swift_func_object { uintptr_t *original_type_ptr; #if defined(__x86_64__) uintptr_t *unknown0; #else uintptr_t *unknown0, *unknown1; #endif uintptr_t function_address; uintptr_t *self; }; uintptr_t _rd_get_func_impl(void *func) { struct swift_func_object *obj = (struct swift_func_object *)*(uintptr_t *)(func + kObjectFieldOffset); return obj->function_address; }

Slide 44

Slide 44 text

WHAT IS A FUNCTION? func add(a: Int, b: Int) -> Int { return a + b } let f = add f(1, 2) // $R0: Int = 3 println(f) // (Function)

Slide 45

Slide 45 text

NAME MANGLING $ xcrun swiftc func.swift $ nm -g func 0000000100000f10 T __TF4func3addFTSiSi_Si [...] $ xcrun swift-demangle __TF4func3addFTSiSi_Si _TF4func3addFTSiSi_Si ---> func.add (Swift.Int, Swift.Int) -> Swift.Int

Slide 46

Slide 46 text

MEMORY LAYOUT ▸ 8 bytes => Pointer to _TPA__TTRXFo_dSidSi_dSi_XFo_iTSiSi__iSi_ ▸ 8 bytes => Pointer to struct _TPA__TTRXFo_dSidSi_dSi_XFo_iTSiSi__iSi_ ---> partial apply forwarder for reabstraction thunk helper [...]

Slide 47

Slide 47 text

MEMORY LAYOUT ▸ 16 bytes => Swift object ▸ 8 bytes => Pointer to _TF6memory3addFTSiSi_Si Function pointer !

Slide 48

Slide 48 text

struct f_trampoline { var trampoline_ptr: COpaquePointer var function_obj_ptr: UnsafeMutablePointer } struct function_obj { var some_ptr_0: COpaquePointer var some_ptr_1: COpaquePointer var function_ptr: COpaquePointer }

Slide 49

Slide 49 text

import Darwin @asmname("floor") func my_floor(dbl: Double) -> Double println(my_floor(6.7)) let handle = dlopen(nil, RTLD_NOW) let pointer = COpaquePointer(dlsym(handle, "ceil")) typealias FunctionType = (Double) -> Double

Slide 50

Slide 50 text

struct f_trampoline { [...] } struct function_obj { [...] } let orig = unsafeBitCast(my_floor, f_trampoline.self) let new = f_trampoline(prototype: orig, new_fp: pointer) let my_ceil = unsafeBitCast(new, FunctionType.self) println(my_ceil(6.7))

Slide 51

Slide 51 text

$ xcrun swift -Onone hook.swift 6.0 7.0

Slide 52

Slide 52 text

void executeFunction(void(*f)(void)) { f(); } @asmname("executeFunction") func executeFunction(fp: CFunctionPointer<()->()>)

Slide 53

Slide 53 text

func greeting() { println("Hello from Swift") } let t = unsafeBitCast(greeting, f_trampoline.self) let fp = CFunctionPointer<()->()> (t.function_obj_ptr.memory.function_ptr) executeFunction(fp) Hello from Swift Program ended with exit code: 0

Slide 54

Slide 54 text

SWIFT RUNTIME

Slide 55

Slide 55 text

▸ libswiftCore.dylib implementations of NSSwiftArray, etc. ▸ libswiftRuntime.a low-level primitives like swift_release

Slide 56

Slide 56 text

HOPPER

Slide 57

Slide 57 text

COMPATIBILITY ▸ App Compatibility ✅ ▸ Binary Compatibility ⛔️ ▸ Source Compatibility ⛔️

Slide 58

Slide 58 text

Foo.app boris$ find . -type f ./Frameworks/libswiftCore.dylib ./Frameworks/libswiftCoreGraphics.dylib ./Frameworks/libswiftCoreImage.dylib ./Frameworks/libswiftDarwin.dylib ./Frameworks/libswiftDispatch.dylib ./Frameworks/libswiftFoundation.dylib ./Frameworks/libswiftObjectiveC.dylib ./Frameworks/libswiftUIKit.dylib ./Info.plist ./PkgInfo ./Foo

Slide 59

Slide 59 text

TWO FINAL TIDBITS

Slide 60

Slide 60 text

SPEED ▸ less dynamic dispatch ▸ omits _cmd - freeing one register ▸ usually no pointer aliasing int *ptrA = malloc(100 * sizeof(*ptrA)); int *ptrB = ptrA;

Slide 61

Slide 61 text

class BankAccount { var balance: Double = 0.0 func deposit(amount: Double) { balance += amount } } let account = BankAccount() account.deposit(100) let depositor = BankAccount.deposit depositor(account)(100) BankAccount.deposit(account)(100)

Slide 62

Slide 62 text

METHODS ARE CURRIED FUNCTIONS

Slide 63

Slide 63 text

THANK YOU!

Slide 64

Slide 64 text

▸ https://www.mikeash.com/pyblog/ ▸ http://airspeedvelocity.net/ ▸ https://developer.apple.com/swift/blog/ ▸ http://www.russbishop.net/swift-how-did-i-do-horrible-things

Slide 65

Slide 65 text

@NeoNacho [email protected] http://buegling.com/talks