]> granicus.if.org Git - vim/commitdiff
patch 8.2.3426: crash when deleting a listener in a listener callback v8.2.3426
authorBram Moolenaar <Bram@vim.org>
Sat, 11 Sep 2021 13:06:44 +0000 (15:06 +0200)
committerBram Moolenaar <Bram@vim.org>
Sat, 11 Sep 2021 13:06:44 +0000 (15:06 +0200)
Problem:    Crash when deleting a listener in a listener callback. (Naohiro
            Ono)
Solution:   Mark the listener and delete it later.

src/change.c
src/testdir/test_listener.vim
src/version.c

index 799aa5efbc261189bb4f3b71aa20cc8ae9fbd7c7..e171956207cd478ff771abd25824e2124b44da9e 100644 (file)
@@ -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;
index b23f2be90091cd2ff1f9277db124eceefa79c95e..343fb98e3cc0fd83a7e75285058ef93bd1c633b6 100644 (file)
@@ -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
index 05331a68691c989b8522a6abcdb7580905c53af4..c98249b94f764929e577733c329f584ade71ca0c 100644 (file)
@@ -755,6 +755,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3426,
 /**/
     3425,
 /**/