Skip to content

Commit b74d934

Browse files
committed
Don’t enter an infinite loop when a circular symlink is added to a project
1 parent 20f0d3f commit b74d934

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
@@ -2281,6 +2281,24 @@ final class BackgroundIndexingTests: XCTestCase {
22812281
try await project.testClient.send(SynchronizeRequest(index: true))
22822282
XCTAssertEqual(indexedFiles.value, [try project.uri(for: "test.c")])
22832283
}
2284+
2285+
func testCircularSymlink() async throws {
2286+
let project = try await SwiftPMTestProject(
2287+
files: [
2288+
"Symlink.swift": ""
2289+
],
2290+
enableBackgroundIndexing: true
2291+
)
2292+
let circularSymlink = try XCTUnwrap(project.uri(for: "Symlink.swift").fileURL)
2293+
try FileManager.default.removeItem(at: circularSymlink)
2294+
try FileManager.default.createSymbolicLink(at: circularSymlink, withDestinationURL: circularSymlink)
2295+
2296+
project.testClient.send(
2297+
DidChangeWatchedFilesNotification(changes: [FileEvent(uri: URI(circularSymlink), type: .changed)])
2298+
)
2299+
// Check that we don't enter an infinite loop trying to index the circular symlink.
2300+
try await project.testClient.send(SynchronizeRequest(index: true))
2301+
}
22842302
}
22852303

22862304
extension HoverResponseContents {

0 commit comments

Comments
 (0)