Skip to content

Commit fdb3366

Browse files
committed
Fix panic from tab-expansion bug in LineOverflow
LineOverflow diagnostics treated visual columns (tabs expanded) as byte offsets, so a line with tabs could produced an out-of-bounds annotation and a panic. Now emits a normal overflow diagnostic.
1 parent 0332da0 commit fdb3366

File tree

3 files changed

+69
-1
lines changed

3 files changed

+69
-1
lines changed

src/formatting.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@ pub(crate) struct FormattingError {
311311
is_comment: bool,
312312
is_string: bool,
313313
pub(crate) line_buffer: String,
314+
// Number of spaces a tab represents when computing visual columns.
315+
// Needed to translate visual column based overflow ranges into byte indices.
316+
tab_spaces: usize,
314317
}
315318

316319
impl FormattingError {
@@ -321,6 +324,8 @@ impl FormattingError {
321324
kind,
322325
is_string: false,
323326
line_buffer: psess.span_to_first_line_string(span),
327+
// Default; actual value only matters for LineOverflow emitted via push_err
328+
tab_spaces: 4,
324329
}
325330
}
326331

@@ -347,7 +352,7 @@ impl FormattingError {
347352
// (space, target)
348353
pub(crate) fn format_len(&self) -> (usize, usize) {
349354
match self.kind {
350-
ErrorKind::LineOverflow(found, max) => (max, found - max),
355+
ErrorKind::LineOverflow(found, max) => self.line_overflow_byte_range(found, max),
351356
ErrorKind::TrailingWhitespace
352357
| ErrorKind::DeprecatedAttr
353358
| ErrorKind::BadAttr
@@ -365,6 +370,40 @@ impl FormattingError {
365370
_ => unreachable!(),
366371
}
367372
}
373+
374+
// Compute (start_byte, length) tuple for a LineOverflow error converting visual columns
375+
// (tabs expanded to `tab_spaces`) into byte indices within the stored line buffer.
376+
fn line_overflow_byte_range(&self, found: usize, max: usize) -> (usize, usize) {
377+
if max >= found || self.line_buffer.is_empty() {
378+
return (0, 0);
379+
}
380+
381+
let mut visual_col = 0;
382+
let mut start_byte = None;
383+
let mut end_byte = None;
384+
for (idx, ch) in self.line_buffer.char_indices() {
385+
let ch_width = if ch == '\t' { self.tab_spaces } else { 1 };
386+
let next_col = visual_col + ch_width;
387+
if start_byte.is_none() && next_col > max {
388+
start_byte = Some(idx);
389+
} else if start_byte.is_none() && next_col == max {
390+
// Start will be at next character (if any)
391+
}
392+
393+
if end_byte.is_none() && next_col >= found {
394+
end_byte = Some(idx + ch.len_utf8());
395+
break;
396+
}
397+
398+
visual_col = next_col;
399+
}
400+
401+
let start = start_byte.unwrap_or_else(|| self.line_buffer.len().saturating_sub(1));
402+
let end = end_byte.unwrap_or(self.line_buffer.len());
403+
let len = end.saturating_sub(start).max(1);
404+
405+
(start.min(self.line_buffer.len().saturating_sub(1)), len)
406+
}
368407
}
369408

370409
pub(crate) type FormatErrorMap = HashMap<FileName, Vec<FormattingError>>;
@@ -600,6 +639,7 @@ impl<'a> FormatLines<'a> {
600639
is_comment,
601640
is_string,
602641
line_buffer: self.line_buffer.clone(),
642+
tab_spaces: self.config.tab_spaces(),
603643
});
604644
}
605645

tests/source/issue-6632.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
macro_rules! impl_routes_and_health {
2+
($($feature:literal, $variant:ident),* $(,)?) => {
3+
impl EitherState {
4+
pub(crate) fn service_name(&self) -> &'static str {
5+
match self {
6+
$(
7+
#[cfg(feature = $feature)]
8+
Self::$variant(s) => s.service_name(),// BuildService::service_name(s.clone()),
9+
)*
10+
}
11+
}
12+
}
13+
};
14+
}

tests/target/issue-6632.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
macro_rules! impl_routes_and_health {
2+
($($feature:literal, $variant:ident),* $(,)?) => {
3+
impl EitherState {
4+
pub(crate) fn service_name(&self) -> &'static str {
5+
match self {
6+
$(
7+
#[cfg(feature = $feature)]
8+
Self::$variant(s) => s.service_name(),// BuildService::service_name(s.clone()),
9+
)*
10+
}
11+
}
12+
}
13+
};
14+
}

0 commit comments

Comments
 (0)