Skip to content

Commit e0ce590

Browse files
authored
Revert "Replace call to objc_addLoadImageFunc(). (#1536)" (#1658)
This reverts commit fc0c105. This change caused problems for Playgrounds when using JIT linking, so revert for now as we explore other options. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 21e1d4c commit e0ce590

2 files changed

Lines changed: 96 additions & 66 deletions

File tree

Sources/_TestDiscovery/SectionBounds.swift

Lines changed: 96 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -48,35 +48,104 @@ struct SectionBounds: Sendable, BitwiseCopyable {
4848
#if SWT_TARGET_OS_APPLE && !SWT_NO_DYNAMIC_LINKING
4949
// MARK: - Apple implementation
5050

51-
/// Get section bounds for the given section from the given Mach header.
52-
///
53-
/// - Parameters:
54-
/// - segmentName: The Mach-O segment name of interest.
55-
/// - sectionName: The Mach-O section name of interest.
56-
/// - mh: The Mach header.
51+
extension SectionBounds.Kind {
52+
/// The Mach-O segment and section name for this instance as a pair of
53+
/// null-terminated UTF-8 C strings and pass them to a function.
54+
///
55+
/// The values of this property are instances of `StaticString` rather than
56+
/// `String` because the latter's inner storage is sometimes backed by
57+
/// Objective-C and touching it here can cause a recursive access to an
58+
/// internal libobjc lock, whereas `StaticString`'s internal storage is
59+
/// immediately available.
60+
fileprivate var segmentAndSectionName: (segmentName: StaticString, sectionName: StaticString) {
61+
switch self {
62+
case .testContent:
63+
("__DATA_CONST", "__swift5_tests")
64+
#if !SWT_NO_LEGACY_TEST_DISCOVERY
65+
case .typeMetadata:
66+
("__TEXT", "__swift5_types")
67+
#endif
68+
}
69+
}
70+
}
71+
72+
/// An array containing all of the test content section bounds known to the
73+
/// testing library.
5774
///
58-
/// - Returns: The requested section bounds, or `nil` if they couldn't be found.
59-
private func _sectionBounds(_ segmentName: String, _ sectionName: String, in mh: UnsafePointer<mach_header>) -> SectionBounds? {
75+
/// Indices into this array are equivalent to the `rawValue` values of instances
76+
/// of ``SectionBounds/Kind``.
77+
private nonisolated(unsafe) let _sectionBounds = {
78+
// We generate a contiguous array here rather than a dictionary because the
79+
// former has less overall bridging with the Objective-C runtime (reducing the
80+
// risk of reentrance while holding the libobjc lock) and because the set of
81+
// keys or indices is closed, so an array lookup is always more efficient than
82+
// a hashtable lookup.
83+
let kindCount = SectionBounds.Kind.allCases.count
84+
let result = ManagedBuffer<ContiguousArray<ContiguousArray<SectionBounds>>, pthread_mutex_t>.create(
85+
minimumCapacity: 1,
86+
makingHeaderWith: { _ in .init(repeating: [], count: kindCount) }
87+
)
88+
89+
result.withUnsafeMutablePointers { sectionBounds, lock in
90+
_ = pthread_mutex_init(lock, nil)
91+
92+
let imageCount = Int(clamping: _dyld_image_count())
93+
for kind in SectionBounds.Kind.allCases {
94+
sectionBounds.pointee[kind.rawValue].reserveCapacity(imageCount)
95+
}
96+
}
97+
98+
return result
99+
}()
100+
101+
/// A call-once function that initializes `_sectionBounds` and starts listening
102+
/// for loaded Mach headers.
103+
private let _startCollectingSectionBounds: Void = {
104+
// Ensure _sectionBounds is initialized before we touch libobjc or dyld.
105+
_ = _sectionBounds
106+
107+
func addSectionBounds(from mh: UnsafePointer<mach_header>) {
60108
#if _pointerBitWidth(_64)
61-
let mh = UnsafeRawPointer(mh).assumingMemoryBound(to: mach_header_64.self)
109+
let mh = UnsafeRawPointer(mh).assumingMemoryBound(to: mach_header_64.self)
62110
#endif
63111

64-
// Ignore this Mach header if it is in the shared cache. On platforms that
65-
// support it (Darwin), most system images are contained in this range.
66-
// System images can be expected not to contain test declarations, so we
67-
// don't need to walk them.
68-
guard 0 == mh.pointee.flags & MH_DYLIB_IN_CACHE else {
69-
return nil
70-
}
112+
// Ignore this Mach header if it is in the shared cache. On platforms that
113+
// support it (Darwin), most system images are contained in this range.
114+
// System images can be expected not to contain test declarations, so we
115+
// don't need to walk them.
116+
guard 0 == mh.pointee.flags & MH_DYLIB_IN_CACHE else {
117+
return
118+
}
71119

72-
var size = CUnsignedLong(0)
73-
if let start = getsectiondata(mh, segmentName, sectionName, &size), size > 0 {
74-
let buffer = UnsafeRawBufferPointer(start: start, count: Int(clamping: size))
75-
return SectionBounds(imageAddress: mh, buffer: buffer)
120+
// If this image contains the Swift section(s) we need, acquire the lock and
121+
// store the section's bounds.
122+
for kind in SectionBounds.Kind.allCases {
123+
let (segmentName, sectionName) = kind.segmentAndSectionName
124+
var size = CUnsignedLong(0)
125+
if let start = getsectiondata(mh, segmentName.utf8Start, sectionName.utf8Start, &size), size > 0 {
126+
let buffer = UnsafeRawBufferPointer(start: start, count: Int(clamping: size))
127+
let sb = SectionBounds(imageAddress: mh, buffer: buffer)
128+
_sectionBounds.withUnsafeMutablePointers { sectionBounds, lock in
129+
pthread_mutex_lock(lock)
130+
defer {
131+
pthread_mutex_unlock(lock)
132+
}
133+
sectionBounds.pointee[kind.rawValue].append(sb)
134+
}
135+
}
136+
}
76137
}
77138

78-
return nil
79-
}
139+
#if _runtime(_ObjC)
140+
objc_addLoadImageFunc { mh in
141+
addSectionBounds(from: mh)
142+
}
143+
#else
144+
_dyld_register_func_for_add_image { mh, _ in
145+
addSectionBounds(from: mh!)
146+
}
147+
#endif
148+
}()
80149

81150
/// The Apple-specific implementation of ``SectionBounds/all(_:)``.
82151
///
@@ -86,43 +155,14 @@ private func _sectionBounds(_ segmentName: String, _ sectionName: String, in mh:
86155
/// - Returns: An array of structures describing the bounds of all known test
87156
/// content sections in the current process.
88157
private func _sectionBounds(_ kind: SectionBounds.Kind) -> some RandomAccessCollection<SectionBounds> {
89-
// If this image contains the Swift section(s) we need, get the section bounds
90-
// of interest.
91-
let (segmentName, sectionName) = switch kind {
92-
case .testContent:
93-
("__DATA_CONST", "__swift5_tests")
94-
#if !SWT_NO_LEGACY_TEST_DISCOVERY
95-
case .typeMetadata:
96-
("__TEXT", "__swift5_types")
97-
#endif
98-
}
99-
100-
#if _runtime(_ObjC)
101-
var imageCount = UInt32(0)
102-
let imageNames = objc_copyImageNames(&imageCount)
103-
defer {
104-
free(imageNames)
105-
}
106-
let imageNameBuffer = UnsafeBufferPointer(start: imageNames, count: Int(imageCount))
107-
108-
return imageNameBuffer.compactMap { imageName in
109-
guard let handle = dlopen(imageName, RTLD_LAZY | RTLD_NOLOAD) else {
110-
return nil
111-
}
158+
_startCollectingSectionBounds
159+
return _sectionBounds.withUnsafeMutablePointers { sectionBounds, lock in
160+
pthread_mutex_lock(lock)
112161
defer {
113-
dlclose(handle)
114-
}
115-
guard let mh = _dyld_get_dlopen_image_header(handle) else {
116-
return nil
162+
pthread_mutex_unlock(lock)
117163
}
118-
return _sectionBounds(segmentName, sectionName, in: mh)
164+
return sectionBounds.pointee[kind.rawValue]
119165
}
120-
#else
121-
let imageCount = _dyld_image_count()
122-
return (0 ..< imageCount)
123-
.compactMap(_dyld_get_image_header)
124-
.compactMap { _sectionBounds(segmentName, sectionName, in: $0) }
125-
#endif
126166
}
127167

128168
#elseif (os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android)) && !SWT_NO_DYNAMIC_LINKING

Sources/_TestingInternals/include/Stubs.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,6 @@ static mach_port_t swt_mach_task_self(void) {
8484
}
8585
#endif
8686

87-
#if defined(__APPLE__) && !SWT_NO_DYNAMIC_LINKING
88-
/// A function exported from dyld that maps a `dlopen()` handle to its
89-
/// corresponding Mach header.
90-
///
91-
/// This declaration is provided because it is not provided publicly by Apple's
92-
/// SDK. The dyld code owners are aware of this declaration.
93-
SWT_IMPORT_FROM_STDLIB const struct mach_header *_Nullable _dyld_get_dlopen_image_header(void *handle)
94-
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0), visionos(1.0));
95-
#endif
96-
9787
#if defined(__APPLE__)
9888
/// Define the minimal set of atomic operations supported and used by the
9989
/// testing library for a given C type.

0 commit comments

Comments
 (0)