Mitigate panic in merge conflict resolution (#29678)

We have a report of a panic when indexing into
`BufferConflicts.block_ids` using the `old_range` from the
`ConflictsUpdated` event, indicating that the `block_ids` array can get
out of sync with the underlying `ConflictSet`. This PR adds a mitigation
so that we won't panic in this situation, as a stopgap until the bug can
be reproduced in a test and fixed at the root.

Release Notes:

- N/A
This commit is contained in:
Cole Miller 2025-04-30 11:25:26 -04:00 committed by GitHub
parent 632f08d2a3
commit ff4900c942
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -14,6 +14,7 @@ use ui::{
ActiveTheme, AnyElement, Element as _, StatefulInteractiveElement, Styled,
StyledTypography as _, div, h_flex, rems,
};
use util::{debug_panic, maybe};
pub(crate) struct ConflictAddon {
buffers: HashMap<BufferId, BufferConflicts>,
@ -89,12 +90,22 @@ fn excerpt_for_buffer_updated(
cx: &mut Context<Editor>,
) {
let conflicts_len = conflict_set.read(cx).snapshot().conflicts.len();
let buffer_id = conflict_set.read(cx).snapshot().buffer_id;
let Some(buffer_conflicts) = editor
.addon_mut::<ConflictAddon>()
.unwrap()
.buffers
.get(&buffer_id)
else {
return;
};
let addon_conflicts_len = buffer_conflicts.block_ids.len();
conflicts_updated(
editor,
conflict_set,
&ConflictSetUpdate {
buffer_range: None,
old_range: 0..conflicts_len,
old_range: 0..addon_conflicts_len,
new_range: 0..conflicts_len,
},
cx,
@ -174,10 +185,37 @@ fn conflicts_updated(
return;
};
let old_range = maybe!({
let conflict_addon = editor.addon_mut::<ConflictAddon>().unwrap();
let buffer_conflicts = conflict_addon.buffers.get(&buffer_id)?;
match buffer_conflicts.block_ids.get(event.old_range.clone()) {
Some(_) => Some(event.old_range.clone()),
None => {
debug_panic!(
"conflicts updated event old range is invalid for buffer conflicts view (block_ids len is {:?}, old_range is {:?})",
buffer_conflicts.block_ids.len(),
event.old_range,
);
if event.old_range.start <= event.old_range.end {
Some(
event.old_range.start.min(buffer_conflicts.block_ids.len())
..event.old_range.end.min(buffer_conflicts.block_ids.len()),
)
} else {
None
}
}
}
});
// Remove obsolete highlights and blocks
let conflict_addon = editor.addon_mut::<ConflictAddon>().unwrap();
if let Some(buffer_conflicts) = conflict_addon.buffers.get_mut(&buffer_id) {
let old_conflicts = buffer_conflicts.block_ids[event.old_range.clone()].to_owned();
if let Some((buffer_conflicts, old_range)) = conflict_addon
.buffers
.get_mut(&buffer_id)
.zip(old_range.clone())
{
let old_conflicts = buffer_conflicts.block_ids[old_range].to_owned();
let mut removed_highlighted_ranges = Vec::new();
let mut removed_block_ids = HashSet::default();
for (conflict_range, block_id) in old_conflicts {
@ -263,9 +301,11 @@ fn conflicts_updated(
let new_block_ids = editor.insert_blocks(blocks, None, cx);
let conflict_addon = editor.addon_mut::<ConflictAddon>().unwrap();
if let Some(buffer_conflicts) = conflict_addon.buffers.get_mut(&buffer_id) {
if let Some((buffer_conflicts, old_range)) =
conflict_addon.buffers.get_mut(&buffer_id).zip(old_range)
{
buffer_conflicts.block_ids.splice(
event.old_range.clone(),
old_range,
new_conflicts
.iter()
.map(|conflict| conflict.range.clone())