@@ -297,26 +297,70 @@ public struct Driver {
297
297
}
298
298
}
299
299
300
- // Response files.
300
+ // MARK: - Response files.
301
301
extension Driver {
302
- /// Tokenize a single line in a response file
303
- private static func tokenizeResponseFileLine( _ line: String ) -> String {
304
- // FIXME: This is wrong. We need to do proper shell escaping.
305
- return line. replacingOccurrences ( of: " \\ " , with: " " )
302
+ /// Tokenize a single line in a response file.
303
+ ///
304
+ /// This method supports response files with:
305
+ /// 1. Double slash comments at the beginning of a line.
306
+ /// 2. Backslash escaping.
307
+ /// 3. Space character (U+0020 SPACE).
308
+ ///
309
+ /// - Returns: One line String ready to be used in the shell, if any.
310
+ ///
311
+ /// - Complexity: O(*n*), where *n* is the length of the line.
312
+ private static func tokenizeResponseFileLine< S: StringProtocol > ( _ line: S ) -> String ? {
313
+ if line. isEmpty { return nil }
314
+
315
+ // Support double dash comments only if they start at the beginning of a line.
316
+ if line. hasPrefix ( " // " ) { return nil }
317
+
318
+ var result : String = " "
319
+ /// Indicates if we just parsed an escaping backslash.
320
+ var isEscaping = false
321
+
322
+ for char in line {
323
+ if char. isNewline { return result }
324
+
325
+ // Backslash escapes to the next character.
326
+ if char == #"\"# , !isEscaping {
327
+ isEscaping = true
328
+ continue
329
+ } else if isEscaping {
330
+ // Disable escaping and keep parsing.
331
+ isEscaping = false
332
+ }
333
+
334
+ // Ignore spacing characters, except by the space character.
335
+ if char. isWhitespace && char != " " { continue }
336
+
337
+ result. append ( char)
338
+ }
339
+ return result. isEmpty ? nil : result
306
340
}
307
341
342
+ /// Tokenize each line of the response file, omitting empty lines.
343
+ ///
344
+ /// - Parameter content: response file's content to be tokenized.
345
+ private static func tokenizeResponseFile( _ content: String ) -> [ String ] {
346
+ return content
347
+ . split ( separator: " \n " )
348
+ . compactMap { tokenizeResponseFileLine ( $0) }
349
+ }
350
+
351
+ /// Recursively expands the response files.
352
+ /// - Parameter visitedResponseFiles: Set containing visited response files to detect recursive parsing.
308
353
private static func expandResponseFiles(
309
354
_ args: [ String ] ,
310
355
diagnosticsEngine: DiagnosticsEngine ,
311
356
visitedResponseFiles: inout Set < AbsolutePath >
312
357
) throws -> [ String ] {
313
- // FIXME: This is very very prelimary. Need to look at how Swift compiler expands response file.
314
-
315
358
var result : [ String ] = [ ]
316
359
317
360
// Go through each arg and add arguments from response files.
318
361
for arg in args {
319
362
if arg. first == " @ " , let responseFile = try ? AbsolutePath ( validating: String ( arg. dropFirst ( ) ) ) {
363
+ // Guard against infinite parsing loop.
320
364
guard visitedResponseFiles. insert ( responseFile) . inserted else {
321
365
diagnosticsEngine. emit ( . warn_recursive_response_file( responseFile) )
322
366
continue
@@ -326,7 +370,7 @@ extension Driver {
326
370
}
327
371
328
372
let contents = try localFileSystem. readFileContents ( responseFile) . cString
329
- let lines = contents . split ( separator : " \n " , omittingEmptySubsequences : true ) . map { tokenizeResponseFileLine ( String ( $0 ) ) }
373
+ let lines = tokenizeResponseFile ( contents )
330
374
result. append ( contentsOf: try expandResponseFiles ( lines, diagnosticsEngine: diagnosticsEngine, visitedResponseFiles: & visitedResponseFiles) )
331
375
} else {
332
376
result. append ( arg)
0 commit comments