Skip to content

Commit 6228f3d

Browse files
committed
When sourcekitd crashes, log the file contents with which it crashed and the request
This should make it a lot easier to reproduce sourcekitd crashes.
1 parent 4e0e12c commit 6228f3d

12 files changed

+67
-40
lines changed

Sources/LSPLogging/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ add_library(LSPLogging STATIC
55
LoggingScope.swift
66
NonDarwinLogging.swift
77
OrLog.swift
8+
SplitLogMessage.swift
89
)
910
set_target_properties(LSPLogging PROPERTIES
1011
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// Splits `message` on newline characters such that each chunk is at most `maxChunkSize` bytes long.
14+
///
15+
/// The intended use case for this is to split compiler arguments into multiple chunks so that each chunk doesn't exceed
16+
/// the maximum message length of `os_log` and thus won't get truncated.
17+
///
18+
/// - Note: This will only split along newline boundary. If a single line is longer than `maxChunkSize`, it won't be
19+
/// split. This is fine for compiler argument splitting since a single argument is rarely longer than 800 characters.
20+
public func splitLongMultilineMessage(message: String, maxChunkSize: Int = 800) -> [String] {
21+
var chunks: [String] = []
22+
for line in message.split(separator: "\n", omittingEmptySubsequences: false) {
23+
if let lastChunk = chunks.last, lastChunk.utf8.count + line.utf8.count < maxChunkSize {
24+
chunks[chunks.count - 1] += "\n" + line
25+
} else {
26+
chunks.append(String(line))
27+
}
28+
}
29+
return chunks
30+
}

Sources/SKCore/BuildSystemManager.swift

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ fileprivate actor BuildSettingsLogger {
321321
\(settings.workingDirectory ?? "<nil>")
322322
"""
323323

324-
let chunks = splitLongMultilineMessage(message: log, maxChunkSize: 800)
324+
let chunks = splitLongMultilineMessage(message: log)
325325
for (index, chunk) in chunks.enumerated() {
326326
logger.log(
327327
"""
@@ -331,23 +331,4 @@ fileprivate actor BuildSettingsLogger {
331331
)
332332
}
333333
}
334-
335-
/// Splits `message` on newline characters such that each chunk is at most `maxChunkSize` bytes long.
336-
///
337-
/// The intended use case for this is to split compiler arguments into multiple chunks so that each chunk doesn't exceed
338-
/// the maximum message length of `os_log` and thus won't get truncated.
339-
///
340-
/// - Note: This will only split along newline boundary. If a single line is longer than `maxChunkSize`, it won't be
341-
/// split. This is fine for compiler argument splitting since a single argument is rarely longer than 800 characters.
342-
private func splitLongMultilineMessage(message: String, maxChunkSize: Int) -> [String] {
343-
var chunks: [String] = []
344-
for line in message.split(separator: "\n", omittingEmptySubsequences: false) {
345-
if let lastChunk = chunks.last, lastChunk.utf8.count + line.utf8.count < maxChunkSize {
346-
chunks[chunks.count - 1] += "\n" + line
347-
} else {
348-
chunks.append(String(line))
349-
}
350-
}
351-
return chunks
352-
}
353334
}

Sources/SourceKitD/SourceKitD.swift

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ extension SourceKitD {
8080
return dict
8181
}
8282

