Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
.build/
.swiftpm/
Package.resolved
/Echo.xcodeproj
/Project.swift
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ let package = Package(
)
],
dependencies: [
.package(url: "https://github.com/apple/swift-atomics.git", from: "0.0.1")
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.0")
],
targets: [
.target(
Expand Down
7 changes: 6 additions & 1 deletion Sources/Echo/ContextDescriptor/ContextDescriptorValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,12 @@ public struct TypeContextDescriptorFlags {

/// The resilient superclass type reference kind.
public var resilientSuperclassRefKind: TypeReferenceKind {
TypeReferenceKind(rawValue: UInt16(bits) & 0xE00)!
// The reference kind occupies a 3-bit field starting at bit 9, so it must
// be shifted down after masking — without the shift, any non-direct kind
// (e.g. the indirect reference used for a cross-module resilient
// superclass) produces a value like 0x200 that is not a valid
// TypeReferenceKind raw value and traps the force-unwrap.
TypeReferenceKind(rawValue: UInt16(bits & (0x7 << 9)) >> 9)!
}

/// Whether or not this class has any immediate members negative.
Expand Down
2 changes: 1 addition & 1 deletion Sources/Echo/ContextDescriptor/GenericContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public struct GenericRequirementDescriptor: LayoutWrapper {
and: UInt8.self
)
).signed
return ProtocolDescriptor(ptr: ptr)
return ProtocolDescriptor(ptr: ptr!)
}

/// If this requirement is some layout (currently can only be a class),
Expand Down
15 changes: 12 additions & 3 deletions Sources/Echo/Metadata/ClassMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ public struct ClassMetadata: TypeMetadata, LayoutWrapper {
public let ptr: UnsafeRawPointer

/// The class context descriptor that describes this class.
public var descriptor: ClassDescriptor {
public var descriptor: ClassDescriptor? {
precondition(isSwiftClass)
return ClassDescriptor(ptr: layout._descriptor.signed)

if let descriptorPtr = layout._descriptor.signed {
return ClassDescriptor(ptr: descriptorPtr)
}

return nil
}

/// The Objective-C ISA pointer, if it has one.
Expand Down Expand Up @@ -101,7 +106,11 @@ public struct ClassMetadata: TypeMetadata, LayoutWrapper {

/// An array of field offsets for this class's stored representation.
public var fieldOffsets: [Int] {
Array(unsafeUninitializedCapacity: descriptor.numFields) {
guard let descriptor = descriptor else {
return []
}

return Array(unsafeUninitializedCapacity: descriptor.numFields) {
let start = ptr.offset(of: descriptor.fieldOffsetVectorOffset)

for i in 0 ..< descriptor.numFields {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Echo/Metadata/MetadataAccessFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ func createMetadataAccessBuffer(
// First loop is inserting the key arguments at the front of the buffer.
for i in 0 ..< args.count {
buffer.storeBytes(
of: args[0].0,
of: args[i].0,
toByteOffset: ptrSize * i,
as: Any.Type.self
)
Expand Down
29 changes: 20 additions & 9 deletions Sources/Echo/Metadata/TypeMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,17 @@ extension TypeMetadata {
iterateSharedObjects()
#endif

guard let contextDescriptorPtr = contextDescriptor?.ptr else {
return []
}

return conformanceLock.withLock {
Echo.conformances[contextDescriptor.ptr, default: []]
Echo.conformances[contextDescriptorPtr, default: []]
}
}

/// The base type context descriptor for this type metadata record.
public var contextDescriptor: TypeContextDescriptor {
public var contextDescriptor: TypeContextDescriptor? {
switch self {
case let structMetadata as StructMetadata:
return structMetadata.descriptor
Expand All @@ -74,7 +78,7 @@ extension TypeMetadata {
}
}

var genericArgumentPtr: UnsafeRawPointer {
var genericArgumentPtr: UnsafeRawPointer? {
switch self {
case is StructMetadata:
return ptr + MemoryLayout<_StructMetadata>.size
Expand All @@ -83,7 +87,10 @@ extension TypeMetadata {
return ptr + MemoryLayout<_EnumMetadata>.size

case let classMetadata as ClassMetadata:
return ptr.offset(of: classMetadata.descriptor.genericArgumentOffset)
guard let descriptor = classMetadata.descriptor else {
return nil
}
return ptr.offset(of: descriptor.genericArgumentOffset)

default:
fatalError("Unknown TypeMetadata conformance")
Expand All @@ -93,17 +100,17 @@ extension TypeMetadata {
/// An array of types that represent the generic arguments that make up this
/// type.
public var genericTypes: [Any.Type] {
guard contextDescriptor.flags.isGeneric else {
guard let contextDescriptor = contextDescriptor,
contextDescriptor.flags.isGeneric,
// Explicitly only call this once because class metadata could require
// computation, so only do it once if needed.
let gap = genericArgumentPtr else {
return []
}

let numParams = contextDescriptor.genericContext!.numParams

return Array(unsafeUninitializedCapacity: numParams) {
// Explicitly only call this once because class metadata could require
// computation, so only do it once if needed.
let gap = genericArgumentPtr

for i in 0 ..< numParams {
let type = gap.load(
fromByteOffset: i * MemoryLayout<Any.Type>.stride,
Expand Down Expand Up @@ -142,6 +149,10 @@ extension TypeMetadata {
return entry!
}

guard let contextDescriptor = contextDescriptor else {
return nil
}

let length = getSymbolicMangledNameLength(mangledName)
let name = mangledName.assumingMemoryBound(to: UInt8.self)
let type = _getTypeByMangledNameInContext(
Expand Down
2 changes: 1 addition & 1 deletion Sources/Echo/Metadata/ValueWitnessTable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public struct ValueWitnessTable: LayoutWrapper {
let ptr: UnsafeRawPointer

var _vwt: _ValueWitnessTable {
layout.signed.load(as: _ValueWitnessTable.self)
layout.signed!.load(as: _ValueWitnessTable.self)
}

/// Given a buffer an instance of the type in the source buffer, initialize
Expand Down
18 changes: 12 additions & 6 deletions Sources/Echo/Runtime/ConformanceDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,22 @@ public struct ConformanceDescriptor: LayoutWrapper {
/// The context descriptor of the type being conformed.
public var contextDescriptor: TypeContextDescriptor? {
let start = address(for: \._typeRef)
let ptr: UnsafeRawPointer?

switch flags.typeReferenceKind {
case .directTypeDescriptor:
let ptr = start.relativeDirectAddress(as: _ContextDescriptor.self)
return getContextDescriptor(at: ptr) as? TypeContextDescriptor
ptr = start.relativeDirectAddress(as: _ContextDescriptor.self)
case .indirectTypeDescriptor:
var ptr = start.relativeDirectAddress(as: UnsafeRawPointer.self)
ptr = ptr.load(as: UnsafeRawPointer.self)
return getContextDescriptor(at: ptr) as? TypeContextDescriptor
ptr = start.relativeDirectAddress(as: UnsafeRawPointer?.self).load(as: UnsafeRawPointer?.self)
default:
return nil
}

if let ptr {
return getContextDescriptor(at: ptr) as? TypeContextDescriptor
}

return nil
}

/// The ObjectiveC class metadata of the type being conformed.
Expand All @@ -64,7 +68,9 @@ public struct ConformanceDescriptor: LayoutWrapper {
.assumingMemoryBound(to: CChar.self)

guard let anyClass = objc_lookUpClass(ptr) else {
fatalError("No Objective-C class named \(ptr.string)")
// A conformance with a nil class means the class was weak-linked
// from a newer SDK and isn't available in this version of iOS
return nil
}

return reflect(anyClass) as? ObjCClassWrapperMetadata
Expand Down
72 changes: 72 additions & 0 deletions Sources/Echo/Runtime/DynamicCast.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// DynamicCast.swift
// Echo
//
// Runtime dynamic casting: cast a value to a type that is only known at
// runtime (as `Metadata`), which the static `as?` operator cannot express.
//

import CEcho

/// Attempts to dynamically cast `value` to the type described by `targetType`,
/// returning the cast value on success or `nil` on failure.
///
/// This is the runtime equivalent of `value as? T`, but with the target type
/// supplied as `Metadata` rather than a static type — useful when the type to
/// cast to is itself discovered through reflection. It performs the same checks
/// the compiler emits for `as?`: class-hierarchy walks, protocol-conformance
/// lookups, bridging, and existential unwrapping.
/// - Parameters:
/// - value: The value to cast. It is not modified.
/// - targetType: Metadata for the type to cast to.
/// - Returns: The cast value boxed as `Any`, or `nil` if the cast fails.
public func dynamicCast(_ value: Any, to targetType: Metadata) -> Any? {
var container = container(for: value)
let sourceType = container.metadata

let sourceBuffer = sourceType.allocateValueBuffer()
let destinationBuffer = targetType.allocateValueBuffer()
defer {
sourceBuffer.deallocate()
destinationBuffer.deallocate()
}

// Copy the source into an owned buffer so the cast may consume it without
// disturbing the caller's value.
withValuePointer(of: value) { source in
sourceType.vwt.initializeWithCopy(sourceBuffer, source.mutable)
}

// DynamicCastFlags.takeOnSuccess (0x2) | .destroyOnFailure (0x4): a
// conditional cast that consumes `sourceBuffer`'s value on both the success
// and failure paths, so we only free its raw storage. (Bit 0x1 is
// Unconditional, which would trap on failure instead of returning false.)
let flags = 0x2 | 0x4
let didCast = swift_dynamicCast(
destinationBuffer,
sourceBuffer,
sourceType.ptr,
targetType.ptr,
flags
)

guard didCast else {
return nil
}

// On success `destinationBuffer` holds an initialized value of `targetType`.
defer { targetType.vwt.destroy(destinationBuffer) }
return targetType.value(at: destinationBuffer)
}

/// Attempts to dynamically cast `value` to `type`.
///
/// A convenience over ``dynamicCast(_:to:)-(Any,Metadata)`` that takes a
/// metatype instead of `Metadata`.
/// - Parameters:
/// - value: The value to cast. It is not modified.
/// - type: The type to cast to.
/// - Returns: The cast value boxed as `Any`, or `nil` if the cast fails.
public func dynamicCast(_ value: Any, to type: Any.Type) -> Any? {
dynamicCast(value, to: reflect(type))
}
37 changes: 31 additions & 6 deletions Sources/Echo/Runtime/ImageInspection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ let protocolLock = NSLock()
var _protocols = Set<UnsafeRawPointer>()

@_cdecl("registerProtocols")
func registerProtocols(section: UnsafeRawPointer, size: Int) {
public func registerProtocols(section: UnsafeRawPointer, size: Int) {
for i in 0 ..< size / 4 {
let start = section.offset(of: i, as: Int32.self)
let ptr = start.relativeDirectAddress(as: _ProtocolDescriptor.self)
Expand All @@ -75,7 +75,7 @@ let conformanceLock = NSLock()
var conformances = [UnsafeRawPointer: [ConformanceDescriptor]]()

@_cdecl("registerProtocolConformances")
func registerProtocolConformances(section: UnsafeRawPointer, size: Int) {
public func registerProtocolConformances(section: UnsafeRawPointer, size: Int) {
for i in 0 ..< size / 4 {
let start = section.offset(of: i, as: Int32.self)
let ptr = start.relativeDirectAddress(as: _ConformanceDescriptor.self)
Expand Down Expand Up @@ -137,11 +137,36 @@ let typeLock = NSLock()
var _types = Set<UnsafeRawPointer>()

@_cdecl("registerTypeMetadata")
func registerTypeMetadata(section: UnsafeRawPointer, size: Int) {
public func registerTypeMetadata(section: UnsafeRawPointer, size: Int) {
for i in 0 ..< size / 4 {
let start = section.offset(of: i, as: Int32.self)
let ptr = start.relativeDirectAddress(as: _ContextDescriptor.self)


// Each record is a RelativeDirectPointerIntPair<ContextDescriptor,
// TypeReferenceKind>: the low 2 bits of the relative offset encode the
// reference kind and must be masked off before resolving the pointer.
// Records whose kind is an indirect reference point at a GOT slot that
// holds the real descriptor, and ObjC class references never appear here.
let raw = Int(start.load(as: Int32.self))
let pointerOffset = raw & ~0x3

// A zero offset is a null/padding record; skip it.
guard pointerOffset != 0 else {
continue
}

let addr = start + pointerOffset
let ptr: UnsafeRawPointer

switch TypeReferenceKind(rawValue: UInt16(raw & 0x3)) {
case .directTypeDescriptor:
ptr = addr
case .indirectTypeDescriptor:
ptr = addr.load(as: UnsafeRawPointer.self)
default:
// .directObjCClass / .indirectObjCClass are never emitted into this list.
continue
}

_ = typeLock.withLock {
_types.insert(ptr)
}
Expand All @@ -161,7 +186,7 @@ typealias mach_header_platform = mach_header
#endif

@_cdecl("lookupSection")
func lookupSection(
public func lookupSection(
_ header: UnsafePointer<mach_header>?,
segment: UnsafePointer<CChar>?,
section: UnsafePointer<CChar>?,
Expand Down
Loading