Skip to content

Commit 6d96ec6

Browse files
authored
Merge pull request #2064 from ahoppen/circular-symlinks
Don’t enter an infinite loop when a circular symlink is added to a project
2 parents b1a7b57 + a76f572 commit 6d96ec6

File tree

2 files changed

+26
-0
lines changed

2 files changed

+26
-0
lines changed

Sources/SemanticIndex/CheckedIndex.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,11 +355,14 @@ private struct IndexOutOfDateChecker {
355355

356356
private enum Error: Swift.Error, CustomStringConvertible {
357357
case fileAttributesDontHaveModificationDate
358+
case circularSymlink(URL)
358359

359360
var description: String {
360361
switch self {
361362
case .fileAttributesDontHaveModificationDate:
362363
return "File attributes don't contain a modification date"
364+
case .circularSymlink(let url):
365+
return "Circular symlink at \(url)"
363366
}
364367
}
365368
}
@@ -517,13 +520,18 @@ private struct IndexOutOfDateChecker {
517520
}
518521
var modificationDate = try Self.modificationDate(atPath: fileURL.filePath)
519522

523+
var visited: Set<URL> = [fileURL]
524+
520525
// Get the maximum mtime in the symlink chain as the modification date of the URI. That way if either the symlink
521526
// is changed to point to a different file or if the underlying file is modified, the modification time is
522527
// updated.
523528
while let relativeSymlinkDestination = try? FileManager.default.destinationOfSymbolicLink(
524529
atPath: fileURL.filePath
525530
) {
526531
fileURL = URL(fileURLWithPath: relativeSymlinkDestination, relativeTo: fileURL)
532+
if !visited.insert(fileURL).inserted {
533+
throw Error.circularSymlink(fileURL)
534+
}
527535
modificationDate = max(modificationDate, try Self.modificationDate(atPath: fileURL.filePath))
528536
}
529537

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2521,6 +2521,24 @@ final class BackgroundIndexingTests: XCTestCase {
25212521
let workspaceSymbolsAfterUpdate = try await project.testClient.send(WorkspaceSymbolsRequest(query: "myTest"))
25222522
XCTAssertEqual(workspaceSymbolsAfterUpdate?.compactMap(\.symbolInformation?.name), ["myTestA_updated", "myTestB"])
25232523
}
2524+
2525+
func testCircularSymlink() async throws {
2526+
let project = try await SwiftPMTestProject(
2527+
files: [
2528+
"Symlink.swift": ""
2529+
],
2530+
enableBackgroundIndexing: true
2531+
)
2532+
let circularSymlink = try XCTUnwrap(project.uri(for: "Symlink.swift").fileURL)
2533+
try FileManager.default.removeItem(at: circularSymlink)
2534+
try FileManager.default.createSymbolicLink(at: circularSymlink, withDestinationURL: circularSymlink)
2535+
2536+
project.testClient.send(
2537+
DidChangeWatchedFilesNotification(changes: [FileEvent(uri: URI(circularSymlink), type: .changed)])
2538+
)
2539+
// Check that we don't enter an infinite loop trying to index the circular symlink.
2540+
try await project.testClient.send(SynchronizeRequest(index: true))
2541+
}
25242542
}
25252543

25262544
extension HoverResponseContents {

0 commit comments

Comments
 (0)