83-
public func send(_ req: SKDRequestDictionary) async throws -> SKDResponseDictionary {
83+
/// - Parameters:
84+
/// - req: The request to send to sourcekitd.
85+
/// - fileContents: The contents of the file that the request operates on. If sourcekitd crashes, the file contents
86+
/// will be logged.
87+
public func send(_ req: SKDRequestDictionary, fileContents: String?) async throws -> SKDResponseDictionary {
8488
logRequest(req)
8589

8690
let sourcekitdResponse: SKDResponse = await withCheckedContinuation { continuation in
@@ -93,6 +97,24 @@ extension SourceKitD {
9397
logResponse(sourcekitdResponse)
9498

9599
guard let dict = sourcekitdResponse.value else {
100+
if sourcekitdResponse.error == .connectionInterrupted {
101+
let log = """
102+
Request:
103+
\(req.description)
104+
105+
File contents:
106+
\(fileContents ?? "<nil>")
107+
"""
108+
let chunks = splitLongMultilineMessage(message: log)
109+
for (index, chunk) in chunks.enumerated() {
110+
logger.fault(
111+
"""
112+
sourcekitd crashed (\(index + 1)/\(chunks.count))
113+
\(chunk)
114+
"""
115+
)
116+
}
117+
}
96118
throw sourcekitdResponse.error!
97119
}
98120

@@ -107,9 +129,7 @@ extension SourceKitD {
107129
}
108130

109131
private func logRequest(_ request: SKDRequestDictionary) {
110-
// FIXME: Ideally we could log the request key here at the info level but the dictionary is
111-
// readonly.
112-
logger.log(
132+
logger.info(
113133
"""
114134
Sending sourcekitd request:
115135
\(request.forLogging)

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -620,12 +620,7 @@ extension SourceKitServer: MessageHandler {
620620
private func handleImpl(_ params: some NotificationType, from clientID: ObjectIdentifier) async {
621621
let notification = Notification(params, clientID: clientID)
622622

623-
logger.log(
624-
"""
625-
Received notification
626-
\(notification.forLogging)
627-
"""
628-
)
623+
logger.log("Received notification: \(notification.forLogging)")
629624

630625
switch notification.params {
631626
case let notification as InitializedNotification:

Sources/SourceKitLSP/Swift/CodeCompletionSession.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ class CodeCompletionSession {
198198
req[keys.compilerargs] = compileCommand.compilerArgs
199199
}
200200

201-
let dict = try await sourcekitd.send(req)
201+
let dict = try await sourcekitd.send(req, fileContents: snapshot.text)
202202
self.state = .open
203203

204204
guard let completions: SKDResponseArray = dict[keys.results] else {
@@ -231,7 +231,7 @@ class CodeCompletionSession {
231231
req[keys.name] = uri.pseudoPath
232232
req[keys.codecomplete_options] = optionsDictionary(filterText: filterText, options: options)
233233

234-
let dict = try await sourcekitd.send(req)
234+
let dict = try await sourcekitd.send(req, fileContents: snapshot.text)
235235
guard let completions: SKDResponseArray = dict[keys.results] else {
236236
return CompletionList(isIncomplete: false, items: [])
237237
}

Sources/SourceKitLSP/Swift/CursorInfo.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ extension SwiftLanguageServer {
115115

116116
appendAdditionalParameters?(skreq)
117117

118-
let dict = try await self.sourcekitd.send(skreq)
118+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
119119

120120
guard let kind: sourcekitd_uid_t = dict[keys.kind] else {
121121
// Nothing to report.

Sources/SourceKitLSP/Swift/OpenInterface.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ extension SwiftLanguageServer {
8282
skreq[keys.compilerargs] = compileCommand.compilerArgs
8383
}
8484

85-
let dict = try await self.sourcekitd.send(skreq)
85+
let dict = try await self.sourcekitd.send(skreq, fileContents: nil)
8686
return InterfaceInfo(contents: dict[keys.sourcetext] ?? "")
8787
}
8888

@@ -103,7 +103,7 @@ extension SwiftLanguageServer {
103103
skreq[keys.sourcefile] = uri.pseudoPath
104104
skreq[keys.usr] = symbol
105105

106-
let dict = try await self.sourcekitd.send(skreq)
106+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
107107
if let offset: Int = dict[keys.offset],
108108
let position = snapshot.positionOf(utf8Offset: offset)
109109
{

Sources/SourceKitLSP/Swift/SemanticRefactoring.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ extension SwiftLanguageServer {
163163
skreq[keys.compilerargs] = compileCommand.compilerArgs
164164
}
165165

166-
let dict = try await self.sourcekitd.send(skreq)
166+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
167167
guard let refactor = SemanticRefactoring(refactorCommand.title, dict, snapshot, self.keys) else {
168168
throw SemanticRefactoringError.noEditsNeeded(uri)
169169
}

Sources/SourceKitLSP/Swift/SemanticTokens.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ extension SwiftLanguageServer {
3131
// FIXME: SourceKit should probably cache this for us.
3232
skreq[keys.compilerargs] = buildSettings.compilerArgs
3333

34-
let dict = try await sourcekitd.send(skreq)
34+
let dict = try await sourcekitd.send(skreq, fileContents: snapshot.text)
3535

3636
guard let skTokens: SKDResponseArray = dict[keys.semantic_tokens] else {
3737
return nil

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ extension SwiftLanguageServer {
627627
skreq[keys.compilerargs] = compileCommand.compilerArgs
628628
}
629629

630-
let dict = try await self.sourcekitd.send(skreq)
630+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
631631

632632
guard let results: SKDResponseArray = dict[self.keys.results] else {
633633
return []
@@ -1084,7 +1084,7 @@ extension SwiftLanguageServer {
10841084
// FIXME: SourceKit should probably cache this for us.
10851085
skreq[keys.compilerargs] = buildSettings.compilerArgs
10861086

1087-
let dict = try await self.sourcekitd.send(skreq)
1087+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
10881088

10891089
try Task.checkCancellation()
10901090
guard documentManager.latestSnapshot(req.textDocument.uri)?.id == snapshot.id else {

Sources/SourceKitLSP/Swift/VariableTypeInfo.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ extension SwiftLanguageServer {
116116
skreq[keys.compilerargs] = compileCommand.compilerArgs
117117
}
118118

119-
let dict = try await self.sourcekitd.send(skreq)
119+
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
120120
guard let skVariableTypeInfos: SKDResponseArray = dict[keys.variable_type_list] else {
121121
return []
122122
}

0 commit comments

Comments
 (0)