summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2021-09-11 15:06:44 +0200
committerBram Moolenaar <Bram@vim.org>2021-09-11 15:06:44 +0200
commit4b4b1b84eee70b74fa3bb57624533c65bafd8428 (patch)
treef27ec20083b3a70710a250be2e16c8131d2a5a85
parent56e14698b45a9c4ef8fa65c55c9bfe86fbb3d6bf (diff)
downloadvim-git-4b4b1b84eee70b74fa3bb57624533c65bafd8428.tar.gz
patch 8.2.3426: crash when deleting a listener in a listener callbackv8.2.3426
Problem: Crash when deleting a listener in a listener callback. (Naohiro Ono) Solution: Mark the listener and delete it later.
-rw-r--r--src/change.c38
-rw-r--r--src/testdir/test_listener.vim18
-rw-r--r--src/version.c2
3 files changed, 52 insertions, 6 deletions
diff --git a/src/change.c b/src/change.c
index 799aa5efb..e17195620 100644
--- a/src/change.c
+++ b/src/change.c
@@ -293,6 +293,18 @@ f_listener_flush(typval_T *argvars, typval_T *rettv UNUSED)
invoke_listeners(buf);
}
+
+ static void
+remove_listener(buf_T *buf, listener_T *lnr, listener_T *prev)
+{
+ if (prev != NULL)
+ prev->lr_next = lnr->lr_next;
+ else
+ buf->b_listener = lnr->lr_next;
+ free_callback(&lnr->lr_callback);
+ vim_free(lnr);
+}
+
/*
* listener_remove() function
*/
@@ -317,12 +329,13 @@ f_listener_remove(typval_T *argvars, typval_T *rettv)
next = lnr->lr_next;
if (lnr->lr_id == id)
{
- if (prev != NULL)
- prev->lr_next = lnr->lr_next;
- else
- buf->b_listener = lnr->lr_next;
- free_callback(&lnr->lr_callback);
- vim_free(lnr);
+ if (textwinlock > 0)
+ {
+ // in invoke_listeners(), clear ID and delete later
+ lnr->lr_id = 0;
+ return;
+ }
+ remove_listener(buf, lnr, prev);
rettv->vval.v_number = 1;
return;
}
@@ -357,6 +370,7 @@ invoke_listeners(buf_T *buf)
linenr_T added = 0;
int save_updating_screen = updating_screen;
static int recursive = FALSE;
+ listener_T *next;
if (buf->b_recorded_changes == NULL // nothing changed
|| buf->b_listener == NULL // no listeners
@@ -400,6 +414,18 @@ invoke_listeners(buf_T *buf)
clear_tv(&rettv);
}
+ // If f_listener_remove() was called may have to remove a listener now.
+ for (lnr = buf->b_listener; lnr != NULL; lnr = next)
+ {
+ listener_T *prev = NULL;
+
+ next = lnr->lr_next;
+ if (lnr->lr_id == 0)
+ remove_listener(buf, lnr, prev);
+ else
+ prev = lnr;
+ }
+
--textwinlock;
list_unref(buf->b_recorded_changes);
buf->b_recorded_changes = NULL;
diff --git a/src/testdir/test_listener.vim b/src/testdir/test_listener.vim
index b23f2be90..343fb98e3 100644
--- a/src/testdir/test_listener.vim
+++ b/src/testdir/test_listener.vim
@@ -370,4 +370,22 @@ func Test_col_after_deletion_moved_cur()
delfunc Listener
endfunc
+func Test_remove_listener_in_callback()
+ new
+ let s:ID = listener_add('Listener')
+ func Listener(...)
+ call listener_remove(s:ID)
+ let g:listener_called = 'yes'
+ endfunc
+ call setline(1, ['foo'])
+ call feedkeys("lD", 'xt')
+ call listener_flush()
+ call assert_equal('yes', g:listener_called)
+
+ bwipe!
+ delfunc Listener
+ unlet g:listener_called
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 05331a686..c98249b94 100644
--- a/src/version.c
+++ b/src/version.c
@@ -756,6 +756,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 3426,
+/**/
3425,
/**/
3424,