Skip to content

Commit 9356239

Browse files
authored
Avoid truncating the path to the test executable on Windows. (#724)
On Windows, we get the path to the (current) test executable by calling `GetModuleFileNameW()`. This function has odd behaviour when the input buffer is too short. Rather than returning `false` or `-1` or some such, as you might expect, it returns successfully but truncates the path (and null-terminates while doing so.) The function _does_ set the last error in this case to `ERROR_INSUFFICIENT_BUFFER`, indicating we need a larger buffer. (The error behaviour on Windows XP is different, but we don't support Windows XP. If you decide to add support for Windows XP to Swift Testing, keep this in mind.) So this PR checks for `ERROR_INSUFFICIENT_BUFFER` and loops with a larger buffer, similar to what we do on Darwin. ### 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 b86027b commit 9356239

File tree

2 files changed

+32
-14
lines changed

2 files changed

+32
-14
lines changed

Sources/Testing/ExitTests/SpawnProcess.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ func spawnExecutable(
4646
) throws -> ProcessID {
4747
// Darwin and Linux differ in their optionality for the posix_spawn types we
4848
// use, so use this typealias to paper over the differences.
49-
#if SWT_TARGET_OS_APPLE
49+
#if SWT_TARGET_OS_APPLE || os(FreeBSD)
5050
typealias P<T> = T?
51-
#elseif os(Linux) || os(FreeBSD)
51+
#elseif os(Linux)
5252
typealias P<T> = T
5353
#endif
5454

Sources/Testing/Support/Additions/CommandLineAdditions.swift

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,19 @@ extension CommandLine {
1616
get throws {
1717
#if os(macOS)
1818
var result: String?
19-
var bufferCount = UInt32(1024)
19+
#if DEBUG
20+
var bufferCount = UInt32(1) // force looping
21+
#else
22+
var bufferCount = UInt32(PATH_MAX)
23+
#endif
2024
while result == nil {
21-
result = withUnsafeTemporaryAllocation(of: CChar.self, capacity: Int(bufferCount)) { buffer in
25+
withUnsafeTemporaryAllocation(of: CChar.self, capacity: Int(bufferCount)) { buffer in
2226
// _NSGetExecutablePath returns 0 on success and -1 if bufferCount is
2327
// too small. If that occurs, we'll return nil here and loop with the
2428
// new value of bufferCount.
2529
if 0 == _NSGetExecutablePath(buffer.baseAddress, &bufferCount) {
26-
return String(cString: buffer.baseAddress!)
30+
result = String(cString: buffer.baseAddress!)
2731
}
28-
return nil
2932
}
3033
}
3134
return result!
@@ -40,7 +43,7 @@ extension CommandLine {
4043
}
4144
#elseif os(FreeBSD)
4245
var mib = [CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1]
43-
try mib.withUnsafeMutableBufferPointer { mib in
46+
return try mib.withUnsafeMutableBufferPointer { mib in
4447
var bufferCount = 0
4548
guard 0 == sysctl(mib.baseAddress!, .init(mib.count), nil, &bufferCount, nil, 0) else {
4649
throw CError(rawValue: swt_errno())
@@ -53,15 +56,30 @@ extension CommandLine {
5356
}
5457
}
5558
#elseif os(Windows)
56-
return try withUnsafeTemporaryAllocation(of: wchar_t.self, capacity: Int(MAX_PATH) * 2) { buffer in
57-
guard 0 != GetModuleFileNameW(nil, buffer.baseAddress!, DWORD(buffer.count)) else {
58-
throw Win32Error(rawValue: GetLastError())
59-
}
60-
guard let path = String.decodeCString(buffer.baseAddress!, as: UTF16.self)?.result else {
61-
throw Win32Error(rawValue: DWORD(ERROR_ILLEGAL_CHARACTER))
59+
var result: String?
60+
#if DEBUG
61+
var bufferCount = Int(1) // force looping
62+
#else
63+
var bufferCount = Int(MAX_PATH)
64+
#endif
65+
while result == nil {
66+
try withUnsafeTemporaryAllocation(of: wchar_t.self, capacity: bufferCount) { buffer in
67+
SetLastError(DWORD(ERROR_SUCCESS))
68+
_ = GetModuleFileNameW(nil, buffer.baseAddress!, DWORD(buffer.count))
69+
switch GetLastError() {
70+
case DWORD(ERROR_SUCCESS):
71+
result = String.decodeCString(buffer.baseAddress!, as: UTF16.self)?.result
72+
if result == nil {
73+
throw Win32Error(rawValue: DWORD(ERROR_ILLEGAL_CHARACTER))
74+
}
75+
case DWORD(ERROR_INSUFFICIENT_BUFFER):
76+
bufferCount += Int(MAX_PATH)
77+
case let errorCode:
78+
throw Win32Error(rawValue: errorCode)
79+
}
6280
}
63-
return path
6481
}
82+
return result!
6583
#elseif os(WASI)
6684
// WASI does not really have the concept of a file system path to the main
6785
// executable, so simply return the first argument--presumably the program

0 commit comments

Comments
 (0)