From 1bc923c1bc9c21787253400cbdedbf146c440fb1 Mon Sep 17 00:00:00 2001 From: Edouard Schweisguth Date: Tue, 21 Apr 2026 17:40:58 +0000 Subject: [PATCH] Fix panic on text align with unicode --- text/align.go | 7 +++++++ text/align_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/text/align.go b/text/align.go index 33512ee..18cc59e 100644 --- a/text/align.go +++ b/text/align.go @@ -127,6 +127,13 @@ func justifyText(text string, textLength int, maxLength int) string { // get the number of spaces to insert into the text numSpacesNeeded := maxLength - textLength + strings.Count(text, " ") + if numSpacesNeeded < 0 { + // textLength (display-width) exceeds maxLength; this can happen + // when the cell contains wide Unicode characters (e.g. CJK) whose + // display width is greater than their rune count. Return the text + // as-is; truncation is the caller's responsibility. + return text + } numSpacesNeededBetweenWords := 0 if len(words) > 1 { numSpacesNeededBetweenWords = numSpacesNeeded / (len(words) - 1) diff --git a/text/align_test.go b/text/align_test.go index 75b7425..78e1aed 100644 --- a/text/align_test.go +++ b/text/align_test.go @@ -67,6 +67,21 @@ func TestAlign_Apply(t *testing.T) { assert.Equal(t, "+5.43x ", AlignAuto.Apply("+5.43x", 12)) } +func TestAlign_Apply_JustifyCJKOverflow(t *testing.T) { + // U+4222 (䈢) has display width 2; the string below has display width 42, + // which exceeds maxLength=40. AlignJustify.Apply must not panic. + cell := "0000000000000000000000000000000000 0000䈢0" + assert.NotPanics(t, func() { + assert.Equal(t, cell, AlignJustify.Apply(cell, 40)) + }) + + // Further sanity checks: any CJK-only cell whose display width exceeds + // maxLength must also be returned unchanged without panicking. + assert.NotPanics(t, func() { + assert.Equal(t, "中文", AlignJustify.Apply("中文", 3)) + }) +} + func TestAlign_Apply_ColoredText(t *testing.T) { // AlignDefault & AlignLeft are the same assert.Equal(t, "\x1b[33mJon Snow\x1b[0m ", AlignDefault.Apply("\x1b[33mJon Snow\x1b[0m", 12)) @@ -164,3 +179,29 @@ func TestAlign_MarkdownProperty_WithMinLength(t *testing.T) { assert.Equal(t, " --- ", AlignDefault.MarkdownProperty(3)) assert.Equal(t, " ---- ", AlignDefault.MarkdownProperty(4)) } + +// FuzzAlign_Apply exercises Align.Apply across all alignment modes with +// arbitrary UTF-8 input (including wide Unicode characters) and arbitrary +// maxLength values. It guards against panics such as the "strings: negative +// Repeat count" crash in justifyText when the display width of the cell +// exceeds maxLength. +func FuzzAlign_Apply(f *testing.F) { + f.Add("Jon Snow", 12) + f.Add("0000000000000000000000000000000000 0000䈢0", 40) + f.Add("中文 字符", 3) + f.Add("", 5) + f.Add("a b c", 0) + f.Add("\x1b[33mJon Snow\x1b[0m", 12) + + aligns := []Align{AlignDefault, AlignLeft, AlignCenter, AlignJustify, AlignRight, AlignAuto} + f.Fuzz(func(t *testing.T, s string, maxLength int) { + // keep maxLength in a sane range; negative/huge values are not the + // concern of this fuzz target. + if maxLength < 0 || maxLength > 1024 { + t.Skip() + } + for _, a := range aligns { + a.Apply(s, maxLength) + } + }) +}