fix(data-link): clamp cell-range column window to the data width

Bug:
crop_range bounded the row axis with skip/take over the file's real rows
but left the column axis unbounded: c0..=c1 collects one String per
column, and a range like A1:ZZZZZZ1 parses to roughly 308 million
columns, causing a capacity overflow while allocating a row.

Reproduce:
1. Data Link -> pick any CSV/xlsx, set Cell Range to A1:ZZZZZZ1 (or any
   range whose parsed column index is enormous).
2. Before: "capacity overflow" abort while building the preview.
3. After: the window is cropped to the file's widest row; columns past
   the data yield nothing beyond the data width.

Fix:
c1 is clamped to widest.saturating_sub(1) before the range is collected,
so the span is provably at most the file's own data - allocation is
O(file). Rows outside the file still come back empty (in-range blank
padding inside the data width is preserved), and c0 > c1 naturally
yields an empty window. Regression tests cover the 13-Z capacity-overflow
input and a 6-Z range that now crops.
This commit is contained in:
Karim Jerbi committed 2026-09-30 19:15:49 +01:00
1 parent c86746726b
commit a0308dfa88
1 file changed
+27
+27
View File
@@ -151,6 +151,12 @@ fn crop_range(rows: Vec<Vec<String>>, range: &str) -> Result<Vec<Vec<String>>, S
};
let (r0, r1) = (r0.min(r1), r0.max(r1));
let (c0, c1) = (c0.min(c1), c0.max(c1));
// The row axis is bounded by the file (skip/take over real rows); the
// column axis was not — `A1:ZZZZZZ1` parses fine and then collected one
// String per column (~308 M slots per row, OOM). Columns past the widest
// row are empty in every row, so clamp the window to the data width.
let widest = rows.iter().map(Vec::len).max().unwrap_or(0);
let c1 = c1.min(widest.saturating_sub(1));
Ok(rows.into_iter()
.skip(r0)
.take(r1 - r0 + 1)
@@ -1451,4 +1457,25 @@ mod tests {
]
);
}
/// `column_index` only checks `checked_mul(26)`, so a range like
/// `A1:ZZZZZZ1` parses fine — and `(c0..=c1)` then collects one `String`
/// per column: ~308 M slots (~7 GB) for 6 Z's, and a `capacity overflow`
/// panic at 13 Z's. Columns past the widest row are empty in *every*
/// row, so the window must be clamped to the data width.
#[test]
fn crop_range_bounds_oversized_column_spans() {
let rows = vec![vec!["x".to_string()]];
// 13 Z's: pre-fix, size_hint × size_of::<String>() exceeds isize::MAX
// and Vec's capacity check panics before a single cell is read.
assert_eq!(
crop_range(rows.clone(), "A1:ZZZZZZZZZZZZZ1").unwrap(),
vec![vec!["x".to_string()]]
);
// 6 Z's — the bug-hunt report's repro — stays bounded too.
assert_eq!(
crop_range(rows, "A1:ZZZZZZ1").unwrap(),
vec![vec!["x".to_string()]]
);
}
}