Skip to content

Commit df81ada

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 df81ada

File tree

4 files changed

+31
-11
lines changed

4 files changed

+31
-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: 25 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,29 @@ 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+
var droppedTailing: Bool = false
530+
// There seems to be many assumptions around paths and trailing '\'
531+
var substring = path[path.startIndex..<path.endIndex]
532+
while !substring.isEmpty && substring.utf8.last == UInt8(ascii: "\\") {
533+
substring = substring.dropLast()
534+
droppedTailing = true
535+
}
536+
// Don't use stripped substring is we only have <drive>: left
537+
if hasDrive && substring.count == 2 && droppedTailing {
538+
self.string = Self.repr(path) + "\\"
539+
} else {
540+
self.string = Self.repr(String(substring))
524541
}
525542
}
526543

@@ -544,7 +561,7 @@ private struct WindowsPath: Path, Sendable {
544561
self.init(string: ".")
545562
} else {
546563
let realpath: String = Self.repr(path)
547-
// Treat a relative path as an invalid relative path...
564+
// Treat an absolute path as an invalid relative path
548565
if Self.isAbsolutePath(realpath) || realpath.first == "\\" {
549566
throw PathValidationError.invalidRelativePath(path)
550567
}
@@ -568,6 +585,7 @@ private struct WindowsPath: Path, Sendable {
568585
_ = string.withCString(encodedAs: UTF16.self) { root in
569586
name.withCString(encodedAs: UTF16.self) { path in
570587
PathAllocCombine(root, path, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result)
588+
_ = PathCchStripPrefix(result, wcslen(result))
571589
}
572590
}
573591
defer { LocalFree(result) }
@@ -579,6 +597,7 @@ private struct WindowsPath: Path, Sendable {
579597
_ = string.withCString(encodedAs: UTF16.self) { root in
580598
relativePath.string.withCString(encodedAs: UTF16.self) { path in
581599
PathAllocCombine(root, path, ULONG(PATHCCH_ALLOW_LONG_PATHS.rawValue), &result)
600+
_ = PathCchStripPrefix(result, wcslen(result))
582601
}
583602
}
584603
defer { LocalFree(result) }
@@ -965,8 +984,7 @@ extension AbsolutePath {
965984
preconditionFailure("invalid relative path computed from \(pathString)")
966985
}
967986
}
968-
969-
assert(AbsolutePath(base, result) == self)
987+
assert(AbsolutePath(base, result) == self, "\(AbsolutePath(base, result)) != \(self)")
970988
return result
971989
}
972990

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)