Describe the bug
text.Align.Apply() and text.VAlign.Apply() allocate the requested padding up front with no upper bound, so a large maxLength / maxLines takes the process down instead of returning a clamped string. Small negative values are handled correctly — only the large-positive side is unguarded.
Two distinct failure modes, and the second one is the reason I am reporting it:
math.MaxInt → recoverable panic, runtime error: makeslice: len out of range
1 << 40 → unrecoverable fatal error: runtime: out of memory, which recover() cannot catch, so a caller has no way to defend against it
Steps to reproduce
package main
import (
"fmt"
"math"
"github.com/jedib0t/go-pretty/v6/text"
)
func try(name string, f func()) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("PANIC %-28s -> %v\n", name, r)
return
}
fmt.Printf("ok %-28s\n", name)
}()
f()
}
func main() {
try("AlignLeft.Apply MaxInt", func() { text.AlignLeft.Apply("hi", math.MaxInt) })
try("AlignRight.Apply MaxInt", func() { text.AlignRight.Apply("hi", math.MaxInt) })
try("AlignCenter.Apply MaxInt", func() { text.AlignCenter.Apply("hi", math.MaxInt) })
try("AlignJustify.Apply MaxInt", func() { text.AlignJustify.Apply("hi", math.MaxInt) })
try("VAlignTop.Apply MaxInt", func() { text.VAlignTop.Apply([]string{"hi"}, math.MaxInt) })
try("VAlignMiddle.Apply MaxInt", func() { text.VAlignMiddle.Apply([]string{"hi"}, math.MaxInt) })
try("VAlignBottom.Apply MaxInt", func() { text.VAlignBottom.Apply([]string{"hi"}, math.MaxInt) })
try("AlignLeft.Apply 1<<40", func() { text.AlignLeft.Apply("hi", 1<<40) })
try("AlignLeft.Apply negative", func() { text.AlignLeft.Apply("hi", -5) })
}
Expected behavior
Return the text padded to whatever is actually representable (or return it unchanged), the same way the negative case already does. In no case should a width argument be able to abort the process.
Actual behavior
PANIC AlignLeft.Apply MaxInt -> runtime error: makeslice: len out of range
PANIC AlignRight.Apply MaxInt -> runtime error: makeslice: len out of range
PANIC AlignCenter.Apply MaxInt -> runtime error: makeslice: len out of range
PANIC AlignJustify.Apply MaxInt -> runtime error: makeslice: len out of range
PANIC VAlignTop.Apply MaxInt -> runtime error: makeslice: len out of range
PANIC VAlignMiddle.Apply MaxInt -> runtime error: makeslice: len out of range
PANIC VAlignBottom.Apply MaxInt -> runtime error: makeslice: len out of range
fatal error: runtime: out of memory
The run never reaches the last two cases: 1 << 40 kills the process outright. Tested separately, AlignLeft.Apply("hi", -5) correctly returns "hi".
Root cause
Horizontal — text/align.go. Apply() computes padding := maxLength - sLenWoE and hands it to padText(), which preallocates the whole result before writing a byte:
func padText(text string, left int, right int) string {
if left <= 0 && right <= 0 {
return text
}
var out strings.Builder
out.Grow(len(text) + left + right) // align.go:64 — unbounded
...
}
The left <= 0 && right <= 0 early return is why negatives are safe; there is no matching upper bound, so Grow(math.MaxInt) reaches makeslice.
Vertical — text/valign.go:42:
linesOut := strings.Split(strings.Repeat("\n", maxLines-1), "\n")
VAlign does guard the low side (if maxLines < 1 { return lines }) but not the high side, so maxLines = math.MaxInt asks strings.Repeat for a MaxInt-1 byte string.
Suggested fix
Clamp in padText() — it is the single choke point for all four Align values — for example by capping the grow hint at something sane and letting the write loops produce the rest, or by rejecting padding beyond a maximum representable width. VAlign.Apply() needs the mirror-image guard to the maxLines < 1 check it already has.
Version
github.com/jedib0t/go-pretty/v6 v6.8.3, go1.25.0, linux/amd64.
Note on how this is reachable
I hit this while probing extreme-value handling across terminal-formatting libraries rather than from a production crash, so treat the severity accordingly. It matters most where a width is computed rather than hard-coded — terminal-size arithmetic that underflows, or a column width taken from config or user input — because the 1 << 40 case cannot be contained with recover().
Describe the bug
text.Align.Apply()andtext.VAlign.Apply()allocate the requested padding up front with no upper bound, so a largemaxLength/maxLinestakes the process down instead of returning a clamped string. Small negative values are handled correctly — only the large-positive side is unguarded.Two distinct failure modes, and the second one is the reason I am reporting it:
math.MaxInt→ recoverable panic,runtime error: makeslice: len out of range1 << 40→ unrecoverablefatal error: runtime: out of memory, whichrecover()cannot catch, so a caller has no way to defend against itSteps to reproduce
Expected behavior
Return the text padded to whatever is actually representable (or return it unchanged), the same way the negative case already does. In no case should a width argument be able to abort the process.
Actual behavior
The run never reaches the last two cases:
1 << 40kills the process outright. Tested separately,AlignLeft.Apply("hi", -5)correctly returns"hi".Root cause
Horizontal —
text/align.go.Apply()computespadding := maxLength - sLenWoEand hands it topadText(), which preallocates the whole result before writing a byte:The
left <= 0 && right <= 0early return is why negatives are safe; there is no matching upper bound, soGrow(math.MaxInt)reachesmakeslice.Vertical —
text/valign.go:42:VAligndoes guard the low side (if maxLines < 1 { return lines }) but not the high side, somaxLines = math.MaxIntasksstrings.Repeatfor aMaxInt-1byte string.Suggested fix
Clamp in
padText()— it is the single choke point for all fourAlignvalues — for example by capping the grow hint at something sane and letting the write loops produce the rest, or by rejecting padding beyond a maximum representable width.VAlign.Apply()needs the mirror-image guard to themaxLines < 1check it already has.Version
github.com/jedib0t/go-pretty/v6 v6.8.3, go1.25.0, linux/amd64.Note on how this is reachable
I hit this while probing extreme-value handling across terminal-formatting libraries rather than from a production crash, so treat the severity accordingly. It matters most where a width is computed rather than hard-coded — terminal-size arithmetic that underflows, or a column width taken from config or user input — because the
1 << 40case cannot be contained withrecover().