Skip to content

Commit eaae48c

Browse files
authored
Avoid calling unsafeBitCast(_:to:) to cast C function pointers. (#967)
This PR replaces calls to `unsafeBitCast(_:to:)` (where the value being cast is a C function pointer) with calls to an internal function that checks that the value being cast is actually a C function pointer. This is intended to make the code in question more self-documenting (by making it clear what kind of cast is being performed.) The function does then call `unsafeBitCast(_:to:)`, but it's in one place only instead of many places—the call sites are all now self-documenting. Also fixed one place we're using `unsafeBitCast(_:to:)` to cast a metatype where, as of Swift 6.1, we don't need to anymore. ### 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 dca4927 commit eaae48c

File tree

8 files changed

+56
-12
lines changed

8 files changed

+56
-12
lines changed

Sources/Testing/ExitTests/SpawnProcess.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ typealias ProcessID = Never
3434
/// `__GLIBC_PREREQ()` is insufficient because `_DEFAULT_SOURCE` may not be
3535
/// defined at the point spawn.h is first included.
3636
private let _posix_spawn_file_actions_addclosefrom_np = symbol(named: "posix_spawn_file_actions_addclosefrom_np").map {
37-
unsafeBitCast($0, to: (@convention(c) (UnsafeMutablePointer<posix_spawn_file_actions_t>, CInt) -> CInt).self)
37+
castCFunction(at: $0, to: (@convention(c) (UnsafeMutablePointer<posix_spawn_file_actions_t>, CInt) -> CInt).self)
3838
}
3939
#endif
4040

Sources/Testing/ExitTests/WaitFor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ private nonisolated(unsafe) let _waitThreadNoChildrenCondition = {
101101
/// only declared if `_GNU_SOURCE` is set, but setting it causes build errors
102102
/// due to conflicts with Swift's Glibc module.
103103
private let _pthread_setname_np = symbol(named: "pthread_setname_np").map {
104-
unsafeBitCast($0, to: (@convention(c) (pthread_t, UnsafePointer<CChar>) -> CInt).self)
104+
castCFunction(at: $0, to: (@convention(c) (pthread_t, UnsafePointer<CChar>) -> CInt).self)
105105
}
106106
#endif
107107

Sources/Testing/Parameterization/TypeInfo.swift

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,11 @@ extension TypeInfo {
265265
}
266266
switch _kind {
267267
case let .type(type):
268-
// _mangledTypeName() works with move-only types, but its signature has
269-
// not been updated yet. SEE: rdar://134278607
268+
#if compiler(>=6.1)
269+
return _mangledTypeName(type)
270+
#else
270271
return _mangledTypeName(unsafeBitCast(type, to: Any.Type.self))
272+
#endif
271273
case let .nameOnly(_, _, mangledName):
272274
return mangledName
273275
}
@@ -412,3 +414,45 @@ extension TypeInfo: Codable {
412414
}
413415

414416
extension TypeInfo.EncodedForm: Codable {}
417+
418+
// MARK: - Custom casts
419+
420+
/// Cast the given data pointer to a C function pointer.
421+
///
422+
/// - Parameters:
423+
/// - address: The C function pointer as an untyped data pointer.
424+
/// - type: The type of the C function. This type must be a function type with
425+
/// the "C" convention (i.e. `@convention (...) -> ...`).
426+
///
427+
/// - Returns: `address` cast to the given C function type.
428+
///
429+
/// This function serves to make code that casts C function pointers more
430+
/// self-documenting. In debug builds, it checks that `type` is a C function
431+
/// type. In release builds, it behaves the same as `unsafeBitCast(_:to:)`.
432+
func castCFunction<T>(at address: UnsafeRawPointer, to type: T.Type) -> T {
433+
#if DEBUG
434+
if let mangledName = TypeInfo(describing: T.self).mangledName {
435+
precondition(mangledName.last == "C", "\(#function) should only be used to cast a pointer to a C function type.")
436+
}
437+
#endif
438+
return unsafeBitCast(address, to: type)
439+
}
440+
441+
/// Cast the given C function pointer to a data pointer.
442+
///
443+
/// - Parameters:
444+
/// - function: The C function pointer.
445+
///
446+
/// - Returns: `function` cast to an untyped data pointer.
447+
///
448+
/// This function serves to make code that casts C function pointers more
449+
/// self-documenting. In debug builds, it checks that `function` is a C function
450+
/// pointer. In release builds, it behaves the same as `unsafeBitCast(_:to:)`.
451+
func castCFunction<T>(_ function: T, to _: UnsafeRawPointer.Type) -> UnsafeRawPointer {
452+
#if DEBUG
453+
if let mangledName = TypeInfo(describing: T.self).mangledName {
454+
precondition(mangledName.last == "C", "\(#function) should only be used to cast a C function.")
455+
}
456+
#endif
457+
return unsafeBitCast(function, to: UnsafeRawPointer.self)
458+
}

Sources/Testing/SourceAttribution/Backtrace.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ extension Backtrace {
339339
#if _runtime(_ObjC) && !SWT_NO_DYNAMIC_LINKING
340340
if Environment.flag(named: "SWT_FOUNDATION_ERROR_BACKTRACING_ENABLED") == true {
341341
let _CFErrorSetCallStackCaptureEnabled = symbol(named: "_CFErrorSetCallStackCaptureEnabled").map {
342-
unsafeBitCast($0, to: (@convention(c) (DarwinBoolean) -> DarwinBoolean).self)
342+
castCFunction(at: $0, to: (@convention(c) (DarwinBoolean) -> DarwinBoolean).self)
343343
}
344344
_ = _CFErrorSetCallStackCaptureEnabled?(true)
345345
return _CFErrorSetCallStackCaptureEnabled != nil

Sources/Testing/Support/Environment.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ enum Environment {
7474
/// system, the value of this property is `nil`.
7575
private static let _environ_lock_np = {
7676
symbol(named: "environ_lock_np").map {
77-
unsafeBitCast($0, to: (@convention(c) () -> Void).self)
77+
castCFunction(at: $0, to: (@convention(c) () -> Void).self)
7878
}
7979
}()
8080

@@ -84,7 +84,7 @@ enum Environment {
8484
/// system, the value of this property is `nil`.
8585
private static let _environ_unlock_np = {
8686
symbol(named: "environ_unlock_np").map {
87-
unsafeBitCast($0, to: (@convention(c) () -> Void).self)
87+
castCFunction(at: $0, to: (@convention(c) () -> Void).self)
8888
}
8989
}()
9090
#endif

Sources/Testing/Support/GetSymbol.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@ func symbol(in handle: ImageAddress? = nil, named symbolName: String) -> UnsafeR
6666
// If the caller supplied a module, use it.
6767
if let handle {
6868
return GetProcAddress(handle, symbolName).map {
69-
unsafeBitCast($0, to: UnsafeRawPointer.self)
69+
castCFunction($0, to: UnsafeRawPointer.self)
7070
}
7171
}
7272

7373
return HMODULE.all.lazy
7474
.compactMap { GetProcAddress($0, symbolName) }
75-
.map { unsafeBitCast($0, to: UnsafeRawPointer.self) }
75+
.map { castCFunction($0, to: UnsafeRawPointer.self) }
7676
.first
7777
}
7878
#else

Sources/Testing/Support/Versions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ let operatingSystemVersion: String = {
6565
// basically always lies on Windows 10, so don't bother calling it on a
6666
// fallback path.
6767
let RtlGetVersion = symbol(in: GetModuleHandleA("ntdll.dll"), named: "RtlGetVersion").map {
68-
unsafeBitCast($0, to: (@convention(c) (UnsafeMutablePointer<OSVERSIONINFOW>) -> NTSTATUS).self)
68+
castCFunction(at: $0, to: (@convention(c) (UnsafeMutablePointer<OSVERSIONINFOW>) -> NTSTATUS).self)
6969
}
7070
if let RtlGetVersion {
7171
var versionInfo = OSVERSIONINFOW()

Tests/TestingTests/ABIEntryPointTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ struct ABIEntryPointTests {
5757
let copyABIEntryPoint_v0 = try withTestingLibraryImageAddress { testingLibrary in
5858
try #require(
5959
symbol(in: testingLibrary, named: "swt_copyABIEntryPoint_v0").map {
60-
unsafeBitCast($0, to: (@convention(c) () -> UnsafeMutableRawPointer).self)
60+
castCFunction(at: $0, to: (@convention(c) () -> UnsafeMutableRawPointer).self)
6161
}
6262
)
6363
}
@@ -140,7 +140,7 @@ struct ABIEntryPointTests {
140140
let abiv0_getEntryPoint = try withTestingLibraryImageAddress { testingLibrary in
141141
try #require(
142142
symbol(in: testingLibrary, named: "swt_abiv0_getEntryPoint").map {
143-
unsafeBitCast($0, to: (@convention(c) () -> UnsafeRawPointer).self)
143+
castCFunction(at: $0, to: (@convention(c) () -> UnsafeRawPointer).self)
144144
}
145145
)
146146
}

0 commit comments

Comments
 (0)