Skip to content

Commit fb87812

Browse files
authored
Merge pull request #1045 from ahoppen/ahoppen/address-reducer-review-comments
Address review comments from #1037
2 parents d784537 + 27db461 commit fb87812

12 files changed

+478
-145
lines changed

Package.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,20 @@ let package = Package(
5454
dependencies: []
5555
),
5656

57-
// MARK: Csourcekitd:
57+
// MARK: Csourcekitd
5858
// C modules wrapper for sourcekitd.
5959
.target(
6060
name: "Csourcekitd",
6161
dependencies: [],
6262
exclude: ["CMakeLists.txt"]
6363
),
6464

65+
// MARK: Diagnose
66+
6567
.target(
6668
name: "Diagnose",
6769
dependencies: [
70+
"LSPLogging",
6871
"SourceKitD",
6972
"SKCore",
7073
.product(name: "ArgumentParser", package: "swift-argument-parser"),
@@ -75,6 +78,19 @@ let package = Package(
7578
exclude: ["CMakeLists.txt"]
7679
),
7780

81+
.testTarget(
82+
name: "DiagnoseTests",
83+
dependencies: [
84+
"Diagnose",
85+
"LSPLogging",
86+
"LSPTestSupport",
87+
"SourceKitD",
88+
"SKCore",
89+
"SKTestSupport",
90+
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
91+
]
92+
),
93+
7894
// MARK: LanguageServerProtocol
7995
// The core LSP types, suitable for any LSP implementation.
8096
.target(

Sources/Diagnose/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
add_library(Diagnose STATIC
22
CommandLineArgumentsReducer.swift
33
DiagnoseCommand.swift
4-
FileReducer.swift
54
FixItApplier.swift
65
OSLogScraper.swift
76
ReductionError.swift
87
ReproducerBundle.swift
98
RequestInfo.swift
9+
SourceKitD+RunWithYaml.swift
1010
SourceKitDRequestExecutor.swift
11-
SourcekitdRequestCommand.swift)
11+
SourcekitdRequestCommand.swift
12+
SourceReducer.swift)
1213

1314
set_target_properties(Diagnose PROPERTIES
1415
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})

Sources/Diagnose/CommandLineArgumentsReducer.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,19 @@
1212

1313
import Foundation
1414

15+
// MARK: - Entry point
16+
17+
extension RequestInfo {
18+
func reduceCommandLineArguments(using executor: SourceKitRequestExecutor) async throws -> RequestInfo {
19+
let reducer = CommandLineArgumentReducer(sourcekitdExecutor: executor)
20+
return try await reducer.run(initialRequestInfo: self)
21+
}
22+
}
23+
24+
// MARK: - FileProducer
25+
1526
/// Reduces the compiler arguments needed to reproduce a sourcekitd crash.
16-
class CommandLineArgumentReducer {
27+
fileprivate class CommandLineArgumentReducer {
1728
/// The executor that is used to run a sourcekitd request and check whether it
1829
/// still crashes.
1930
private let sourcekitdExecutor: SourceKitRequestExecutor
@@ -23,7 +34,11 @@ class CommandLineArgumentReducer {
2334

2435
init(sourcekitdExecutor: SourceKitRequestExecutor) {
2536
self.sourcekitdExecutor = sourcekitdExecutor
26-
temporarySourceFile = FileManager.default.temporaryDirectory.appendingPathComponent("reduce.swift")
37+
temporarySourceFile = FileManager.default.temporaryDirectory.appendingPathComponent("reduce-\(UUID()).swift")
38+
}
39+
40+
deinit {
41+
try? FileManager.default.removeItem(at: temporarySourceFile)
2742
}
2843

2944
func logSuccessfulReduction(_ requestInfo: RequestInfo) {

Sources/Diagnose/DiagnoseCommand.swift

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ public struct DiagnoseCommand: AsyncParsableCommand {
2626
@Option(
2727
name: .customLong("request-file"),
2828
help:
29-
"Path to a sourcekitd request. If not specified, the command will look for crashed sourcekitd requests and have been logged to OSLog"
29+
"Path to a sourcekitd request. If not specified, the command will look for crashed sourcekitd requests that have been logged to OSLog"
3030
)
3131
var sourcekitdRequestPath: String?
3232

3333
@Option(
3434
name: .customLong("os-log-history"),
35-
help: "If now request file is passed, how many minutes of OS Log history should be scraped for a crash."
35+
help: "If no request file is passed, how many minutes of OS Log history should be scraped for a crash."
3636
)
3737
var osLogScrapeDuration: Int = 60
3838

@@ -88,45 +88,53 @@ public struct DiagnoseCommand: AsyncParsableCommand {
8888
throw ReductionError("Unable to find sourcekitd.framework")
8989
}
9090

91+
var reproducerBundle: URL?
9192
for (name, requestInfo) in try requestInfos() {
9293
print("-- Diagnosing \(name)")
9394
do {
94-
var requestInfo = requestInfo
95-
var nspredicate: NSPredicate? = nil
96-
#if canImport(Darwin)
97-
if let predicate {
98-
nspredicate = NSPredicate(format: predicate)
99-
}
100-
#endif
101-
let executor = SourceKitRequestExecutor(
102-
sourcekitd: URL(fileURLWithPath: sourcekitd),
103-
reproducerPredicate: nspredicate
104-
)
105-
let fileReducer = FileReducer(sourcekitdExecutor: executor)
106-
requestInfo = try await fileReducer.run(initialRequestInfo: requestInfo)
107-
108-
let commandLineReducer = CommandLineArgumentReducer(sourcekitdExecutor: executor)
109-
requestInfo = try await commandLineReducer.run(initialRequestInfo: requestInfo)
110-
111-
let reproducerBundle = try makeReproducerBundle(for: requestInfo)
112-
113-
print("----------------------------------------")
114-
print(
115-
"Reduced SourceKit crash and created a bundle that contains information to reproduce the issue at the following path."
116-
)
117-
print("Please file an issue at https://github.com/apple/sourcekit-lsp/issues/new and attach this bundle")
118-
print()
119-
print(reproducerBundle.path)
120-
121-
// We have found a reproducer. Stop. Looking further probably won't help because other crashes are likely the same cause.
122-
return
95+
reproducerBundle = try await reduce(requestInfo: requestInfo, sourcekitd: sourcekitd)
96+
// If reduce didn't throw, we have found a reproducer. Stop.
97+
// Looking further probably won't help because other crashes are likely the same cause.
98+
break
12399
} catch {
124100
// Reducing this request failed. Continue reducing the next one, maybe that one succeeds.
125101
print(error)
126102
}
127103
}
128104

129-
print("No reducible crashes found")
130-
throw ExitCode(1)
105+
guard let reproducerBundle else {
106+
print("No reducible crashes found")
107+
throw ExitCode(1)
108+
}
109+
print(
110+
"""
111+
----------------------------------------
112+
Reduced SourceKit issue and created a bundle that contains a reduced sourcekitd request exhibiting the issue
113+
and all the files referenced from the request.
114+
The information in this bundle should be sufficient to reproduce the issue.
115+
116+
Please file an issue at https://github.com/apple/sourcekit-lsp/issues/new and attach the bundle located at
117+
\(reproducerBundle.path)
118+
"""
119+
)
120+
121+
}
122+
123+
private func reduce(requestInfo: RequestInfo, sourcekitd: String) async throws -> URL {
124+
var requestInfo = requestInfo
125+
var nspredicate: NSPredicate? = nil
126+
#if canImport(Darwin)
127+
if let predicate {
128+
nspredicate = NSPredicate(format: predicate)
129+
}
130+
#endif
131+
let executor = OutOfProcessSourceKitRequestExecutor(
132+
sourcekitd: URL(fileURLWithPath: sourcekitd),
133+
reproducerPredicate: nspredicate
134+
)
135+
requestInfo = try await requestInfo.reduceInputFile(using: executor)
136+
requestInfo = try await requestInfo.reduceCommandLineArguments(using: executor)
137+
138+
return try makeReproducerBundle(for: requestInfo)
131139
}
132140
}

Sources/Diagnose/RequestInfo.swift

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,25 @@ import Foundation
1414
import RegexBuilder
1515

1616
/// All the information necessary to replay a sourcektid request.
17-
struct RequestInfo {
17+
@_spi(Testing)
18+
public struct RequestInfo {
1819
/// The JSON request object. Contains the following dynamic placeholders:
1920
/// - `$OFFSET`: To be replaced by `offset` before running the request
2021
/// - `$FILE`: Will be replaced with a path to the file that contains the reduced source code.
21-
/// - `$COMPILERARGS`: Will be replaced by the compiler arguments of the request
22+
/// - `$COMPILER_ARGS`: Will be replaced by the compiler arguments of the request
2223
var requestTemplate: String
2324

2425
/// The offset at which the sourcekitd request should be run. Replaces the
2526
/// `$OFFSET` placeholder in the request template.
2627
var offset: Int
2728

28-
/// The compiler arguments of the request. Replaces the `$COMPILERARGS`placeholder in the request template.
29-
var compilerArgs: [String]
29+
/// The compiler arguments of the request. Replaces the `$COMPILER_ARGS`placeholder in the request template.
30+
@_spi(Testing)
31+
public var compilerArgs: [String]
3032

3133
/// The contents of the file that the sourcekitd request operates on.
32-
var fileContents: String
34+
@_spi(Testing)
35+
public var fileContents: String
3336

3437
func request(for file: URL) throws -> String {
3538
let encoder = JSONEncoder()
@@ -42,12 +45,13 @@ struct RequestInfo {
4245
return
4346
requestTemplate
4447
.replacingOccurrences(of: "$OFFSET", with: String(offset))
45-
.replacingOccurrences(of: "$COMPILERARGS", with: compilerArgs)
48+
.replacingOccurrences(of: "$COMPILER_ARGS", with: compilerArgs)
4649
.replacingOccurrences(of: "$FILE", with: file.path)
4750

4851
}
4952

50-
init(requestTemplate: String, offset: Int, compilerArgs: [String], fileContents: String) {
53+
@_spi(Testing)
54+
public init(requestTemplate: String, offset: Int, compilerArgs: [String], fileContents: String) {
5155
self.requestTemplate = requestTemplate
5256
self.offset = offset
5357
self.compilerArgs = compilerArgs
@@ -57,7 +61,8 @@ struct RequestInfo {
5761
/// Creates `RequestInfo` from the contents of the JSON sourcekitd request at `requestPath`.
5862
///
5963
/// The contents of the source file are read from disk.
60-
init(request: String) throws {
64+
@_spi(Testing)
65+
public init(request: String) throws {
6166
var requestTemplate = request
6267

6368
// Extract offset
@@ -106,7 +111,7 @@ private func extractCompilerArguments(
106111
else {
107112
return (requestTemplate, [])
108113
}
109-
let template = lines[...compilerArgsStartIndex] + ["$COMPILERARGS"] + lines[compilerArgsEndIndex...]
114+
let template = lines[...compilerArgsStartIndex] + ["$COMPILER_ARGS"] + lines[compilerArgsEndIndex...]
110115
let compilerArgsJson = "[" + lines[(compilerArgsStartIndex + 1)..<compilerArgsEndIndex].joined(separator: "\n") + "]"
111116
let compilerArgs = try JSONDecoder().decode([String].self, from: compilerArgsJson)
112117
return (template.joined(separator: "\n"), compilerArgs)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 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+
import SourceKitD
14+
15+
extension SourceKitD {
16+
/// Parse the request from YAML and execute it.
17+
@_spi(Testing)
18+
public func run(requestYaml: String) async throws -> SKDResponse {
19+
let request = try requestYaml.cString(using: .utf8)?.withUnsafeBufferPointer { buffer in
20+
var error: UnsafeMutablePointer<CChar>?
21+
let req = api.request_create_from_yaml(buffer.baseAddress, &error)
22+
if let error {
23+
throw ReductionError("Failed to parse sourcekitd request from YAML: \(String(cString: error))")
24+
}
25+
precondition(req != nil)
26+
return req
27+
}
28+
return await withCheckedContinuation { continuation in
29+
var handle: sourcekitd_request_handle_t? = nil
30+
api.send_request(request, &handle) { resp in
31+
continuation.resume(returning: SKDResponse(resp, sourcekitd: self))
32+
}
33+
}
34+
}
35+
}

Sources/Diagnose/SourceKitDRequestExecutor.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import SourceKitD
1616
import struct TSCBasic.AbsolutePath
1717
import class TSCBasic.Process
1818

19-
/// The different states in which a sourcektid request can finish.
20-
enum SourceKitDRequestResult {
19+
/// The different states in which a sourcekitd request can finish.
20+
@_spi(Testing)
21+
public enum SourceKitDRequestResult {
2122
/// The request succeeded.
2223
case success(response: String)
2324

@@ -41,8 +42,14 @@ fileprivate extension String {
4142
}
4243
}
4344

45+
/// An executor that can run a sourcekitd request and indicate whether the request reprodes a specified issue.
46+
@_spi(Testing)
47+
public protocol SourceKitRequestExecutor {
48+
func run(request requestString: String) async throws -> SourceKitDRequestResult
49+
}
50+
4451
/// Runs `sourcekit-lsp run-sourcekitd-request` to check if a sourcekit-request crashes.
45-
struct SourceKitRequestExecutor {
52+
struct OutOfProcessSourceKitRequestExecutor: SourceKitRequestExecutor {
4653
/// The path to `sourcekitd.framework/sourcekitd`.
4754
private let sourcekitd: URL
4855

0 commit comments

Comments
 (0)