From fb507675b1563b0eedacf99b349565bddf2cb8d3 Mon Sep 17 00:00:00 2001 From: Raekye Date: Wed, 7 Dec 2022 00:42:17 -0500 Subject: [PATCH 1/2] Fix panic in annotated_snippet dependency (rust-lang#4968). * This change is based on the code review by camsteffen on PR rust-lang#5039 by karyon: have rustfmt internally replace tabs with the corresponding number of spaces, so that columns/indices in the buffer passed to annotated_snippet are counted unambiguously. --- src/formatting.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/formatting.rs b/src/formatting.rs index 1e1e329f624..047351aa084 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -582,12 +582,14 @@ impl<'a> FormatLines<'a> { fn char(&mut self, c: char, kind: FullCodeCharKind) { self.newline_count = 0; self.line_len += if c == '\t' { + self.line_buffer + .push_str(&" ".repeat(self.config.tab_spaces())); self.config.tab_spaces() } else { - 1 + self.line_buffer.push(c); + c.len_utf8() }; self.last_was_space = c.is_whitespace(); - self.line_buffer.push(c); if kind.is_string() { self.current_line_contains_string_literal = true; } From 5e8ce93f9c579a87271a3b39b6c77e1758621101 Mon Sep 17 00:00:00 2001 From: Raekye Date: Sat, 30 Dec 2023 18:46:33 -0500 Subject: [PATCH 2/2] Test case that can validate fix for rust-lang#4968. * With hard_tabs, rustfmt preserves tabs and counts them as multiple characters/columns (based on configuration). * The annotated_snippet dependency, used to display errors, always counts tabs as 1 character. * If rustfmt tries to report an error on a line containing tabs, the indices are mismatched. * annotated_snippet will display the wrong range of the source code slice; in the extreme case, it can panic with out-of-bounds access. * The test case added in this commit is expected to currently fail, since this commit doesn't include the fix. --- src/test/parser.rs | 19 +++++++++++++++++++ tests/parser/issue_4968.rs | 12 ++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tests/parser/issue_4968.rs diff --git a/src/test/parser.rs b/src/test/parser.rs index d951c8469e6..3e8d3a73295 100644 --- a/src/test/parser.rs +++ b/src/test/parser.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use super::read_config; +use crate::FormatReportFormatterBuilder; use crate::modules::{ModuleResolutionError, ModuleResolutionErrorKind}; use crate::{ErrorKind, Input, Session}; @@ -69,3 +70,21 @@ fn crate_parsing_stashed_diag2() { let filename = "tests/parser/stashed-diag2.rs"; assert_parser_error(filename); } + +#[test] +fn indexing_mismatch_with_annotated_snippet() { + // See also https://github.com/rust-lang/rustfmt/issues/4968 + let filename = "tests/parser/issue_4968.rs"; + let file = PathBuf::from(filename); + let config = read_config(&file); + let mut session = Session::::new(config, None); + let report = session.format(Input::File(filename.into())).unwrap(); + let report = FormatReportFormatterBuilder::new(&report).build(); + { + // Panic can only be triggered if we actually try to write the report + // and call into the annotated_snippet dependency. + use std::io::Write; + + write!(&mut Vec::new(), "{report}").unwrap(); + } +} diff --git a/tests/parser/issue_4968.rs b/tests/parser/issue_4968.rs new file mode 100644 index 00000000000..eb0c65756ef --- /dev/null +++ b/tests/parser/issue_4968.rs @@ -0,0 +1,12 @@ +// rustfmt-hard_tabs: true +// rustfmt-max_width: 40 +// rustfmt-error_on_unformatted: true +// rustfmt-error_on_line_overflow: true + +fn foo(x: u32) { + if x > 10 { + if x > 20 { + println!("0123456789abcdefghijklmnopqrstuvwxyz"); + } + } +}