Swift funtime

Swift funtime

A look at accomplishing the things we loved in the Objective-C runtime with Swift. The talk was given at Swift Summit. 🗻

9d2ea021919ff81e02d48530aae191bd?s=128

Boris Bügling

March 22, 2015
Tweet

Transcript

  1. SWIFT FUNTIME ! SWIFTSUMMIT, MARCH 2015 BORIS BÜGLING - @NEONACHO

  2. COCOAPODS

  3. CONTENTFUL

  4. !

  5. None
  6. WHAT IS A SWIFT OBJECT EVEN?

  7. IS IT A BAR?

  8. IT DEPENDS

  9. class MyObject : NSObject { }

  10. ▸ behaves like any old Objective-C object ▸ instance variables

    are properties ▸ fully interopable with ObjC
  11. import ObjectiveC.runtime !

  12. class MyObject { }

  13. ▸ has SwiftObject as superclass ▸ instance variables are ivars

    only ▸ ivars have no type encoding ▸ methods are not ObjC methods ▸ not interoperable with ObjC
  14. SWIFTOBJECT Ivar: magic {SwiftObject_s="isa"^v"refCount"q} Protocol: NSObject NSOBJECT Ivar: isa #

    Protocol: NSObject
  15. HOW DOES BRIDGING WORK, THEN?

  16. IT DOESN'T

  17. func info<T>(x: T) { println("\(x) is a \(_stdlib_getDemangledTypeName(x))") } let

    array = [0, 1, 2] // 'as AnyObject' => ! info(array) // is a Swift.Array import Foundation let objc_array: AnyObject = [0, 1, 2] as AnyObject info(objc_array) // is a Swift._NSSwiftArrayImpl // comparing different array types => compiler error as well //let equal = objc_array == array
  18. Y DID WE ! THE OBJECTIVE-" RUNTIME? ▸ Dynamic Introspection

    ! ▸ Change Behaviour "# ▸ Analyse private API $
  19. DYNAMIC INTROSPECTION

  20. var propertyCount : UInt32 = 0 var properties : UnsafeMutablePointer<objc_property_t>

    = class_copyPropertyList(myClass, &propertyCount) for i in 0..<propertyCount { println("Property: " + String.fromCString(property_getName(properties[Int(i)]))!) }
  21. IN PURE SWIFT => !

  22. THERE IS HOPE // Excerpt from the standard library ///

    How children of this value should be presented in the IDE. enum MirrorDisposition { case Struct case Class case Enum [...] } /// A protocol that provides a reflection interface to an underlying value. protocol MirrorType { [...] }
  23. 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 }
  24. struct MyPoint { let x: Float let y: Float }

    let point = MyPoint(x: 1, y: 2) println(point --> "x") // Optional(1.0) println(point --> "y") // Optional(2.0)
  25. CHANGE BEHAVIOUR

  26. let myString = "foobar" as NSString println(myString.description) // foobar 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) // ✋
  27. NSINVOCATION DOES NOT EXIST

  28. WHAT ABOUT PURE SWIFT?

  29. SWROUTE ▸ PoC of function hooking in Swift ▸ Uses

    rd_route, a Mach specific injection library for C
  30. #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; };
  31. 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; }
  32. LET'S DO THAT IN SWIFT

  33. 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 [...]
  34. MEMORY LAYOUT ▸ 16 bytes => Swift object ▸ 8

    bytes => Pointer to _TF6memory3addFTSiSi_Si Function pointer !
  35. 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 }
  36. @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
  37. 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))
  38. $ xcrun swift -Onone hook.swift 6.0 7.0

  39. CAN WE DO THIS THE OTHER WAY AROUND?

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

  41. 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
  42. ANALYSE PRIVATE API

  43. class MyClass { var someVar = 1 func someFuncWithAReallyLongNameLol() {

    } }
  44. $ xcrun swiftc f.swift $ ./swift-dump.rb f // Code generated

    from `f` import Foundation class MyClass { var someVar: Int = 0 func someFuncWithAReallyLongNameLol() -> () {} }
  45. $ xcrun nm -g f|grep TFC 0000000100000c50 T __TFC1f7MyClass30someFuncWithAReallyLongNameLolfS0_FT_T_ 0000000100000d30

    T __TFC1f7MyClassCfMS0_FT_S0_ 0000000100000c70 T __TFC1f7MyClassD 0000000100000d10 T __TFC1f7MyClasscfMS0_FT_S0_ 0000000100000c60 T __TFC1f7MyClassd 0000000100000ca0 T __TFC1f7MyClassg7someVarSi 0000000100000ce0 T __TFC1f7MyClassm7someVarSi 0000000100000cc0 T __TFC1f7MyClasss7someVarSi
  46. $ xcrun swift-demangle __TFC1f7MyClassg7someVarSi _TFC1f7MyClassg7someVarSi ---> f.MyClass.someVar.getter : Swift.Int

  47. 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
  48. WHAT HAVE WE LEARNED? ▸ import ObjectiveC.runtime ☺ ▸ Introspection

    somewhat exists " ▸ Changing behaviour is hard # ▸ Reverse engineering is still fine $
  49. THANK YOU!

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

  51. @NeoNacho boris@contentful.com http://buegling.com/talks http://www.contentful.com