Skip to content

Commit bf31f4a

Browse files
committed
Sink handling of file writing into the SourceKitRequestExecutor
It’s cleaner plus it opens the option to reduce compiler crashes in the future.
1 parent 8322b9f commit bf31f4a

File tree

5 files changed

+42
-36
lines changed

5 files changed

+42
-36
lines changed

Sources/Diagnose/CommandLineArgumentsReducer.swift

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ fileprivate class CommandLineArgumentReducer {
3434
/// still crashes.
3535
private let sourcekitdExecutor: SourceKitRequestExecutor
3636

37-
/// The file to which we write the reduced source code.
38-
private let temporarySourceFile: URL
39-
4037
/// A callback to be called when the reducer has made progress reducing the request
4138
private let progressUpdate: (_ progress: Double, _ message: String) -> Void
4239

@@ -48,14 +45,9 @@ fileprivate class CommandLineArgumentReducer {
4845
progressUpdate: @escaping (_ progress: Double, _ message: String) -> Void
4946
) {
5047
self.sourcekitdExecutor = sourcekitdExecutor
51-
self.temporarySourceFile = FileManager.default.temporaryDirectory.appendingPathComponent("reduce-\(UUID()).swift")
5248
self.progressUpdate = progressUpdate
5349
}
5450

55-
deinit {
56-
try? FileManager.default.removeItem(at: temporarySourceFile)
57-
}
58-
5951
func logSuccessfulReduction(_ requestInfo: RequestInfo) {
6052
progressUpdate(
6153
1 - (Double(requestInfo.compilerArgs.count) / Double(initialCommandLineCount)),
@@ -64,7 +56,6 @@ fileprivate class CommandLineArgumentReducer {
6456
}
6557

6658
func run(initialRequestInfo: RequestInfo) async throws -> RequestInfo {
67-
try initialRequestInfo.fileContents.write(to: temporarySourceFile, atomically: true, encoding: .utf8)
6859
var requestInfo = initialRequestInfo
6960
self.initialCommandLineCount = requestInfo.compilerArgs.count
7061

@@ -123,7 +114,7 @@ fileprivate class CommandLineArgumentReducer {
123114
var reducedRequestInfo = requestInfo
124115
reducedRequestInfo.compilerArgs.removeSubrange(argumentsToRemove)
125116

126-
let result = try await sourcekitdExecutor.run(request: reducedRequestInfo.request(for: temporarySourceFile))
117+
let result = try await sourcekitdExecutor.run(request: reducedRequestInfo)
127118
if case .reproducesIssue = result {
128119
logSuccessfulReduction(reducedRequestInfo)
129120
return reducedRequestInfo

Sources/Diagnose/RequestInfo.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public struct RequestInfo {
3434
@_spi(Testing)
3535
public var fileContents: String
3636

37-
func request(for file: URL) throws -> String {
37+
@_spi(Testing)
38+
public func request(for file: URL) throws -> String {
3839
let encoder = JSONEncoder()
3940
encoder.outputFormatting = .prettyPrinted
4041
guard var compilerArgs = String(data: try encoder.encode(compilerArgs), encoding: .utf8) else {

Sources/Diagnose/SourceKitDRequestExecutor.swift

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,29 +45,40 @@ fileprivate extension String {
4545
/// An executor that can run a sourcekitd request and indicate whether the request reprodes a specified issue.
4646
@_spi(Testing)
4747
public protocol SourceKitRequestExecutor {
48-
func run(request requestString: String) async throws -> SourceKitDRequestResult
48+
func run(request: RequestInfo) async throws -> SourceKitDRequestResult
4949
}
5050

5151
/// Runs `sourcekit-lsp run-sourcekitd-request` to check if a sourcekit-request crashes.
52-
struct OutOfProcessSourceKitRequestExecutor: SourceKitRequestExecutor {
52+
class OutOfProcessSourceKitRequestExecutor: SourceKitRequestExecutor {
5353
/// The path to `sourcekitd.framework/sourcekitd`.
5454
private let sourcekitd: URL
5555

56-
/// The file to which we write the JSON request that we want to run.
56+
/// The file to which we write the reduce source file.
5757
private let temporarySourceFile: URL
5858

59+
/// The file to which we write the YAML request that we want to run.
60+
private let temporaryRequestFile: URL
61+
5962
/// If this predicate evaluates to true on the sourcekitd response, the request is
6063
/// considered to reproduce the issue.
6164
private let reproducerPredicate: NSPredicate?
6265

6366
init(sourcekitd: URL, reproducerPredicate: NSPredicate?) {
6467
self.sourcekitd = sourcekitd
6568
self.reproducerPredicate = reproducerPredicate
66-
temporarySourceFile = FileManager.default.temporaryDirectory.appendingPathComponent("request.json")
69+
temporaryRequestFile = FileManager.default.temporaryDirectory.appendingPathComponent("request-\(UUID()).yml")
70+
temporarySourceFile = FileManager.default.temporaryDirectory.appendingPathComponent("recude-\(UUID()).swift")
71+
}
72+
73+
deinit {
74+
try? FileManager.default.removeItem(at: temporaryRequestFile)
75+
try? FileManager.default.removeItem(at: temporarySourceFile)
6776
}
6877

69-
func run(request requestString: String) async throws -> SourceKitDRequestResult {
70-
try requestString.write(to: temporarySourceFile, atomically: true, encoding: .utf8)
78+
func run(request: RequestInfo) async throws -> SourceKitDRequestResult {
79+
try request.fileContents.write(to: temporarySourceFile, atomically: true, encoding: .utf8)
80+
let requestString = try request.request(for: temporarySourceFile)
81+
try requestString.write(to: temporaryRequestFile, atomically: true, encoding: .utf8)
7182

7283
let process = Process(
7384
arguments: [
@@ -76,7 +87,7 @@ struct OutOfProcessSourceKitRequestExecutor: SourceKitRequestExecutor {
7687
"--sourcekitd",
7788
sourcekitd.path,
7889
"--request-file",
79-
temporarySourceFile.path,
90+
temporaryRequestFile.path,
8091
]
8192
)
8293
try process.launch()

Sources/Diagnose/SourceReducer.swift

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,6 @@ fileprivate class SourceReducer {
6666
/// still crashes.
6767
private let sourcekitdExecutor: SourceKitRequestExecutor
6868

69-
/// The file to which we write the reduced source code.
70-
private let temporarySourceFile: URL
71-
7269
/// A callback to call to report progress
7370
private let progressUpdate: (_ progress: Double, _ message: String) -> Void
7471

@@ -83,14 +80,9 @@ fileprivate class SourceReducer {
8380
progressUpdate: @escaping (_ progress: Double, _ message: String) -> Void
8481
) {
8582
self.sourcekitdExecutor = sourcekitdExecutor
86-
self.temporarySourceFile = FileManager.default.temporaryDirectory.appendingPathComponent("reduce-\(UUID()).swift")
8783
self.progressUpdate = progressUpdate
8884
}
8985

90-
deinit {
91-
try? FileManager.default.removeItem(at: temporarySourceFile)
92-
}
93-
9486
/// Reduce the file contents in `initialRequest` to a smaller file that still reproduces a crash.
9587
func run(initialRequestInfo: RequestInfo) async throws -> RequestInfo {
9688
var requestInfo = initialRequestInfo
@@ -243,9 +235,8 @@ fileprivate class SourceReducer {
243235
fileContents: reducedSource
244236
)
245237

246-
try reducedSource.write(to: temporarySourceFile, atomically: true, encoding: .utf8)
247238
logger.debug("Try reduction to the following input file:\n\(reducedSource)")
248-
let result = try await sourcekitdExecutor.run(request: reducedRequestInfo.request(for: temporarySourceFile))
239+
let result = try await sourcekitdExecutor.run(request: reducedRequestInfo)
249240
if case .reproducesIssue = result {
250241
logger.debug("Reduction successful")
251242
logSuccessfulReduction(reducedRequestInfo, tree: tree)
@@ -511,9 +502,8 @@ fileprivate func getSwiftInterface(
511502
compilerArgs: compilerArgs,
512503
fileContents: ""
513504
)
514-
let request = try requestInfo.request(for: URL(fileURLWithPath: "/"))
515505

516-
guard case .success(let result) = try await executor.run(request: request) else {
506+
guard case .success(let result) = try await executor.run(request: requestInfo) else {
517507
throw ReductionError("Failed to get Swift Interface for \(moduleName)")
518508
}
519509

Tests/DiagnoseTests/DiagnoseTests.swift

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,30 +191,43 @@ private func assertReduce(
191191
}
192192
}
193193

194-
private struct InProcessSourceKitRequestExecutor: SourceKitRequestExecutor {
194+
/// We can't run the `OutOfProcessSourceKitRequestExecutor` in tests because that runs the sourcekit-lsp executable,
195+
/// which isn't built when running tests.
196+
private class InProcessSourceKitRequestExecutor: SourceKitRequestExecutor {
195197
/// The path to `sourcekitd.framework/sourcekitd`.
196198
private let sourcekitd: URL
197199

198-
/// The file to which we write the JSON request that we want to run.
200+
/// The file to which we write the reduce source file.
199201
private let temporarySourceFile: URL
200202

203+
/// The file to which we write the AYML request that we want to run.
204+
private let temporaryRequestFile: URL
205+
201206
/// If this predicate evaluates to true on the sourcekitd response, the request is
202207
/// considered to reproduce the issue.
203208
private let reproducerPredicate: NSPredicate
204209

205210
init(sourcekitd: URL, reproducerPredicate: NSPredicate) {
206211
self.sourcekitd = sourcekitd
207212
self.reproducerPredicate = reproducerPredicate
208-
temporarySourceFile = FileManager.default.temporaryDirectory.appendingPathComponent("request.json")
213+
temporaryRequestFile = FileManager.default.temporaryDirectory.appendingPathComponent("request-\(UUID()).yml")
214+
temporarySourceFile = FileManager.default.temporaryDirectory.appendingPathComponent("recude-\(UUID()).swift")
215+
}
216+
217+
deinit {
218+
try? FileManager.default.removeItem(at: temporaryRequestFile)
219+
try? FileManager.default.removeItem(at: temporarySourceFile)
209220
}
210221

211-
func run(request requestYaml: String) async throws -> SourceKitDRequestResult {
212-
logger.info("Sending request: \(requestYaml)")
222+
func run(request: RequestInfo) async throws -> SourceKitDRequestResult {
223+
try request.fileContents.write(to: temporarySourceFile, atomically: true, encoding: .utf8)
224+
let requestString = try request.request(for: temporarySourceFile)
225+
logger.info("Sending request: \(requestString)")
213226

214227
let sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate(
215228
dylibPath: try! AbsolutePath(validating: sourcekitd.path)
216229
)
217-
let response = try await sourcekitd.run(requestYaml: requestYaml)
230+
let response = try await sourcekitd.run(requestYaml: requestString)
218231

219232
logger.info("Received response: \(response.description)")
220233

0 commit comments

Comments
 (0)