From 8d5689a7faa7c4d52bcbb46e8133081d0c950f69 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 9 Mar 2026 09:45:46 +0100 Subject: [PATCH] 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 --- crates/editor/src/display_map/block_map.rs | 26 +++++++++++++-------- crates/editor/src/display_map/dimensions.rs | 4 ++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 2673baae84ab74b2852004320cf1d94c5ed1ed42..d45165660d92170ecc176ebd8e038b890933bd57 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -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 { .. }); } diff --git a/crates/editor/src/display_map/dimensions.rs b/crates/editor/src/display_map/dimensions.rs index fd8efa6ca539d7eee8d59962ad7541d2bbc4fc4b..0bee934f8f87f1ad490cc74e60bb40bf86d8cdc8 100644 --- a/crates/editor/src/display_map/dimensions.rs +++ b/crates/editor/src/display_map/dimensions.rs @@ -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 {