From 3e867a056018c507d79396cb5c5b4b8309c609c2 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Fri, 28 May 2021 11:00:37 +0000 Subject: [PATCH] Fix crashes with cut-off fullwidth characters There's a few places in Alacritty where it was assumed that after a WIDE_CHAR cell, there'd always be a WIDE_CHAR_SPACER. However since resizes in the alternate screen buffer do not reflow any content, it's possible to have a WIDE_CHAR without any WIDE_CHAR_SPACER right behind it. This patch changes these instances to be more defensive about accepting potentially unreasonable input data caused by alt screen resizes. Fixes #5185. Fixes #5170. --- CHANGELOG.md | 1 + alacritty_terminal/src/term/mod.rs | 6 ++-- alacritty_terminal/src/term/search.rs | 42 +++++++++++++++++++++------ 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14984fd5..9a3778f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Regression in rendering performance with dense grids since 0.6.0 +- Crash/Freezes with partially visible fullwidth characters due to alt screen resize ## 0.8.0 diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index 2fb4da34..256f2f29 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -699,7 +699,9 @@ impl Term { point.column = Column(1); point.line += 1; }, - Direction::Right if flags.contains(Flags::WIDE_CHAR) => point.column += 1, + Direction::Right if flags.contains(Flags::WIDE_CHAR) => { + point.column = min(point.column + 1, self.last_column()); + }, Direction::Left if flags.intersects(Flags::WIDE_CHAR | Flags::WIDE_CHAR_SPACER) => { if flags.contains(Flags::WIDE_CHAR_SPACER) { point.column -= 1; @@ -774,7 +776,7 @@ impl Term { // Remove wide char and spacer. let wide = cursor_cell.flags.contains(Flags::WIDE_CHAR); let point = self.grid.cursor.point; - if wide { + if wide && point.column + 1 < self.columns() { self.grid[point.line][point.column + 1].flags.remove(Flags::WIDE_CHAR_SPACER); } else { self.grid[point.line][point.column - 1].clear_wide(); diff --git a/alacritty_terminal/src/term/search.rs b/alacritty_terminal/src/term/search.rs index 93345e4f..fd90f0e1 100644 --- a/alacritty_terminal/src/term/search.rs +++ b/alacritty_terminal/src/term/search.rs @@ -239,7 +239,12 @@ impl Term { } // Stop once we've reached the target point. - if point == end { + // + // We check beyond the point itself to account for skipped characters after wide chars + // without spacer. + if (direction == Direction::Right && point >= end) + || (direction == Direction::Left && point <= end) + { break; } @@ -497,8 +502,10 @@ impl<'a, T> Iterator for RegexIter<'a, T> { mod tests { use super::*; + use crate::config::Config; use crate::index::{Column, Line}; use crate::term::test::mock_term; + use crate::term::SizeInfo; #[test] fn regex_right() { @@ -571,8 +578,8 @@ mod tests { "); let dfas = RegexSearch::new("nothing").unwrap(); - let start = Point::new(Line(2), Column(0)); - let end = Point::new(Line(0), Column(4)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(2), Column(4)); assert_eq!(term.regex_search_right(&dfas, start, end), None); } @@ -586,8 +593,8 @@ mod tests { "); let dfas = RegexSearch::new("nothing").unwrap(); - let start = Point::new(Line(0), Column(4)); - let end = Point::new(Line(2), Column(0)); + let start = Point::new(Line(2), Column(4)); + let end = Point::new(Line(0), Column(0)); assert_eq!(term.regex_search_left(&dfas, start, end), None); } @@ -723,15 +730,15 @@ mod tests { "); let dfas = RegexSearch::new("🦇x").unwrap(); - let start = Point::new(Line(1), Column(0)); - let end = Point::new(Line(0), Column(3)); + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(1), Column(3)); let match_start = Point::new(Line(0), Column(0)); let match_end = Point::new(Line(0), Column(2)); assert_eq!(term.regex_search_right(&dfas, start, end), Some(match_start..=match_end)); let dfas = RegexSearch::new("x🦇").unwrap(); - let start = Point::new(Line(0), Column(2)); - let end = Point::new(Line(1), Column(0)); + let start = Point::new(Line(1), Column(2)); + let end = Point::new(Line(0), Column(0)); let match_start = Point::new(Line(1), Column(1)); let match_end = Point::new(Line(1), Column(3)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); @@ -774,4 +781,21 @@ mod tests { let match_end = Point::new(Line(1), Column(1)); assert_eq!(term.regex_search_left(&dfas, start, end), Some(match_start..=match_end)); } + + #[test] + fn wide_without_spacer() { + let size = SizeInfo::new(2., 2., 1., 1., 0., 0., false); + let mut term = Term::new(&Config::<()>::default(), size, ()); + term.grid[Line(0)][Column(0)].c = 'x'; + term.grid[Line(0)][Column(1)].c = '字'; + term.grid[Line(0)][Column(1)].flags = Flags::WIDE_CHAR; + + let dfas = RegexSearch::new("test").unwrap(); + + let start = Point::new(Line(0), Column(0)); + let end = Point::new(Line(0), Column(1)); + + let mut iter = RegexIter::new(start, end, Direction::Right, &term, &dfas); + assert_eq!(iter.next(), None); + } }