History

acp: Fix panic with edit file tool (#36732)

We had a frequent panic when the agent was using our edit file tool. The root cause was that we were constructing a `BufferDiff` with `BufferDiff::new`, then calling `set_base_text`, but not waiting for that asynchronous operation to finish. This means there was a window of time where the diff's base text was set to the initial value of `""`--that's not a problem in itself, but it was possible for us to call `PendingDiff::update` during that window, which calls `BufferDiff::update_diff`, which calls `BufferDiffSnapshot::new_with_base_buffer`, which takes two arguments `base_text` and `base_text_snapshot` that are supposed to represent the same text. We were getting the first of those arguments from the `base_text` field of `PendingDiff`, which is set immediately to the target base text without waiting for `BufferDiff::set_base_text` to run to completion; and the second from the `BufferDiff` itself, which still has the empty base text during that window. As a result of that mismatch, we could end up adding `DeletedHunk` diff transforms to the multibuffer for the diff card even though the multibuffer's base text was empty, ultimately leading to a panic very far away in rendering code. I've fixed this by adding a new `BufferDiff` constructor for the case where the buffer contents and the base text are (initially) the same, like for the diff cards, and so we don't need an async diff calculation. I also added a debug assertion to catch the basic issue here earlier, when `BufferDiffSnapshot::new_with_base_buffer` is called with two base texts that don't match. Release Notes: - N/A ---------

Cole Miller committed 1 year ago

Commit d88fd001 parent f4ba7992 files · +57 −16Browse files

Showing the changes to crates/buffer_diff/src/buffer_diff.rs (1 of 2 files). Show all

Unified · Split

crates/buffer_diff/src/buffer_diff.rs+32−1
@@ -162,6 +162,22 @@ impl BufferDiffSnapshot { · 161 unmodified lines
162 }162 }
163 }163 }
164 164
165 fn unchanged(
166 buffer: &text::BufferSnapshot,
167 base_text: language::BufferSnapshot,
168 ) -> BufferDiffSnapshot {
169 debug_assert_eq!(buffer.text(), base_text.text());
170 BufferDiffSnapshot {
171 inner: BufferDiffInner {
172 base_text,
173 hunks: SumTree::new(buffer),
174 pending_hunks: SumTree::new(buffer),
175 base_text_exists: false,
176 },
177 secondary_diff: None,
178 }
179 }
180
165 fn new_with_base_text(181 fn new_with_base_text(
166 buffer: text::BufferSnapshot,182 buffer: text::BufferSnapshot,
167 base_text: Option<Arc<String>>,183 base_text: Option<Arc<String>>,
@@ -213,7 +229,10 @@ impl BufferDiffSnapshot { · 45 unmodified lines
213 cx: &App,229 cx: &App,
214 ) -> impl Future<Output = Self> + use<> {230 ) -> impl Future<Output = Self> + use<> {
215 let base_text_exists = base_text.is_some();231 let base_text_exists = base_text.is_some();
216 let base_text_pair = base_text.map(|text| (text, base_text_snapshot.as_rope().clone()));232 let base_text_pair = base_text.map(|text| {
233 debug_assert_eq!(&*text, &base_text_snapshot.text());
234 (text, base_text_snapshot.as_rope().clone())
235 });
217 cx.background_executor()236 cx.background_executor()
218 .spawn_labeled(*CALCULATE_DIFF_TASK, async move {237 .spawn_labeled(*CALCULATE_DIFF_TASK, async move {
219 Self {238 Self {
@@ -873,6 +892,18 @@ impl BufferDiff { · 653 unmodified lines
873 }892 }
874 }893 }
875 894
895 pub fn new_unchanged(
896 buffer: &text::BufferSnapshot,
897 base_text: language::BufferSnapshot,
898 ) -> Self {
899 debug_assert_eq!(buffer.text(), base_text.text());
900 BufferDiff {
901 buffer_id: buffer.remote_id(),
902 inner: BufferDiffSnapshot::unchanged(buffer, base_text).inner,
903 secondary_diff: None,
904 }
905 }
906
876 #[cfg(any(test, feature = "test-support"))]907 #[cfg(any(test, feature = "test-support"))]
877 pub fn new_with_base_text(908 pub fn new_with_base_text(
878 base_text: &str,909 base_text: &str,