Skip to content

Instantly share code, notes, and snippets.

@pofat
Created March 12, 2021 15:49
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pofat/d3c77ca88c5b2a3019febcb073c3d879 to your computer and use it in GitHub Desktop.
Save pofat/d3c77ca88c5b2a3019febcb073c3d879 to your computer and use it in GitHub Desktop.
Swift Struct Metadata
// Kind of type
public enum Kind {
case `struct`
case `enum`
case optional
case opaque
case tuple
case function
case existential
case metatype
case objCClassWrapper
case existentialMetatype
case foreignClass
case heapLocalVariable
case heapGenericLocalVariable
case errorObject
case `class`
init(flag: Int) {
switch flag {
case 1: self = .struct
case (0 | Flags.kindIsNonHeap): self = .struct
case 2: self = .enum
case (1 | Flags.kindIsNonHeap): self = .enum
case 3: self = .optional
case (2 | Flags.kindIsNonHeap): self = .optional
case 8: self = .opaque
case (3 | Flags.kindIsNonHeap): self = .foreignClass
case 9: self = .tuple
case (0 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .opaque
case 10: self = .function
case (1 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .tuple
case 12: self = .existential
case (2 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .function
case 13: self = .metatype
case (3 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .existential
case 14: self = .objCClassWrapper
case (4 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .metatype
case 15: self = .existentialMetatype
case (5 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .objCClassWrapper
case 16: self = .foreignClass
case (6 | Flags.kindIsRuntimePrivate | Flags.kindIsNonHeap): self = .existentialMetatype
case 64: self = .heapLocalVariable
case (0 | Flags.kindIsNonType): self = .heapLocalVariable
case 65: self = .heapGenericLocalVariable
case (0 | Flags.kindIsNonType | Flags.kindIsRuntimePrivate): self = .heapGenericLocalVariable
case 128: self = .errorObject
case (1 | Flags.kindIsNonType | Flags.kindIsRuntimePrivate): self = .errorObject
default: self = .class
}
}
enum Flags {
static let kindIsNonHeap = 0x200
static let kindIsRuntimePrivate = 0x100
static let kindIsNonType = 0x400
}
init(type: Any.Type) {
let rawValue = getKindRawValue(of: type)
self.init(flag: rawValue)
}
}
private func getKindRawValue(of type: Any.Type) -> Int {
let pointer = unsafeBitCast(type, to: UnsafePointer<Int>.self)
return pointer.pointee
}
struct User {
var name = "Tom"
let age = 32
var birthday = Date()
}
// Get metadata of struct
let ptr = unsafeBitCast(User.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)
print("0x\(String(ptr.pointee.kind, radix: 16))") // 0x200 <--- struct
let descriptorPointer = ptr.pointee.descriptor
let name = descriptorPointer.pointee.name.advanced()
print("Type name : \(String(cString: name))")
print("parent: \(descriptorPointer.pointee.parent.advanced().pointee)")
let propertyCount = Int(descriptorPointer.pointee.numFields)
print("property count: \(propertyCount)")
print("properties:")
let offset = ptr.pointee.getFieldOffsets()
let genericVector = ptr.pointee.genericArgumentVector()
for index in 0..<propertyCount {
let propertyPointer = descriptorPointer.pointee.fields.advanced().pointee.getField(atIndex: Int32(index))
let type = propertyPointer.pointee.getType(genericContext: descriptorPointer, genericArguments: genericVector)
print("""
name: \(propertyPointer.pointee.getFieldName())
type: \(type))
is var: \(propertyPointer.pointee.flags.isVar)
""")
}
// MARK: Metadata of Struct
enum ContextDescriptorKind: UInt8 {
case module = 0
case `extension`
case anonymous // such as closure
case `protocol` // A protocol
case opaque // opaque alias
case `class` = 16
case `struct`
case `enum`
init?(flags: ContextDescriptorFlags) {
if let kind = ContextDescriptorKind(rawValue: numericCast(flags & 0x1F)) {
self = kind
} else {
return nil
}
}
}
// MARK: Struct
struct StructMetadata {
var kind: Int
var descriptor: UnsafeMutablePointer<StructTypeDescriptor>
var genericArguemntOffset: Int {
// It's at offset 2; 0 is kind, 1 is descriptor
return 2
}
// Number of properties
func getNumberOfFields() -> Int {
return Int(descriptor.pointee.numFields)
}
// Get offset of each field (property)
mutating func getFieldOffsets() -> [Int] {
let offset = descriptor.pointee.fieldOffsetVectorOffset
let count = getNumberOfFields()
return withUnsafePointer(to: &self) { p in
let begin = UnsafeRawPointer(p).assumingMemoryBound(to: Int.self)
return begin.advanced(by: numericCast(offset))
.raw.assumingMemoryBound(to: UInt32.self) // fieldOffsetVectorOffset is UInt32
.makeBuffer(ofLength: count).map(numericCast)
}
}
mutating func genericArgumentVector() -> UnsafeMutablePointer<Any.Type> {
let genericAgrOffset = self.genericArguemntOffset
return withUnsafeMutablePointer(to: &self) { p in
return p.raw.advanced(by: genericAgrOffset * MemoryLayout<UnsafeRawPointer>.size)
.assumingMemoryBound(to: Any.Type.self)
}
}
}
// struct nominal type descriptor
typealias ContextDescriptorFlags = UInt32
/// [Swift Source Code] Metadata.h -> class TargetTypeContextDescriptor
struct StructTypeDescriptor {
var flags: ContextDescriptorFlags
var parent: RelativePointer<Int32, Kind> // should always be 0, which means null
var name: RelativePointer<Int32, CChar>
/// A pointer to the metadata access function for this type.
///
/// The function type here is a stand-in. You should use getAccessFunction()
/// to wrap the function pointer in an accessor that uses the proper calling
/// convention for a given number of arguments
var accessFunctionPtr: RelativePointer<Int32, UnsafeRawPointer>
var fields: RelativePointer<Int32, FieldDescriptor>
var numFields: UInt32
/// The offset of the field offset vector for this struct's stored
/// properties in its metadata, if any. 0 means there is no field offset
/// vector.
var fieldOffsetVectorOffset: UInt32
var hasStoredProperties: Bool {
return fieldOffsetVectorOffset != 0
}
}
// [Swift Source Code] Records.h
struct FieldDescriptor {
// enum class FieldDescriptorKind : uint16_t
enum Kind: UInt16 {
case `struct`, `class`, `enum`
// Fixed-size multi-payload enums have a special descriptor format that
// encodes spare bits.
case multipPayloadEnum
// A Swift opaque protocol. There are no fields, just a record for the
// type itself.
case `protocol`
// A Swift class-bound protocol.
case classProtocol
// An Objective-C protocol, which may be imported or defined in Swift.
case objCProtocol
// An Objective-C class, which may be imported or defined in Swift.
// In the former case, field type metadata is not emitted, and
// must be obtained from the Objective-C runtime.
case objCClass
}
var mangeldTypeName: RelativePointer<Int32, CChar>
var superClass: RelativePointer<Int32, CChar>
var fieldDescriptorKind: Kind
var fieldRecordSize: UInt16
var numFields: UInt32
// Get record of each field
mutating func getField(atIndex index: Int32) -> UnsafeMutablePointer<FieldRecord> {
return withUnsafeMutablePointer(to: &self) { p in
return p.advanced(by: 1).raw
.assumingMemoryBound(to: FieldRecord.self)
.advanced(by: numericCast(index))
}
}
}
struct FieldRecord {
// FieldRecordFlags
struct Flags {
private enum Constants {
// Is this an indirect enum case?
static let isIndirectCase: UInt32 = 0x1
// Is this a mutable var property
static let isVar: UInt32 = 0x2
// Is this an artificial field
static let isArtificial: UInt32 = 0x4
}
private var data: UInt32
public var isIndirectCase: Bool {
return (data & Constants.isIndirectCase) == Constants.isIndirectCase
}
public var isVar: Bool {
return (data & Constants.isVar) == Constants.isVar
}
public var isArtificial: Bool {
return (data & Constants.isArtificial) == Constants.isArtificial
}
}
var flags: Flags
var mangledtypeName: RelativePointer<Int32, UInt8> // The `Int8` here is actually CChar
var fieldName: RelativePointer<Int32, CChar>
// let or var
var isVar: Bool {
return flags.isVar
}
// Get name of this property
mutating func getFieldName() -> String {
return String(cString: fieldName.advanced())
}
// Get type of this property
mutating func getType(genericContext: UnsafeRawPointer?,
genericArguments: UnsafeRawPointer?) -> Any.Type {
let typeName = mangledtypeName.advanced()
return _getTypeByMangledNameInContext(typeName,
UInt(getSymbolicMangledNameLength(typeName)),
genericContext: genericContext,
genericArguments: genericArguments)
}
// From `KeyPath.swift`
private func getSymbolicMangledNameLength(_ base: UnsafeRawPointer) -> Int {
var end = base
while let current = Optional(end.load(as: UInt8.self)), current != 0 {
// Skip the current character
end += 1
// Skip over a symbolic reference
if current >= 0x1 && current <= 0x17 {
end += 4
} else if current >= 0x18 && current <= 0x1F {
end += MemoryLayout<Int>.size
}
}
return end - base
}
}
// MARK: Pointer
struct RelativePointer<Offset: FixedWidthInteger, Pointee> {
var offset: Offset
mutating func advanced() -> UnsafeMutablePointer<Pointee> {
let offset = self.offset
return withUnsafeMutablePointer(to: &self) { p in
return p.raw
.advanced(by: numericCast(offset))
.assumingMemoryBound(to: Pointee.self)
}
}
}
extension UnsafeMutablePointer {
var raw: UnsafeMutableRawPointer {
return UnsafeMutableRawPointer(self)
}
}
extension UnsafePointer {
var raw: UnsafeRawPointer {
return UnsafeRawPointer(self)
}
func makeBuffer(ofLength length: Int) -> UnsafeBufferPointer<Pointee> {
return UnsafeBufferPointer(start: self, count: length)
}
var mutable: UnsafeMutablePointer<Pointee> {
return UnsafeMutablePointer<Pointee>(mutating: self)
}
}
// MARK: Demangle
@_silgen_name("swift_getTypeByMangledNameInContext")
func _getTypeByMangledNameInContext(
_ name: UnsafePointer<UInt8>,
_ nameLength: UInt,
genericContext: UnsafeRawPointer?,
genericArguments: UnsafeRawPointer?)
-> Any.Type
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment