From a76f57202e6e27f051874f58a838e2d3134b0896 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 13 Mar 2025 21:11:04 -0700 Subject: [PATCH] =?UTF-8?q?Don=E2=80=99t=20enter=20an=20infinite=20loop=20?= =?UTF-8?q?when=20a=20circular=20symlink=20is=20added=20to=20a=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/SemanticIndex/CheckedIndex.swift | 8 ++++++++ .../BackgroundIndexingTests.swift | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Sources/SemanticIndex/CheckedIndex.swift b/Sources/SemanticIndex/CheckedIndex.swift index 48d849fee..ec6a99036 100644 --- a/Sources/SemanticIndex/CheckedIndex.swift +++ b/Sources/SemanticIndex/CheckedIndex.swift @@ -355,11 +355,14 @@ private struct IndexOutOfDateChecker { private enum Error: Swift.Error, CustomStringConvertible { case fileAttributesDontHaveModificationDate + case circularSymlink(URL) var description: String { switch self { case .fileAttributesDontHaveModificationDate: return "File attributes don't contain a modification date" + case .circularSymlink(let url): + return "Circular symlink at \(url)" } } } @@ -517,6 +520,8 @@ private struct IndexOutOfDateChecker { } var modificationDate = try Self.modificationDate(atPath: fileURL.filePath) + var visited: Set = [fileURL] + // Get the maximum mtime in the symlink chain as the modification date of the URI. That way if either the symlink // is changed to point to a different file or if the underlying file is modified, the modification time is // updated. @@ -524,6 +529,9 @@ private struct IndexOutOfDateChecker { atPath: fileURL.filePath ) { fileURL = URL(fileURLWithPath: relativeSymlinkDestination, relativeTo: fileURL) + if !visited.insert(fileURL).inserted { + throw Error.circularSymlink(fileURL) + } modificationDate = max(modificationDate, try Self.modificationDate(atPath: fileURL.filePath)) } diff --git a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift index 5988cdafa..1aca7fc6c 100644 --- a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift +++ b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift @@ -2521,6 +2521,24 @@ final class BackgroundIndexingTests: XCTestCase { let workspaceSymbolsAfterUpdate = try await project.testClient.send(WorkspaceSymbolsRequest(query: "myTest")) XCTAssertEqual(workspaceSymbolsAfterUpdate?.compactMap(\.symbolInformation?.name), ["myTestA_updated", "myTestB"]) } + + func testCircularSymlink() async throws { + let project = try await SwiftPMTestProject( + files: [ + "Symlink.swift": "" + ], + enableBackgroundIndexing: true + ) + let circularSymlink = try XCTUnwrap(project.uri(for: "Symlink.swift").fileURL) + try FileManager.default.removeItem(at: circularSymlink) + try FileManager.default.createSymbolicLink(at: circularSymlink, withDestinationURL: circularSymlink) + + project.testClient.send( + DidChangeWatchedFilesNotification(changes: [FileEvent(uri: URI(circularSymlink), type: .changed)]) + ) + // Check that we don't enter an infinite loop trying to index the circular symlink. + try await project.testClient.send(SynchronizeRequest(index: true)) + } } extension HoverResponseContents {