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

Swift funtime

Swift funtime

This talk revisits the Objective-C 2.0 runtime in the age of Swift and also takes a look at the runtime for pure Swift classes. You will learn which parts of dynamic behaviour you can still utilise and how common practical runtime hackery translates to Swift.

Boris Bügling

February 23, 2015
Tweet

More Decks by Boris Bügling

Other Decks in Technology

Transcript

  1. AGENDA ▸ What is a Swift object? ▸ Objective-C runtime

    in the age of Swift ▸ Swift runtime
  2. ▸ behaves like any old Objective-C object ▸ instance variables

    are properties ▸ fully interopable with ObjC
  3. ▸ has SwiftObject as superclass ▸ instance variables are ivars

    ▸ ivars have no type encoding ▸ methods are not ObjC methods ▸ not interoperable with ObjC
  4. PLAYGROUND! import ObjectiveC.runtime but Playground execution failed: Error in auto-import:

    failed to get module 'runtime' from AST context (rdar://problem/18482380)
  5. !"

  6. 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
  7. #import <Foundation/Foundation.h> #import <objc/runtime.h> @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; } }
  8. 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 } [...] }
  9. // 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")
  10. #!/usr/bin/env xcrun swift func info<T>(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
  11. 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)
  12. 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)
  13. 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;
  14. OBJECTS ▸ struct magic ▸ contains refCount and isa ▸

    methods are in virtual table, like in C++ CLASSES ▸ have mangled names, which contain the module name
  15. 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 <A : Swift.Equatable>(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
  16. 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
  17. SWROUTE ▸ PoC of function hooking in Swift ▸ Uses

    rd_route, a Mach specific injection library for C
  18. #include <stdint.h> #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; }
  19. 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)
  20. 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
  21. 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 [...]
  22. MEMORY LAYOUT ▸ 16 bytes => Swift object ▸ 8

    bytes => Pointer to _TF6memory3addFTSiSi_Si Function pointer !
  23. struct f_trampoline { var trampoline_ptr: COpaquePointer var function_obj_ptr: UnsafeMutablePointer<function_obj> }

    struct function_obj { var some_ptr_0: COpaquePointer var some_ptr_1: COpaquePointer var function_ptr: COpaquePointer }
  24. 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
  25. 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))
  26. 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
  27. 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
  28. SPEED ▸ less dynamic dispatch ▸ omits _cmd - freeing

    one register ▸ usually no pointer aliasing int *ptrA = malloc(100 * sizeof(*ptrA)); int *ptrB = ptrA;
  29. 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)