Skip to content

Commit d9d64a6

Browse files
committed
Fix quadratic behavior when GenericOutputParser is written to many times per line
rdar://151481591
1 parent 1d66c4f commit d9d64a6

File tree

1 file changed

+6
-7
lines changed

1 file changed

+6
-7
lines changed

Sources/SWBCore/SpecImplementations/CommandLineToolSpec.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,7 +1436,7 @@ open class GenericOutputParser : TaskOutputParser {
14361436
public let toolBasenames: Set<String>
14371437

14381438
/// Buffered output that has not yet been parsed (parsing is line-by-line, so we buffer incomplete lines until we receive more output).
1439-
private var unparsedBytes: [UInt8] = []
1439+
private var unparsedBytes: ArraySlice<UInt8> = []
14401440

14411441
/// The Diagnostic that is being constructed, possibly across multiple lines of input.
14421442
private var inProgressDiagnostic: Diagnostic?
@@ -1483,19 +1483,18 @@ open class GenericOutputParser : TaskOutputParser {
14831483
// Forward the unparsed bytes immediately (without line buffering).
14841484
delegate.emitOutput(bytes)
14851485

1486-
// Append the new output to whatever partial line of output we might already have buffered.
1487-
unparsedBytes.append(contentsOf: bytes.bytes)
1488-
14891486
// Split the buffer into slices separated by newlines. The last slice represents the partial last line (there always is one, even if it's empty).
1490-
let lines = unparsedBytes.split(separator: UInt8(ascii: "\n"), maxSplits: .max, omittingEmptySubsequences: false)
1487+
var lines = bytes.split(separator: UInt8(ascii: "\n"), maxSplits: .max, omittingEmptySubsequences: false)
1488+
// Any unparsed bytes belong to the first line. We don't want to run `split` over these because it can lead to accidentally quadratic behavior if write is called many times per line.
1489+
lines[0] = unparsedBytes + lines[0]
14911490

14921491
// Parse any complete lines of output.
14931492
for line in lines.dropLast() {
14941493
parseLine(line)
14951494
}
14961495

1497-
// Remove any complete lines from the buffer, leaving only the last partial line (if any).
1498-
unparsedBytes.removeFirst(unparsedBytes.count - lines.last!.count)
1496+
// Track the last, incomplete line to as the unparsed bytes.
1497+
unparsedBytes = lines.last ?? []
14991498
}
15001499

15011500
/// Regex to extract location information from a diagnostic prefix (capture group 0 is the name, 1 is the line number, and 2 is the column).

0 commit comments

Comments
 (0)