editor: Fix underflow panic in block map sync when blocks overlap (#51078)
In `BlockMap::sync`, blocks within an edited region are sorted and processed sequentially. Each block placement computes `rows_before_block` by subtracting `new_transforms.summary().input_rows` from the block's target position. The `Near`/`Below` cases have a guard that skips the block if the target is already behind the current progress, but `Above` and `Replace` were missing this guard. When a `Replace` block (tie_break 0) is processed before an `Above` block (tie_break 1) at the same or overlapping position, the `Replace` block consumes multiple input rows, advancing `input_rows` past the `Above` block's position. The subsequent `position - input_rows` subtraction underflows on `u32`, producing a huge `RowDelta` that wraps `wrap_row_end` past `wrap_row_start`, creating an inverted range that propagates through the display map layers and panics as `begin <= end (47 <= 0)` in a rope chunk slice. Add underflow guards to `Above` and `Replace`, matching the existing pattern in `Near`/`Below`. Release Notes: - Fixed a source of underflowing subtractions causing spurious panics
This commit is contained in:
1 parent
3f2ddcbca3
commit
8d5689a7fa
2 files changed
+20
-10
No files matched your search
@@ -1091,23 +1091,29 @@ impl BlockMap {
|
||||
};
|
||||
|
||||
let rows_before_block;
|
||||
match block_placement {
|
||||
BlockPlacement::Above(position) => {
|
||||
rows_before_block = position - new_transforms.summary().input_rows;
|
||||
let input_rows = new_transforms.summary().input_rows;
|
||||
match &block_placement {
|
||||
&BlockPlacement::Above(position) => {
|
||||
let Some(delta) = position.checked_sub(input_rows) else {
|
||||
continue;
|
||||
};
|
||||
rows_before_block = delta;
|
||||
just_processed_folded_buffer = false;
|
||||
}
|
||||
BlockPlacement::Near(position) | BlockPlacement::Below(position) => {
|
||||
&BlockPlacement::Near(position) | &BlockPlacement::Below(position) => {
|
||||
if just_processed_folded_buffer {
|
||||
continue;
|
||||
}
|
||||
if position + RowDelta(1) < new_transforms.summary().input_rows {
|
||||
let Some(delta) = (position + RowDelta(1)).checked_sub(input_rows) else {
|
||||
continue;
|
||||
}
|
||||
rows_before_block =
|
||||
(position + RowDelta(1)) - new_transforms.summary().input_rows;
|
||||
};
|
||||
rows_before_block = delta;
|
||||
}
|
||||
BlockPlacement::Replace(ref range) => {
|
||||
rows_before_block = *range.start() - new_transforms.summary().input_rows;
|
||||
BlockPlacement::Replace(range) => {
|
||||
let Some(delta) = range.start().checked_sub(input_rows) else {
|
||||
continue;
|
||||
};
|
||||
rows_before_block = delta;
|
||||
summary.input_rows = WrapRow(1) + (*range.end() - *range.start());
|
||||
just_processed_folded_buffer = matches!(block, Block::FoldedBuffer { .. });
|
||||
}
|
||||
|
||||
@@ -41,6 +41,10 @@ macro_rules! impl_for_row_types {
|
||||
pub fn saturating_sub(self, other: $row_delta) -> $row {
|
||||
$row(self.0.saturating_sub(other.0))
|
||||
}
|
||||
|
||||
pub fn checked_sub(self, other: $row) -> Option<$row_delta> {
|
||||
self.0.checked_sub(other.0).map($row_delta)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::Add for $row {
|
||||
|
||||
Reference in new issue
Block a user