Skip to content

Commit 72aa50e

Browse files
remi-dupredjc
authored andcommitted
fix(utils): undefined behavior in truncate_str when tail is larger than width
1 parent e6882ab commit 72aa50e

File tree

1 file changed

+21
-11
lines changed

1 file changed

+21
-11
lines changed

src/utils.rs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,10 @@ pub(crate) fn char_width(_c: char) -> usize {
814814
/// escapes code will still be honored. If truncation takes place
815815
/// the tail string will be appended.
816816
pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> {
817+
if measure_text_width(s) <= width {
818+
return Cow::Borrowed(s);
819+
}
820+
817821
#[cfg(feature = "ansi-parsing")]
818822
{
819823
use std::cmp::Ordering;
@@ -825,12 +829,12 @@ pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> {
825829
match item {
826830
(s, false) => {
827831
if rv.is_none() {
828-
if str_width(s) + length > width - str_width(tail) {
832+
if str_width(s) + length > width.saturating_sub(str_width(tail)) {
829833
let ts = iter.current_slice();
830834

831835
let mut s_byte = 0;
832836
let mut s_width = 0;
833-
let rest_width = width - str_width(tail) - length;
837+
let rest_width = width.saturating_sub(str_width(tail)).saturating_sub(length);
834838
for c in s.chars() {
835839
s_byte += c.len_utf8();
836840
s_width += char_width(c);
@@ -869,15 +873,11 @@ pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> {
869873

870874
#[cfg(not(feature = "ansi-parsing"))]
871875
{
872-
if s.len() <= width - tail.len() {
873-
Cow::Borrowed(s)
874-
} else {
875-
Cow::Owned(format!(
876-
"{}{}",
877-
s.get(..width - tail.len()).unwrap_or_default(),
878-
tail
879-
))
880-
}
876+
Cow::Owned(format!(
877+
"{}{}",
878+
&s[..width.saturating_sub(tail.len())],
879+
tail
880+
))
881881
}
882882
}
883883

@@ -998,13 +998,23 @@ fn test_truncate_str() {
998998
&truncate_str(&s, 6, ""),
999999
&format!("foo {}", style("バ").red().force_styling(true))
10001000
);
1001+
let s = format!("foo {}", style("バー").red().force_styling(true));
1002+
assert_eq!(
1003+
&truncate_str(&s, 2, "!!!"),
1004+
&format!("!!!{}", style("").red().force_styling(true))
1005+
);
10011006
}
10021007

10031008
#[test]
10041009
fn test_truncate_str_no_ansi() {
1010+
assert_eq!(&truncate_str("foo bar", 7, "!"), "foo bar");
10051011
assert_eq!(&truncate_str("foo bar", 5, ""), "foo b");
10061012
assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !");
10071013
assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar...");
1014+
assert_eq!(&truncate_str("foo bar", 0, ""), "");
1015+
assert_eq!(&truncate_str("foo bar", 0, "!"), "!");
1016+
assert_eq!(&truncate_str("foo bar", 2, "!!!"), "!!!");
1017+
assert_eq!(&truncate_str("ab", 2, "!!!"), "ab");
10081018
}
10091019

10101020
#[test]

0 commit comments

Comments
 (0)