diff options
author | Bram Moolenaar <Bram@vim.org> | 2021-09-11 15:06:44 +0200 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2021-09-11 15:06:44 +0200 |
commit | 4b4b1b84eee70b74fa3bb57624533c65bafd8428 (patch) | |
tree | f27ec20083b3a70710a250be2e16c8131d2a5a85 | |
parent | 56e14698b45a9c4ef8fa65c55c9bfe86fbb3d6bf (diff) | |
download | vim-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.c | 38 | ||||
-rw-r--r-- | src/testdir/test_listener.vim | 18 | ||||
-rw-r--r-- | src/version.c | 2 |
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, |