fix: clamp ProgressBar barLength to non-negative value to avoid crash (#1525)

- Closes #1547.
- A negative progress value (e.g. from a race in progress events) would
produce a negative barLength and crash inside String(repeating:count:).
Wrap the computed length in max(0, ...) and add a regression test that
calls set(size: -10) and exercises draw(state:detail:).
This commit is contained in:
Erwan Legrand
2026-05-21 19:11:28 -07:00
committed by GitHub
parent de780c1478
commit 831a6bfe1c
2 changed files with 17 additions and 1 deletions
+1 -1
View File
@@ -275,7 +275,7 @@ extension ProgressBar {
// 45 reserves space for components rendered after the bar (size, speed, time, etc.)
let usedWidth = (useColor ? joinedComponents.visibleLength : joinedComponents.count) + 45
let remainingWidth = max(config.width - usedWidth, 1)
let barLength = min(remainingWidth, state.finished ? remainingWidth : Int(Int64(remainingWidth) * value / total))
let barLength = min(remainingWidth, max(0, state.finished ? remainingWidth : Int(Int64(remainingWidth) * value / total)))
let barPaddingLength = remainingWidth - barLength
if useColor {
let filledBar = EscapeSequence.colored(String(repeating: config.theme.bar, count: barLength), EscapeSequence.green)
@@ -749,6 +749,22 @@ final class ProgressBarTests: XCTestCase {
let _ = progress.draw()
}
func testProgressBarNegativeValue() async throws {
// Regression test: a negative progress value (e.g. from a race in progress events)
// must not cause String(repeating:count:) to be called with a negative count.
let config = try ProgressConfig(
description: "Task",
showProgressBar: true,
totalSize: 50,
width: 57
)
let progress = ProgressBar(config: config)
progress.set(size: -10)
// draw(state:detail:) should clamp barLength to [0, remainingWidth] and not crash.
let state = progress.state.withLock { $0 }
let _ = progress.draw(state: state, detail: .full)
}
func testItemsName() async throws {
let config = try ProgressConfig(
description: "Task",