Skip to content

Commit 975683d

Browse files
committed
Fix some cases where AbsolutePath::relative(to: AbsolutePath) would assert
- there where a couple case where relative(to:) would assert on windows when a path was long (>206) and when tailing slashes where not removed in some cases.
1 parent 84d81b7 commit 975683d

File tree

4 files changed

+29
-11
lines changed

4 files changed

+29
-11
lines changed

Sources/TSCBasic/FileSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ private struct LocalFileSystem: FileSystem {
533533
let fsr: UnsafePointer<Int8> = cwdStr.fileSystemRepresentation
534534
defer { fsr.deallocate() }
535535

536-
return try? AbsolutePath(String(cString: fsr))
536+
return try? AbsolutePath(validating: String(cString: fsr))
537537
#endif
538538
}
539539

Sources/TSCBasic/Path.swift

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public struct AbsolutePath: Hashable, Sendable {
8888
}
8989
defer { LocalFree(pwszResult) }
9090

91-
self.init(String(decodingCString: pwszResult, as: UTF16.self))
91+
try self.init(validating: String(decodingCString: pwszResult, as: UTF16.self))
9292
#else
9393
try self.init(basePath, RelativePath(validating: str))
9494
#endif
@@ -515,12 +515,27 @@ private struct WindowsPath: Path, Sendable {
515515
}
516516

517517
init(string: String) {
518-
if string.first?.isASCII ?? false, string.first?.isLetter ?? false, string.first?.isLowercase ?? false,
518+
let path: String
519+
let hasDrive: Bool
520+
if string.first?.isASCII ?? false, string.first?.isLetter ?? false,
519521
string.count > 1, string[string.index(string.startIndex, offsetBy: 1)] == ":"
520522
{
521-
self.string = "\(string.first!.uppercased())\(string.dropFirst(1))"
523+
hasDrive = true
524+
path = "\(string.first!.uppercased())\(string.dropFirst(1))"
522525
} else {
523-
self.string = string
526+
hasDrive = false
527+
path = string
528+
}
529+
// There seems to be many assumptions around paths and trailing '\'
530+
var substring = path[path.startIndex..<path.endIndex]
531+
while substring.count > 1 && substring.utf8.last == UInt8(ascii: "\\") {
532+
substring = substring.dropLast()
533+
}
534+
// Don't use stripped substring is we only have <drive>: left
535+
if hasDrive && substring.count == 2 {
536+
self.string = Self.repr(path)
537+
} else {
538+
self.string = Self.repr(String(substring))
524539
}
525540
}
526541

@@ -544,7 +559,7 @@ private struct WindowsPath: Path, Sendable {
544559
self.init(string: ".")
545560
} else {
546561
let realpath: String = Self.repr(path)
547-
// Treat a relative path as an invalid relative path...
562+
// Treat an absolute path as an invalid relative path
548563
if Self.isAbsolutePath(realpath) || realpath.first == "\\" {
549564
throw PathValidationError.invalidRelativePath(path)
550565
}
@@ -568,6 +583,7 @@ private struct WindowsPath: Path, Sendable {
568583
_ = string.withCString(encodedAs: UTF16.self) { root in
569584
name.withCString(encodedAs: UTF16.self) { path in
570585
PathAllocCombine(root, path, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result)
586+
_ = PathCchStripPrefix(result, wcslen(result))
571587
}
572588
}
573589
defer { LocalFree(result) }
@@ -579,6 +595,7 @@ private struct WindowsPath: Path, Sendable {
579595
_ = string.withCString(encodedAs: UTF16.self) { root in
580596
relativePath.string.withCString(encodedAs: UTF16.self) { path in
581597
PathAllocCombine(root, path, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result)
598+
_ = PathCchStripPrefix(result, wcslen(result))
582599
}
583600
}
584601
defer { LocalFree(result) }
@@ -965,8 +982,7 @@ extension AbsolutePath {
965982
preconditionFailure("invalid relative path computed from \(pathString)")
966983
}
967984
}
968-
969-
assert(AbsolutePath(base, result) == self)
985+
assert(AbsolutePath(base, result) == self, "\(AbsolutePath(base, result)) != \(self)")
970986
return result
971987
}
972988

Sources/TSCBasic/PathShims.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public func resolveSymlinks(_ path: AbsolutePath) throws -> AbsolutePath {
4444
} else {
4545
pathBaseAddress = UnsafePointer($0.baseAddress!)
4646
}
47-
return try AbsolutePath(String(decodingCString: pathBaseAddress, as: UTF16.self))
47+
return try AbsolutePath(validating: String(decodingCString: pathBaseAddress, as: UTF16.self))
4848
}
4949
#else
5050
let pathStr = path.pathString

Sources/TSCUtility/FSWatch.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,9 @@ public final class RDCWatcher {
250250
}
251251

252252
if !GetOverlappedResult(watch.hDirectory, &watch.overlapped, &dwBytesReturned, false) {
253+
guard let path = try? AbsolutePath(validating: watch.path) else { continue }
253254
queue.async {
254-
delegate?.pathsDidReceiveEvent([AbsolutePath(watch.path)])
255+
delegate?.pathsDidReceiveEvent([path])
255256
}
256257
return
257258
}
@@ -272,7 +273,8 @@ public final class RDCWatcher {
272273
String(utf16CodeUnitsNoCopy: &pNotify.pointee.FileName,
273274
count: Int(pNotify.pointee.FileNameLength) / MemoryLayout<WCHAR>.stride,
274275
freeWhenDone: false)
275-
paths.append(AbsolutePath(file))
276+
guard let path = try? AbsolutePath(validating: file) else { continue }
277+
paths.append(path)
276278

277279
pNotify = (UnsafeMutableRawPointer(pNotify) + Int(pNotify.pointee.NextEntryOffset))
278280
.assumingMemoryBound(to: FILE_NOTIFY_INFORMATION.self)

0 commit comments

Comments
 (0)