Skip to content

Commit cfea672

Browse files
committed
Add logic to split command line arguments on Windows
Previously, we were splitting command line arguments on Windows using the same rules as on Unix, which was incorrect, most importantly because backslashes in the first component of a Windows command line invocation are not escaping anything but interpreted verbatim. Fixes #1020 rdar://120809063
1 parent 000a566 commit cfea672

File tree

5 files changed

+470
-161
lines changed

5 files changed

+470
-161
lines changed

Sources/SKCore/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_library(SKCore STATIC
1111
FileBuildSettings.swift
1212
MainFilesProvider.swift
1313
PathPrefixMapping.swift
14+
SplitShellCommand.swift
1415
Toolchain.swift
1516
ToolchainRegistry.swift
1617
XCToolchainPlist.swift)

Sources/SKCore/CompilationDatabase.swift

Lines changed: 4 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,11 @@ extension CompilationDatabase.Command: Codable {
251251
if let arguments = try container.decodeIfPresent([String].self, forKey: .arguments) {
252252
self.commandLine = arguments
253253
} else if let command = try container.decodeIfPresent(String.self, forKey: .command) {
254+
#if os(Windows)
255+
self.commandLine = splitWindowsCommandLine(command, initialCommandName: true)
256+
#else
254257
self.commandLine = splitShellEscapedCommand(command)
258+
#endif
255259
} else {
256260
throw CompilationDatabaseDecodingError.missingCommandOrArguments
257261
}
@@ -265,123 +269,3 @@ extension CompilationDatabase.Command: Codable {
265269
try container.encodeIfPresent(output, forKey: .output)
266270
}
267271
}
268-
269-
/// Split and unescape a shell-escaped command line invocation.
270-
///
271-
/// Examples:
272-
///
273-
/// ```
274-
/// abc def -> ["abc", "def"]
275-
/// abc\ def -> ["abc def"]
276-
/// abc"\""def -> ["abc\"def"]
277-
/// abc'\"'def -> ["abc\\"def"]
278-
/// ```
279-
///
280-
/// See clang's `unescapeCommandLine()`.
281-
public func splitShellEscapedCommand(_ cmd: String) -> [String] {
282-
struct Parser {
283-
var content: Substring
284-
var i: Substring.UTF8View.Index
285-
var result: [String] = []
286-
287-
var ch: UInt8 { self.content.utf8[i] }
288-
var done: Bool { self.content.endIndex == i }
289-
290-
init(_ string: Substring) {
291-
self.content = string
292-
self.i = self.content.utf8.startIndex
293-
}
294-
295-
mutating func next() {
296-
i = content.utf8.index(after: i)
297-
}
298-
299-
mutating func next(expect c: UInt8) {
300-
assert(c == ch)
301-
next()
302-
}
303-
304-
mutating func parse() -> [String] {
305-
while !done {
306-
switch ch {
307-
case UInt8(ascii: " "): next()
308-
default: parseString()
309-
}
310-
}
311-
return result
312-
}
313-
314-
mutating func parseString() {
315-
var str = ""
316-
STRING: while !done {
317-
switch ch {
318-
case UInt8(ascii: " "): break STRING
319-
case UInt8(ascii: "\""): parseDoubleQuotedString(into: &str)
320-
case UInt8(ascii: "\'"): parseSingleQuotedString(into: &str)
321-
default: parsePlainString(into: &str)
322-
}
323-
}
324-
result.append(str)
325-
}
326-
327-
mutating func parseDoubleQuotedString(into str: inout String) {
328-
next(expect: UInt8(ascii: "\""))
329-
var start = i
330-
while !done {
331-
switch ch {
332-
case UInt8(ascii: "\""):
333-
str += content[start..<i]
334-
next()
335-
return
336-
case UInt8(ascii: "\\"):
337-
str += content[start..<i]
338-
next()
339-
start = i
340-
if !done { fallthrough }
341-
default:
342-
next()
343-
}
344-
}
345-
str += content[start..<i]
346-
}
347-
348-
mutating func parseSingleQuotedString(into str: inout String) {
349-
next(expect: UInt8(ascii: "\'"))
350-
let start = i
351-
while !done {
352-
switch ch {
353-
case UInt8(ascii: "\'"):
354-
str += content[start..<i]
355-
next()
356-
return
357-
default:
358-
next()
359-
}
360-
}
361-
str += content[start..<i]
362-
}
363-
364-
mutating func parsePlainString(into str: inout String) {
365-
var start = i
366-
while !done {
367-
let _ch = ch
368-
switch _ch {
369-
case UInt8(ascii: "\""), UInt8(ascii: "\'"), UInt8(ascii: " "):
370-
str += content[start..<i]
371-
return
372-
case UInt8(ascii: "\\"):
373-
str += content[start..<i]
374-
next()
375-
start = i
376-
if !done { fallthrough }
377-
default:
378-
next()
379-
}
380-
}
381-
str += content[start..<i]
382-
}
383-
}
384-
385-
var parser = Parser(cmd[...])
386-
return parser.parse()
387-
}

0 commit comments

Comments
 (0)