blob: ecb24b64b70146cee44b41a012dc68eb2106e5b0 [file] [log] [blame]
//===--- SwiftReflectionTest.swift ----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file provides infrastructure for introspecting type information in a
// remote swift executable by swift-reflection-test, using pipes and a
// request-response protocol to communicate with the test tool.
//
//===----------------------------------------------------------------------===//
// FIXME: Make this work with Linux
import MachO
import Darwin
let RequestInstanceKind = "k"
let RequestInstanceAddress = "i"
let RequestReflectionInfos = "r"
let RequestImages = "m"
let RequestReadBytes = "b"
let RequestSymbolAddress = "s"
let RequestStringLength = "l"
let RequestDone = "d"
let RequestPointerSize = "p"
internal func debugLog(_ message: String) {
#if DEBUG_LOG
fputs("Child: \(message)\n", stderr)
fflush(stderr)
#endif
}
public enum InstanceKind : UInt8 {
case None
case Object
case Existential
case ErrorExistential
case Closure
}
/// Represents a section in a loaded image in this process.
internal struct Section {
/// The absolute start address of the section's data in this address space.
let startAddress: UnsafeRawPointer
/// The size of the section in bytes.
let size: UInt
}
/// Holds the addresses and sizes of sections related to reflection
internal struct ReflectionInfo : Sequence {
/// The name of the loaded image
internal let imageName: String
/// Reflection metadata sections
internal let fieldmd: Section?
internal let assocty: Section?
internal let builtin: Section?
internal let capture: Section?
internal let typeref: Section?
internal let reflstr: Section?
internal func makeIterator() -> AnyIterator<Section?> {
return AnyIterator([
fieldmd,
assocty,
builtin,
capture,
typeref,
reflstr
].makeIterator())
}
}
#if arch(x86_64) || arch(arm64)
typealias MachHeader = mach_header_64
#else
typealias MachHeader = mach_header
#endif
/// Get the location and size of a section in a binary.
///
/// - Parameter name: The name of the section
/// - Parameter imageHeader: A pointer to the Mach header describing the
/// image.
/// - Returns: A `Section` containing the address and size, or `nil` if there
/// is no section by the given name.
internal func getSectionInfo(_ name: String,
_ imageHeader: UnsafePointer<MachHeader>) -> Section? {
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
var size: UInt = 0
let address = getsectiondata(imageHeader, "__TEXT", name, &size)
guard let nonNullAddress = address else { return nil }
guard size != 0 else { return nil }
return Section(startAddress: nonNullAddress, size: size)
}
/// Get the Swift Reflection section locations for a loaded image.
///
/// An image of interest must have the following sections in the __DATA
/// segment:
/// - __swift5_fieldmd
/// - __swift5_assocty
/// - __swift5_builtin
/// - __swift5_capture
/// - __swift5_typeref
/// - __swift5_reflstr (optional, may have been stripped out)
///
/// - Parameter i: The index of the loaded image as reported by Dyld.
/// - Returns: A `ReflectionInfo` containing the locations of all of the
/// needed sections, or `nil` if the image doesn't contain all of them.
internal func getReflectionInfoForImage(atIndex i: UInt32) -> ReflectionInfo? {
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
let header = unsafeBitCast(_dyld_get_image_header(i),
to: UnsafePointer<MachHeader>.self)
let imageName = _dyld_get_image_name(i)!
let fieldmd = getSectionInfo("__swift5_fieldmd", header)
let assocty = getSectionInfo("__swift5_assocty", header)
let builtin = getSectionInfo("__swift5_builtin", header)
let capture = getSectionInfo("__swift5_capture", header)
let typeref = getSectionInfo("__swift5_typeref", header)
let reflstr = getSectionInfo("__swift5_reflstr", header)
return ReflectionInfo(imageName: String(validatingUTF8: imageName)!,
fieldmd: fieldmd,
assocty: assocty,
builtin: builtin,
capture: capture,
typeref: typeref,
reflstr: reflstr)
}
/// Get the TEXT segment location and size for a loaded image.
///
/// - Parameter i: The index of the loaded image as reported by Dyld.
/// - Returns: The image name, address, and size.
internal func getAddressInfoForImage(atIndex i: UInt32) ->
(name: String, address: UnsafeMutablePointer<UInt8>?, size: UInt) {
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
let header = unsafeBitCast(_dyld_get_image_header(i),
to: UnsafePointer<MachHeader>.self)
let name = String(validatingUTF8: _dyld_get_image_name(i)!)!
var size: UInt = 0
let address = getsegmentdata(header, "__TEXT", &size)
return (name, address, size)
}
internal func sendBytes<T>(from address: UnsafePointer<T>, count: Int) {
var source = address
var bytesLeft = count
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
while bytesLeft > 0 {
let bytesWritten = fwrite(source, 1, bytesLeft, stdout)
fflush(stdout)
guard bytesWritten > 0 else {
fatalError("Couldn't write to parent pipe")
}
bytesLeft -= bytesWritten
source = source.advanced(by: bytesWritten)
}
}
/// Send the address of an object to the parent.
internal func sendAddress(of instance: AnyObject) {
debugLog("BEGIN \(#function)")
defer { debugLog("END \(#function)") }
var address = Unmanaged.passUnretained(instance).toOpaque()
sendBytes(from: &address, count: MemoryLayout<UInt>.size)
}
/// Send the `value`'s bits to the parent.
internal func sendValue<T>(_ value: T) {
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
var value = value
sendBytes(from: &value, count: MemoryLayout<T>.size)
}
/// Read a word-sized unsigned integer from the parent.
internal func readUInt() -> UInt {
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
var value: UInt = 0
fread(&value, MemoryLayout<UInt>.size, 1, stdin)
return value
}
/// Send all known `ReflectionInfo`s for all images loaded in the current
/// process.
internal func sendReflectionInfos() {
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
let infos = (0..<_dyld_image_count()).compactMap(getReflectionInfoForImage)
var numInfos = infos.count
debugLog("\(numInfos) reflection info bundles.")
precondition(numInfos >= 1)
sendBytes(from: &numInfos, count: MemoryLayout<UInt>.size)
for info in infos {
debugLog("Sending info for \(info.imageName)")
for section in info {
sendValue(section?.startAddress)
sendValue(section?.size ?? 0)
}
}
}
/// Send all loadedimages loaded in the current process.
internal func sendImages() {
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
let infos = (0..<_dyld_image_count()).map(getAddressInfoForImage)
debugLog("\(infos.count) reflection info bundles.")
precondition(infos.count >= 1)
sendValue(infos.count)
for (name, address, size) in infos {
debugLog("Sending info for \(name)")
sendValue(address)
sendValue(size)
}
}
internal func printErrnoAndExit() {
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
let errorCString = strerror(errno)!
let message = String(validatingUTF8: errorCString)! + "\n"
let bytes = Array(message.utf8)
fwrite(bytes, 1, bytes.count, stderr)
fflush(stderr)
exit(EXIT_FAILURE)
}
/// Retrieve the address and count from the parent and send the bytes back.
internal func sendBytes() {
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
let address = readUInt()
let count = Int(readUInt())
debugLog("Parent requested \(count) bytes from \(address)")
var totalBytesWritten = 0
var pointer = UnsafeMutableRawPointer(bitPattern: address)
while totalBytesWritten < count {
let bytesWritten = Int(fwrite(pointer, 1, Int(count), stdout))
fflush(stdout)
if bytesWritten == 0 {
printErrnoAndExit()
}
totalBytesWritten += bytesWritten
pointer = pointer?.advanced(by: bytesWritten)
}
}
/// Send the address of a symbol loaded in this process.
internal func sendSymbolAddress() {
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
let name = readLine()!
name.withCString {
let handle = UnsafeMutableRawPointer(bitPattern: Int(-2))!
let symbol = dlsym(handle, $0)
let symbolAddress = unsafeBitCast(symbol, to: UInt.self)
sendValue(symbolAddress)
}
}
/// Send the length of a string to the parent.
internal func sendStringLength() {
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
let address = readUInt()
let cString = UnsafePointer<CChar>(bitPattern: address)!
let count = String(validatingUTF8: cString)!.utf8.count
sendValue(count)
}
/// Send the size of this architecture's pointer type.
internal func sendPointerSize() {
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
let pointerSize = UInt8(MemoryLayout<UnsafeRawPointer>.size)
sendValue(pointerSize)
}
/// Hold an `instance` and wait for the parent to query for information.
///
/// This is the main "run loop" of the test harness.
///
/// The parent will necessarily need to:
/// - Get the addresses of any swift dylibs that are loaded, where applicable.
/// - Get the address of the `instance`
/// - Get the pointer size of this process, which affects assumptions about the
/// the layout of runtime structures with pointer-sized fields.
/// - Read raw bytes out of this process's address space.
///
/// The parent sends a Done message to indicate that it's done
/// looking at this instance. It will continue to ask for instances,
/// so call doneReflecting() when you don't have any more instances.
internal func reflect(instanceAddress: UInt, kind: InstanceKind) {
while let command = readLine(strippingNewline: true) {
switch command {
case String(validatingUTF8: RequestInstanceKind)!:
sendValue(kind.rawValue)
case String(validatingUTF8: RequestInstanceAddress)!:
sendValue(instanceAddress)
case String(validatingUTF8: RequestReflectionInfos)!:
sendReflectionInfos()
case String(validatingUTF8: RequestImages)!:
sendImages()
case String(validatingUTF8: RequestReadBytes)!:
sendBytes()
case String(validatingUTF8: RequestSymbolAddress)!:
sendSymbolAddress()
case String(validatingUTF8: RequestStringLength)!:
sendStringLength()
case String(validatingUTF8: RequestPointerSize)!:
sendPointerSize()
case String(validatingUTF8: RequestDone)!:
return
default:
fatalError("Unknown request received: '\(Array(command.utf8))'!")
}
}
}
/// Reflect a class instance.
///
/// This reflects the stored properties of the immediate class.
/// The superclass is not (yet?) visited.
public func reflect(object: AnyObject) {
defer { _fixLifetime(object) }
let address = Unmanaged.passUnretained(object).toOpaque()
let addressValue = UInt(bitPattern: address)
reflect(instanceAddress: addressValue, kind: .Object)
}
/// Reflect any type at all by boxing it into an existential container (an `Any`)
///
/// Given a class, this will reflect the reference value, and not the contents
/// of the instance. Use `reflect(object:)` for that.
///
/// This function serves to exercise the projectExistential function of the
/// SwiftRemoteMirror API.
///
/// It tests the three conditions of existential layout:
///
/// ## Class existentials
///
/// For example, a `MyClass as Any`:
/// ```
/// [Pointer to class instance]
/// [Witness table 1]
/// [Witness table 2]
/// ...
/// [Witness table n]
/// ```
///
/// ## Existentials whose contained type fits in the 3-word buffer
///
/// For example, a `(1, 2) as Any`:
/// ```
/// [Tuple element 1: Int]
/// [Tuple element 2: Int]
/// [-Empty_]
/// [Metadata Pointer]
/// [Witness table 1]
/// [Witness table 2]
/// ...
/// [Witness table n]
/// ```
///
/// ## Existentials whose contained type has to be allocated into a
/// heap buffer.
///
/// For example, a `LargeStruct<T> as Any`:
/// ```
/// [Pointer to unmanaged heap container] --> [Large struct]
/// [-Empty-]
/// [-Empty-]
/// [Metadata Pointer]
/// [Witness table 1]
/// [Witness table 2]
/// ...
/// [Witness table n]
/// ```
///
/// The test doesn't care about the witness tables - we only care
/// about what's in the buffer, so we always put these values into
/// an Any existential.
public func reflect<T>(any: T) {
let any: Any = any
let anyPointer = UnsafeMutablePointer<Any>.allocate(capacity: MemoryLayout<Any>.size)
anyPointer.initialize(to: any)
let anyPointerValue = UInt(bitPattern: anyPointer)
reflect(instanceAddress: anyPointerValue, kind: .Existential)
anyPointer.deallocate()
}
// Reflect an `Error`, a.k.a. an "error existential".
//
// These are always boxed on the heap, with the following layout:
//
// - Word 0: Metadata Pointer
// - Word 1: 2x 32-bit reference counts
//
// If Objective-C interop is available, an Error is also an
// `NSError`, and so has:
//
// - Word 2: code (NSInteger)
// - Word 3: domain (NSString *)
// - Word 4: userInfo (NSDictionary *)
//
// Then, always follow:
//
// - Word 2 or 5: Instance type metadata pointer
// - Word 3 or 6: Instance witness table for conforming
// to `Swift.Error`.
//
// Following that is the instance that conforms to `Error`,
// rounding up to its alignment.
public func reflect<T: Error>(error: T) {
let error: Error = error
let errorPointerValue = unsafeBitCast(error, to: UInt.self)
reflect(instanceAddress: errorPointerValue, kind: .ErrorExistential)
}
/// Wraps a thick function with arity 0.
struct ThickFunction0 {
var function: () -> Void
}
/// Wraps a thick function with arity 1.
struct ThickFunction1 {
var function: (Int) -> Void
}
/// Wraps a thick function with arity 2.
struct ThickFunction2 {
var function: (Int, String) -> Void
}
/// Wraps a thick function with arity 3.
struct ThickFunction3 {
var function: (Int, String, AnyObject?) -> Void
}
struct ThickFunctionParts {
var function: UnsafeRawPointer
var context: Optional<UnsafeRawPointer>
}
/// Reflect a closure context. The given function must be a Swift-native
/// @convention(thick) function value.
public func reflect(function: @escaping () -> Void) {
let fn = UnsafeMutablePointer<ThickFunction0>.allocate(
capacity: MemoryLayout<ThickFunction0>.size)
fn.initialize(to: ThickFunction0(function: function))
let contextPointer = fn.withMemoryRebound(
to: ThickFunctionParts.self, capacity: 1) {
UInt(bitPattern: $0.pointee.context)
}
reflect(instanceAddress: contextPointer, kind: .Object)
fn.deallocate()
}
/// Reflect a closure context. The given function must be a Swift-native
/// @convention(thick) function value.
public func reflect(function: @escaping (Int) -> Void) {
let fn =
UnsafeMutablePointer<ThickFunction1>.allocate(
capacity: MemoryLayout<ThickFunction1>.size)
fn.initialize(to: ThickFunction1(function: function))
let contextPointer = fn.withMemoryRebound(
to: ThickFunctionParts.self, capacity: 1) {
UInt(bitPattern: $0.pointee.context)
}
reflect(instanceAddress: contextPointer, kind: .Object)
fn.deallocate()
}
/// Reflect a closure context. The given function must be a Swift-native
/// @convention(thick) function value.
public func reflect(function: @escaping (Int, String) -> Void) {
let fn = UnsafeMutablePointer<ThickFunction2>.allocate(
capacity: MemoryLayout<ThickFunction2>.size)
fn.initialize(to: ThickFunction2(function: function))
let contextPointer = fn.withMemoryRebound(
to: ThickFunctionParts.self, capacity: 1) {
UInt(bitPattern: $0.pointee.context)
}
reflect(instanceAddress: contextPointer, kind: .Object)
fn.deallocate()
}
/// Reflect a closure context. The given function must be a Swift-native
/// @convention(thick) function value.
public func reflect(function: @escaping (Int, String, AnyObject?) -> Void) {
let fn = UnsafeMutablePointer<ThickFunction3>.allocate(
capacity: MemoryLayout<ThickFunction3>.size)
fn.initialize(to: ThickFunction3(function: function))
let contextPointer = fn.withMemoryRebound(
to: ThickFunctionParts.self, capacity: 1) {
UInt(bitPattern: $0.pointee.context)
}
reflect(instanceAddress: contextPointer, kind: .Object)
fn.deallocate()
}
/// Call this function to indicate to the parent that there are
/// no more instances to look at.
public func doneReflecting() {
reflect(instanceAddress: UInt(InstanceKind.None.rawValue), kind: .None)
}
/* Example usage
public protocol P {
associatedtype Index
var startIndex: Index { get }
}
public struct Thing : P {
public let startIndex = 1010
}
public enum E<T: P> {
case B(T)
case C(T.Index)
}
public class A<T: P> : P {
public let x: T?
public let y: T.Index
public let b = true
public let t = (1, 1.0)
private let type: NSObject.Type
public let startIndex = 1010
public init(x: T) {
self.x = x
self.y = x.startIndex
self.type = NSObject.self
}
}
let instance = A(x: A(x: Thing()))
reflect(A(x: Thing()))
*/