fix concurrent-modification in BufferLine anchor iteration

`removeCells`, `insertCells`, and `dispose` each iterate over
`_anchors` while invoking `anchor.dispose()` on entries inside the
loop — but `dispose()` removes the anchor from the same list, which
shifts later indexes left and causes the for-loop to skip them.

Symptoms (no user-facing report yet, but real correctness bug):
- After `removeCells` with multiple anchors past the start, anchors
  that should be repositioned were silently left at their old `x`.
- After `insertCells` with anchors getting pushed past `_length`,
  ones meant to be disposed could survive.
- `BufferLine.dispose` would throw `ConcurrentModificationError` as
  soon as more than one anchor was attached.

Fix: iterate `_anchors.toList()` (a snapshot) in all three sites.
Cheap, safe, and matches the expected anchor-management semantics.

Surfaced by the unit tests added under T-91; that commit covers the
fix with regression tests.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-06 23:03:03 +02:00
co-authored by Claude
parent eb32396bd4
commit 1d75e8e1cd
2 changed files with 115 additions and 112 deletions
+8 -5
View File
@@ -179,8 +179,9 @@ class BufferLine with IndexedItem {
}
// Update anchors, remove anchors that are inside the removed range.
for (var i = 0; i < _anchors.length; i++) {
final anchor = _anchors[i];
// Iterate over a snapshot — anchor.dispose() removes itself from
// _anchors, so a live for-loop would skip later entries.
for (final anchor in _anchors.toList()) {
if (anchor.x >= start) {
if (anchor.x < start + count) {
anchor.dispose();
@@ -218,8 +219,9 @@ class BufferLine with IndexedItem {
}
// Update anchors, move anchors that are after the inserted range.
for (var i = 0; i < _anchors.length; i++) {
final anchor = _anchors[i];
// Iterate over a snapshot — anchor.dispose() removes itself from
// _anchors, so a live for-loop would skip later entries.
for (final anchor in _anchors.toList()) {
if (anchor.x >= start + count) {
anchor.reposition(anchor.x + count);
@@ -352,7 +354,8 @@ class BufferLine with IndexedItem {
}
void dispose() {
for (final anchor in _anchors) {
// Snapshot — anchor.dispose() removes itself from _anchors.
for (final anchor in _anchors.toList()) {
anchor.dispose();
}
}