11
11
//===----------------------------------------------------------------------===//
12
12
13
13
import Foundation
14
+ import LSPLogging
14
15
15
16
// MARK: - Entry point
16
17
@@ -34,9 +35,6 @@ fileprivate class CommandLineArgumentReducer {
34
35
/// still crashes.
35
36
private let sourcekitdExecutor : SourceKitRequestExecutor
36
37
37
- /// The file to which we write the reduced source code.
38
- private let temporarySourceFile : URL
39
-
40
38
/// A callback to be called when the reducer has made progress reducing the request
41
39
private let progressUpdate : ( _ progress: Double , _ message: String ) -> Void
42
40
@@ -48,69 +46,68 @@ fileprivate class CommandLineArgumentReducer {
48
46
progressUpdate: @escaping ( _ progress: Double , _ message: String ) -> Void
49
47
) {
50
48
self . sourcekitdExecutor = sourcekitdExecutor
51
- self . temporarySourceFile = FileManager . default. temporaryDirectory. appendingPathComponent ( " reduce- \( UUID ( ) ) .swift " )
52
49
self . progressUpdate = progressUpdate
53
50
}
54
51
55
- deinit {
56
- try ? FileManager . default. removeItem ( at: temporarySourceFile)
52
+ func run( initialRequestInfo: RequestInfo ) async throws -> RequestInfo {
53
+ var requestInfo = initialRequestInfo
54
+ requestInfo = try await reduce ( initialRequestInfo: requestInfo, simultaneousRemove: 10 )
55
+ requestInfo = try await reduce ( initialRequestInfo: requestInfo, simultaneousRemove: 1 )
56
+ return requestInfo
57
57
}
58
58
59
- func logSuccessfulReduction( _ requestInfo: RequestInfo ) {
60
- progressUpdate (
61
- 1 - ( Double ( requestInfo. compilerArgs. count) / Double( initialCommandLineCount) ) ,
62
- " Reduced compiler arguments to \( requestInfo. compilerArgs. count) "
63
- )
64
- }
59
+ /// Reduce the command line arguments of the given `RequestInfo`.
60
+ ///
61
+ /// If `simultaneousRemove` is set, the reducer will try to remove that many arguments at once. This is useful to
62
+ /// quickly remove multiple arguments from the request.
63
+ private func reduce( initialRequestInfo: RequestInfo , simultaneousRemove: Int ) async throws -> RequestInfo {
64
+ guard initialRequestInfo. compilerArgs. count > simultaneousRemove else {
65
+ // Trying to remove more command line arguments than we have. This isn't going to work.
66
+ return initialRequestInfo
67
+ }
65
68
66
- func run( initialRequestInfo: RequestInfo ) async throws -> RequestInfo {
67
- try initialRequestInfo. fileContents. write ( to: temporarySourceFile, atomically: true , encoding: . utf8)
68
69
var requestInfo = initialRequestInfo
69
70
self . initialCommandLineCount = requestInfo. compilerArgs. count
70
71
71
72
var argumentIndexToRemove = requestInfo. compilerArgs. count - 1
72
- while argumentIndexToRemove >= 0 {
73
- var numberOfArgumentsToRemove = 1
73
+ while argumentIndexToRemove + 1 >= simultaneousRemove {
74
+ defer {
75
+ // argumentIndexToRemove can become negative by being decremented in the code below
76
+ let progress = 1 - ( Double ( max ( argumentIndexToRemove, 0 ) ) / Double( initialCommandLineCount) )
77
+ progressUpdate ( progress, " Reduced compiler arguments to \( requestInfo. compilerArgs. count) " )
78
+ }
79
+ var numberOfArgumentsToRemove = simultaneousRemove
74
80
// If the argument is preceded by -Xswiftc or -Xcxx, we need to remove the `-X` flag as well.
75
- if argumentIndexToRemove - numberOfArgumentsToRemove >= 0
76
- && requestInfo. compilerArgs [ argumentIndexToRemove - numberOfArgumentsToRemove] . hasPrefix ( " -X " )
77
- {
81
+ if requestInfo. compilerArgs [ safe: argumentIndexToRemove - numberOfArgumentsToRemove] ? . hasPrefix ( " -X " ) ?? false {
78
82
numberOfArgumentsToRemove += 1
79
83
}
80
84
81
- if let reduced = try await tryRemoving (
82
- ( argumentIndexToRemove - numberOfArgumentsToRemove + 1 ) ... argumentIndexToRemove,
83
- from: requestInfo
84
- ) {
85
+ let rangeToRemove = ( argumentIndexToRemove - numberOfArgumentsToRemove + 1 ) ... argumentIndexToRemove
86
+ if let reduced = try await tryRemoving ( rangeToRemove, from: requestInfo) {
85
87
requestInfo = reduced
86
88
argumentIndexToRemove -= numberOfArgumentsToRemove
87
89
continue
88
90
}
89
91
90
- // If removing the argument failed and the argument is preceded by an argument starting with `-`, try removing that as well.
91
- // E.g. removing `-F` followed by a search path.
92
- if argumentIndexToRemove - numberOfArgumentsToRemove >= 0
93
- && requestInfo. compilerArgs [ argumentIndexToRemove - numberOfArgumentsToRemove] . hasPrefix ( " - " )
94
- {
92
+ // If removing the argument failed and the argument is preceded by an argument starting with `-`, try removing
93
+ // that as well. E.g. removing `-F` followed by a search path.
94
+ if requestInfo. compilerArgs [ safe: argumentIndexToRemove - numberOfArgumentsToRemove] ? . hasPrefix ( " - " ) ?? false {
95
95
numberOfArgumentsToRemove += 1
96
- }
97
96
98
- // If the argument is preceded by -Xswiftc or -Xcxx, we need to remove the `-X` flag as well.
99
- if argumentIndexToRemove - numberOfArgumentsToRemove >= 0
100
- && requestInfo. compilerArgs [ argumentIndexToRemove - numberOfArgumentsToRemove] . hasPrefix ( " -X " )
101
- {
102
- numberOfArgumentsToRemove += 1
97
+ // If the argument is preceded by -Xswiftc or -Xcxx, we need to remove the `-X` flag as well.
98
+ if requestInfo. compilerArgs [ safe: argumentIndexToRemove - numberOfArgumentsToRemove] ? . hasPrefix ( " -X " ) ?? false {
99
+ numberOfArgumentsToRemove += 1
100
+ }
101
+
102
+ let rangeToRemove = ( argumentIndexToRemove - numberOfArgumentsToRemove + 1 ) ... argumentIndexToRemove
103
+ if let reduced = try await tryRemoving ( rangeToRemove, from: requestInfo) {
104
+ requestInfo = reduced
105
+ argumentIndexToRemove -= numberOfArgumentsToRemove
106
+ continue
107
+ }
103
108
}
104
109
105
- if let reduced = try await tryRemoving (
106
- ( argumentIndexToRemove - numberOfArgumentsToRemove + 1 ) ... argumentIndexToRemove,
107
- from: requestInfo
108
- ) {
109
- requestInfo = reduced
110
- argumentIndexToRemove -= numberOfArgumentsToRemove
111
- continue
112
- }
113
- argumentIndexToRemove -= 1
110
+ argumentIndexToRemove -= simultaneousRemove
114
111
}
115
112
116
113
return requestInfo
@@ -120,16 +117,28 @@ fileprivate class CommandLineArgumentReducer {
120
117
_ argumentsToRemove: ClosedRange < Int > ,
121
118
from requestInfo: RequestInfo
122
119
) async throws -> RequestInfo? {
120
+ logger. debug ( " Try removing the following compiler arguments: \n \( requestInfo. compilerArgs [ argumentsToRemove] ) " )
123
121
var reducedRequestInfo = requestInfo
124
122
reducedRequestInfo. compilerArgs. removeSubrange ( argumentsToRemove)
125
123
126
- let result = try await sourcekitdExecutor. run ( request: reducedRequestInfo. request ( for : temporarySourceFile ) )
124
+ let result = try await sourcekitdExecutor. run ( request: reducedRequestInfo)
127
125
if case . reproducesIssue = result {
128
- logSuccessfulReduction ( reducedRequestInfo )
126
+ logger . debug ( " Reduction successful " )
129
127
return reducedRequestInfo
130
128
} else {
131
129
// The reduced request did not crash. We did not find a reduced test case, so return `nil`.
130
+ logger. debug ( " Reduction did not reproduce the issue " )
131
+ return nil
132
+ }
133
+ }
134
+ }
135
+
136
+ fileprivate extension Array {
137
+ /// Access index in the array if it's in bounds or return `nil` if `index` is outside of the array's bounds.
138
+ subscript ( safe index: Int) - > Element? {
139
+ if index < 0 || index >= count {
132
140
return nil
133
141
}
142
+ return self [ index]
134
143
}
135
144
}
0 commit comments