Skip to content

Commit 0f62e66

Browse files
committed
Add SwiftReflectionTest helper library
This is a small helper library to communicate information back to swift-reflection-test from a test swift executable. Each swift test file under test/Reflection should link this library to get the main test hook to send responses back to the test tool.
1 parent 0dcd813 commit 0f62e66

File tree

3 files changed

+333
-0
lines changed

3 files changed

+333
-0
lines changed

stdlib/private/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
1212
add_subdirectory(StdlibUnittestFoundationExtras)
1313
add_subdirectory(SwiftPrivateLibcExtras)
1414
add_subdirectory(SwiftPrivatePthreadExtras)
15+
add_subdirectory(SwiftReflectionTest)
1516
endif()
1617
endif()
1718

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
set(swift_reflection_test_compile_flags
2+
"-Xfrontend" "-enable-reflection-metadata")
3+
4+
add_swift_library(swiftReflectionTest SHARED IS_STDLIB
5+
SwiftReflectionTest.swift
6+
SWIFT_COMPILE_FLAGS ${swift_reflection_test_compile_flags}
7+
INSTALL_IN_COMPONENT stdlib-experimental)
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
//===--- ReflectionPipe.swift ---------------------------------*- swift -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file provides infrastructure for introspecting type information in a
14+
// remote swift executable by swift-reflection-test, using pipes and a
15+
// request-response protocol to communicate with the test tool.
16+
//
17+
//===----------------------------------------------------------------------===//
18+
19+
// FIXME: Make this work with Linux
20+
21+
import Foundation
22+
import Darwin
23+
24+
let RequestInstanceAddress = "i"
25+
let RequestReflectionInfos = "r"
26+
let RequestReadBytes = "b";
27+
let RequestSymbolAddress = "s"
28+
let RequestStringLength = "l"
29+
let RequestExit = "e"
30+
let RequestPointerSize = "p"
31+
32+
internal func debugLog(message: String) {
33+
#if DEBUG_LOG
34+
fputs("Child: \(message)\n", stderr)
35+
fflush(stderr)
36+
#endif
37+
}
38+
39+
/// Represents a section in a loaded image in this process.
40+
internal struct Section {
41+
/// The absolute start address of the section's data in this address space.
42+
let startAddress: UnsafePointer<Void>
43+
44+
/// The size of the section in bytes.
45+
let size: UInt
46+
}
47+
48+
/// Holds the addresses and sizes of sections related to reflection
49+
internal struct ReflectionInfo : Sequence {
50+
/// The name of the laoded image
51+
internal let imageName: String
52+
53+
/// The Field Metadata section
54+
internal let fieldmd: Section
55+
56+
/// The Typeref section
57+
internal let typeref: Section
58+
59+
/// The Reflection Strings section, which holds property names and other
60+
/// general-purpose strings.
61+
///
62+
/// This section can be stripped out by having compiled with
63+
/// `-strip-reflection-names` and so is optional.
64+
internal let reflstr: Section?
65+
66+
/// The Associated Types section, which indicates which type aliases
67+
/// are provided by a type to conform to some protocol's associated
68+
/// type requirements.
69+
internal let assocty: Section
70+
71+
internal func makeIterator() -> AnyIterator<Section> {
72+
return AnyIterator([
73+
fieldmd,
74+
typeref,
75+
reflstr ?? Section(startAddress: nil, size: 0),
76+
assocty
77+
].makeIterator())
78+
}
79+
}
80+
81+
#if arch(x86_64) || arch(arm64)
82+
typealias MachHeader = mach_header_64
83+
#else
84+
typealias MachHeader = mach_header
85+
#endif
86+
87+
/// Get the location and size of a section in a binary.
88+
///
89+
/// - Parameter name: The name of the section
90+
/// - Parameter imageHeader: A pointer to the Mach header describing the
91+
/// image.
92+
/// - Returns: A `Section` containing the address and size, or `nil` if there
93+
/// is no section by the given name.
94+
internal func getSectionInfo(name: String,
95+
_ imageHeader: UnsafePointer<MachHeader>) -> Section? {
96+
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
97+
var section: Section? = nil
98+
name.withCString {
99+
var size: UInt = 0
100+
let address = getsectiondata(imageHeader, "__DATA", $0, &size)
101+
guard address != nil else { return }
102+
guard size != 0 else { return }
103+
section = Section(startAddress: address, size: size)
104+
}
105+
return section
106+
}
107+
108+
/// Get the Swift Reflection section locations for a loaded image.
109+
///
110+
/// An image of interest must have the following sections in the __DATA
111+
/// segment:
112+
/// - __swift3_fieldmd
113+
/// - __swift3_typeref
114+
/// - __swift3_assocty
115+
/// - __swift3_reflstr (optional, may have been stripped out)
116+
///
117+
/// - Parameter i: The index of the loaded image as reported by Dyld.
118+
/// - Returns: A `ReflectionInfo` containing the locations of all of the
119+
/// needed sections, or `nil` if the image doesn't contain all of them.
120+
internal func getReflectionInfoForImage(atIndex i: UInt32) -> ReflectionInfo? {
121+
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
122+
let header = unsafeBitCast(_dyld_get_image_header(i),
123+
to: UnsafePointer<mach_header_64>.self)
124+
125+
let imageName = _dyld_get_image_name(i)
126+
if let fieldmd = getSectionInfo("__swift3_fieldmd", header),
127+
let typeref = getSectionInfo("__swift3_typeref", header),
128+
let assocty = getSectionInfo("__swift3_assocty", header) {
129+
let reflstr = getSectionInfo("__swift3_reflstr", header)
130+
return ReflectionInfo(imageName: String(validatingUTF8: imageName)!,
131+
fieldmd: fieldmd,
132+
typeref: typeref,
133+
reflstr: reflstr,
134+
assocty: assocty)
135+
}
136+
return nil
137+
}
138+
139+
internal func sendBytes<T>(from address: UnsafePointer<T>, count: Int) {
140+
var source = address
141+
var bytesLeft = count
142+
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
143+
while bytesLeft > 0 {
144+
let bytesWritten = fwrite(source, 1, bytesLeft, stdout)
145+
fflush(stdout)
146+
guard bytesWritten > 0 else {
147+
fatalError("Couldn't write to parent pipe")
148+
}
149+
bytesLeft -= bytesWritten
150+
source = source.advanced(by: bytesWritten)
151+
}
152+
}
153+
154+
/// Send the address of an object to the parent.
155+
internal func sendAddress(of instance: AnyObject) {
156+
debugLog("BEGIN \(#function)")
157+
defer { debugLog("END \(#function)") }
158+
var address = unsafeAddress(of: instance)
159+
sendBytes(from: &address, count: sizeof(UInt.self))
160+
}
161+
162+
/// Send the `value`'s bits to the parent.
163+
internal func sendValue<T>(value: T) {
164+
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
165+
var value = value
166+
sendBytes(from: &value, count: sizeof(T.self))
167+
}
168+
169+
/// Read a word-sized unsigned integer from the parent.
170+
internal func readUInt() -> UInt {
171+
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
172+
var value: UInt = 0
173+
fread(&value, sizeof(UInt.self), 1, stdin)
174+
return value
175+
}
176+
177+
/// Send all known `ReflectionInfo`s for all images loaded in the current
178+
/// process.
179+
internal func sendReflectionInfos() {
180+
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
181+
let infos = (0..<_dyld_image_count()).flatMap(getReflectionInfoForImage)
182+
183+
var numInfos = infos.count
184+
debugLog("\(numInfos) reflection info bundles.")
185+
sendBytes(from: &numInfos, count: sizeof(UInt.self))
186+
precondition(numInfos >= 1)
187+
for info in infos {
188+
debugLog("Sending info for \(info.imageName)")
189+
let imageNameBytes = Array(info.imageName.utf8)
190+
var imageNameLength = UInt64(imageNameBytes.count)
191+
fwrite(&imageNameLength, sizeof(UInt64.self), 1, stdout)
192+
fflush(stdout)
193+
fwrite(imageNameBytes, 1, imageNameBytes.count, stdout)
194+
fflush(stdout)
195+
for section in info {
196+
sendValue(section.startAddress)
197+
sendValue(section.size)
198+
}
199+
}
200+
}
201+
202+
internal func printErrnoAndExit() {
203+
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
204+
let errorCString = strerror(errno)
205+
let message = String(validatingUTF8: errorCString)! + "\n"
206+
let bytes = Array(message.utf8)
207+
fwrite(bytes, 1, bytes.count, stderr)
208+
fflush(stderr)
209+
exit(EXIT_FAILURE)
210+
}
211+
212+
/// Retrieve the address and count from the parent and send the bytes back.
213+
internal func sendBytes() {
214+
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
215+
let address = readUInt()
216+
let count = Int(readUInt())
217+
debugLog("Parent requested \(count) bytes from \(address)")
218+
var totalBytesWritten = 0
219+
var pointer = unsafeBitCast(address, to: UnsafeMutablePointer<Void>.self)
220+
while totalBytesWritten < count {
221+
let bytesWritten = Int(fwrite(pointer, 1, Int(count), stdout))
222+
fflush(stdout)
223+
if bytesWritten == 0 {
224+
printErrnoAndExit()
225+
}
226+
totalBytesWritten += bytesWritten
227+
pointer = pointer.advanced(by: bytesWritten)
228+
}
229+
}
230+
231+
/// Send the address of a symbol loaded in this process.
232+
internal func sendSymbolAddress() {
233+
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
234+
let name = readLine()!
235+
name.withCString {
236+
let handle = unsafeBitCast(Int(-2), to: UnsafeMutablePointer<Void>.self)
237+
let symbol = dlsym(handle, $0)
238+
let symbolAddress = unsafeBitCast(symbol, to: UInt64.self)
239+
sendValue(symbolAddress)
240+
}
241+
}
242+
243+
/// Send the length of a string to the parent.
244+
internal func sendStringLength() {
245+
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
246+
let address = readUInt()
247+
let cString = unsafeBitCast(address, to: UnsafePointer<CChar>.self)
248+
let count = String(validatingUTF8: cString)!.utf8.count
249+
sendValue(count)
250+
}
251+
252+
/// Send the size of this architecture's pointer type.
253+
internal func sendPointerSize() {
254+
debugLog("BEGIN \(#function)"); defer { debugLog("END \(#function)") }
255+
let pointerSize = UInt8(sizeof(UnsafePointer<Void>.self))
256+
sendValue(pointerSize)
257+
}
258+
259+
/// Hold an `instance` and wait for the parent to query for information.
260+
///
261+
/// This is the main "run loop" of the test harness.
262+
///
263+
/// The parent will necessarily need to:
264+
/// - Get the addresses of all of the reflection sections for any swift dylibs
265+
/// that are loaded, where applicable.
266+
/// - Get the address of the `instance`
267+
/// - Get the pointer size of this process, which affects assumptions about the
268+
/// the layout of runtime structures with pointer-sized fields.
269+
/// - Read raw bytes out of this process's address space.
270+
public func reflect(instance: AnyObject) {
271+
while let command = readLine(strippingNewline: true) {
272+
switch command {
273+
case String(validatingUTF8: RequestInstanceAddress)!:
274+
sendAddress(of: instance)
275+
case String(validatingUTF8: RequestReflectionInfos)!:
276+
sendReflectionInfos()
277+
case String(validatingUTF8: RequestReadBytes)!:
278+
sendBytes()
279+
case String(validatingUTF8: RequestSymbolAddress)!:
280+
sendSymbolAddress()
281+
case String(validatingUTF8: RequestStringLength)!:
282+
sendStringLength()
283+
case String(validatingUTF8: RequestPointerSize)!:
284+
sendPointerSize();
285+
case String(validatingUTF8: RequestExit)!:
286+
exit(EXIT_SUCCESS)
287+
default:
288+
fatalError("Unknown request received!")
289+
}
290+
}
291+
}
292+
293+
/* Example usage
294+
295+
public protocol P {
296+
associatedtype Index
297+
var startIndex: Index { get }
298+
}
299+
300+
public struct Thing : P {
301+
public let startIndex = 1010
302+
}
303+
304+
public enum E<T: P> {
305+
case B(T)
306+
case C(T.Index)
307+
}
308+
309+
public class A<T: P> : P {
310+
public let x: T?
311+
public let y: T.Index
312+
public let b = true
313+
public let t = (1, 1.0)
314+
private let type: NSObject.Type
315+
public let startIndex = 1010
316+
public init(x: T) {
317+
self.x = x
318+
self.y = x.startIndex
319+
self.type = NSObject.self
320+
}
321+
}
322+
let instance = A(x: A(x: Thing()))
323+
324+
reflect(A(x: Thing()))
325+
*/

0 commit comments

Comments
 (0)