Skip to content

Commit 76de023

Browse files
committed
Don’t enter an infinite loop when a circular symlink is added to a project
1 parent f9fc58d commit 76de023

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
@@ -341,11 +341,14 @@ private struct IndexOutOfDateChecker {
341341

342342
private enum Error: Swift.Error, CustomStringConvertible {
343343
case fileAttributesDontHaveModificationDate
344+
case circularSymlink(URL)
344345

345346
var description: String {
346347
switch self {
347348
case .fileAttributesDontHaveModificationDate:
348349
return "File attributes don't contain a modification date"
350+
case .circularSymlink(let url):
351+
return "Circular symlink at \(url)"
349352
}
350353
}
351354
}
@@ -484,6 +487,8 @@ private struct IndexOutOfDateChecker {
484487
}
485488
var modificationDate = try Self.modificationDate(atPath: fileURL.filePath)
486489

490+
var visited: Set<URL> = [fileURL]
491+
487492
// Get the maximum mtime in the symlink chain as the modification date of the URI. That way if either the symlink
488493
// is changed to point to a different file or if the underlying file is modified, the modification time is
489494
// updated.
@@ -492,6 +497,9 @@ private struct IndexOutOfDateChecker {
492497
),
493498
let symlinkDestination = URL(string: relativeSymlinkDestination, relativeTo: fileURL)
494499
{
500+
if !visited.insert(symlinkDestination).inserted {
501+
throw Error.circularSymlink(symlinkDestination)
502+
}
495503
fileURL = symlinkDestination
496504
modificationDate = max(modificationDate, try Self.modificationDate(atPath: fileURL.filePath))
497505
}

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2201,6 +2201,24 @@ final class BackgroundIndexingTests: XCTestCase {
22012201
}
22022202
)
22032203
}
2204+
2205+
func testCircularSymlink() async throws {
2206+
let project = try await SwiftPMTestProject(
2207+
files: [
2208+
"Symlink.swift": ""
2209+
],
2210+
enableBackgroundIndexing: true
2211+
)
2212+
let circularSymlink = try XCTUnwrap(project.uri(for: "Symlink.swift").fileURL)
2213+
try FileManager.default.removeItem(at: circularSymlink)
2214+
try FileManager.default.createSymbolicLink(at: circularSymlink, withDestinationURL: circularSymlink)
2215+
2216+
project.testClient.send(
2217+
DidChangeWatchedFilesNotification(changes: [FileEvent(uri: URI(circularSymlink), type: .changed)])
2218+
)
2219+
// Check that we don't enter an infinite loop trying to index the circular symlink.
2220+
try await project.testClient.send(PollIndexRequest())
2221+
}
22042222
}
22052223

22062224
extension HoverResponseContents {

0 commit comments

Comments
 (0)