Skip to content

Commit a3f8a82

Browse files
committed
fix: don't ICE on LineOverflow when the line contains multibyte characters
1 parent 348794e commit a3f8a82

5 files changed

Lines changed: 56 additions & 5 deletions

File tree

src/formatting.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,9 @@ impl FormattingError {
354354
// (space, target)
355355
pub(crate) fn format_len(&self) -> (usize, usize) {
356356
match self.kind {
357-
ErrorKind::LineOverflow(found, max) => (max, found - max),
357+
ErrorKind::LineOverflow(_, _, byte_start) => {
358+
(byte_start, self.line_buffer.len() - byte_start)
359+
}
358360
ErrorKind::TrailingWhitespace
359361
| ErrorKind::DeprecatedAttr
360362
| ErrorKind::BadAttr
@@ -564,8 +566,10 @@ impl<'a> FormatLines<'a> {
564566
}
565567

566568
// Check for any line width errors we couldn't correct.
567-
let error_kind = ErrorKind::LineOverflow(self.line_len, self.config.max_width());
568-
if self.line_len > self.config.max_width()
569+
let max = self.config.max_width();
570+
let error_kind =
571+
ErrorKind::LineOverflow(self.line_len, max, self.byte_offset_at_col(max));
572+
if self.line_len > max
569573
&& !self.is_skipped_line()
570574
&& self.should_report_error(kind, &error_kind)
571575
{
@@ -600,6 +604,22 @@ impl<'a> FormatLines<'a> {
600604
}
601605
}
602606

607+
/// Inverse of `Self::char`'s column accounting: walk `line_buffer` with
608+
/// the same rule (tab = `tab_spaces` cols, every other char = 1 col) and
609+
/// return the byte offset where the accumulated column count first reaches
610+
/// `target_col`. Returns `line_buffer.len()` if the line is shorter.
611+
fn byte_offset_at_col(&self, target_col: usize) -> usize {
612+
let tab_spaces = self.config.tab_spaces();
613+
let mut col = 0;
614+
for (idx, ch) in self.line_buffer.char_indices() {
615+
if col >= target_col {
616+
return idx;
617+
}
618+
col += if ch == '\t' { tab_spaces } else { 1 };
619+
}
620+
self.line_buffer.len()
621+
}
622+
603623
fn push_err(&mut self, kind: ErrorKind, is_comment: bool, is_string: bool) {
604624
self.errors.push(FormattingError {
605625
line: self.cur_line,

src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,13 @@ pub(crate) mod visitor;
106106
/// these can currently be propagated to clients.
107107
#[derive(Error, Debug)]
108108
pub enum ErrorKind {
109-
/// Line has exceeded character limit (found, maximum).
109+
/// Line has exceeded character limit (found, maximum, byte offset within
110+
/// the line where the overflow region begins).
110111
#[error(
111112
"line formatted, but exceeded maximum width \
112113
(maximum: {1} (see `max_width` option), found: {0})"
113114
)]
114-
LineOverflow(usize, usize),
115+
LineOverflow(usize, usize, usize),
115116
/// Line ends in whitespace.
116117
#[error("left behind trailing whitespace")]
117118
TrailingWhitespace,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "issue_6850"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
"☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃☃"
3+
}

tests/rustfmt/main.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,27 @@ fn dont_emit_ICE() {
203203
}
204204
}
205205

206+
#[test]
207+
fn dont_panic_on_line_overflow_with_multibyte_chars() {
208+
// See also https://github.com/rust-lang/rustfmt/issues/6850
209+
let args = [
210+
"--config",
211+
"error_on_line_overflow=true,error_on_unformatted=true",
212+
"tests/cargo-fmt/source/issue_6850/src/main.rs",
213+
];
214+
215+
let (_stdout, stderr) = rustfmt(&args);
216+
assert!(stderr.contains(
217+
"line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option), found: 126)"
218+
));
219+
220+
let panic_re = regex::Regex::new("thread.*panicked").unwrap();
221+
assert!(
222+
!panic_re.is_match(&stderr),
223+
"rustfmt panicked instead of reporting line overflow:\n{stderr}"
224+
);
225+
}
226+
206227
#[test]
207228
fn rustfmt_emits_error_when_control_brace_style_is_always_next_line() {
208229
// See also https://github.com/rust-lang/rustfmt/issues/5912

0 commit comments

Comments
 (0)