changeset 17151:ebe9aab81898 v8.1.1575

patch 8.1.1575: callbacks may be garbage collected commit https://github.com/vim/vim/commit/75a1a9415b9c207de5a29b25c0d1949c6c9c5c61 Author: Bram Moolenaar <Bram@vim.org> Date: Thu Jun 20 03:45:36 2019 +0200 patch 8.1.1575: callbacks may be garbage collected Problem: Callbacks may be garbage collected. Solution: Set reference in callbacks. (Ozaki Kiichi, closes https://github.com/vim/vim/issues/4564)
author Bram Moolenaar <Bram@vim.org>
date Thu, 20 Jun 2019 04:00:07 +0200
parents 96373bb9c0eb
children 61b305eddc3c
files src/buffer.c src/channel.c src/eval.c src/ex_cmds2.c src/popupwin.c src/proto/buffer.pro src/proto/popupwin.pro src/terminal.c src/testdir/test_listener.vim src/testdir/test_popupwin.vim src/testdir/test_prompt_buffer.vim src/userfunc.c src/version.c
diffstat 13 files changed, 164 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -5962,3 +5962,48 @@ wipe_buffer(
     if (!aucmd)
 	unblock_autocmds();
 }
+
+#if defined(FEAT_EVAL) || defined(PROTO)
+/*
+ * Mark references in functions of buffers.
+ */
+    int
+set_ref_in_buffers(int copyID)
+{
+    int		abort = FALSE;
+    buf_T	*bp;
+
+    FOR_ALL_BUFFERS(bp)
+    {
+	listener_T *lnr;
+	typval_T tv;
+
+	for (lnr = bp->b_listener; !abort && lnr != NULL; lnr = lnr->lr_next)
+	{
+	    if (lnr->lr_callback.cb_partial != NULL)
+	    {
+		tv.v_type = VAR_PARTIAL;
+		tv.vval.v_partial = lnr->lr_callback.cb_partial;
+		abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
+	    }
+	}
+# ifdef FEAT_JOB_CHANNEL
+	if (!abort && bp->b_prompt_callback.cb_partial != NULL)
+	{
+	    tv.v_type = VAR_PARTIAL;
+	    tv.vval.v_partial = bp->b_prompt_callback.cb_partial;
+	    abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
+	}
+	if (!abort && bp->b_prompt_interrupt.cb_partial != NULL)
+	{
+	    tv.v_type = VAR_PARTIAL;
+	    tv.vval.v_partial = bp->b_prompt_interrupt.cb_partial;
+	    abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
+	}
+# endif
+	if (abort)
+	    break;
+    }
+    return abort;
+}
+#endif
--- a/src/channel.c
+++ b/src/channel.c
@@ -4479,7 +4479,8 @@ set_ref_in_channel(int copyID)
     channel_T	*channel;
     typval_T	tv;
 
-    for (channel = first_channel; channel != NULL; channel = channel->ch_next)
+    for (channel = first_channel; !abort && channel != NULL;
+						   channel = channel->ch_next)
 	if (channel_still_useful(channel))
 	{
 	    tv.v_type = VAR_CHANNEL;
@@ -5568,7 +5569,7 @@ set_ref_in_job(int copyID)
     job_T	*job;
     typval_T	tv;
 
-    for (job = first_job; job != NULL; job = job->jv_next)
+    for (job = first_job; !abort && job != NULL; job = job->jv_next)
 	if (job_still_useful(job))
 	{
 	    tv.v_type = VAR_JOB;
--- a/src/eval.c
+++ b/src/eval.c
@@ -5678,6 +5678,9 @@ garbage_collect(int testing)
     /* v: vars */
     abort = abort || set_ref_in_ht(&vimvarht, copyID, NULL);
 
+    // callbacks in buffers
+    abort = abort || set_ref_in_buffers(copyID);
+
 #ifdef FEAT_LUA
     abort = abort || set_ref_in_lua(copyID);
 #endif
@@ -5710,6 +5713,10 @@ garbage_collect(int testing)
     abort = abort || set_ref_in_term(copyID);
 #endif
 
+#ifdef FEAT_TEXT_PROP
+    abort = abort || set_ref_in_popups(copyID);
+#endif
+
     if (!abort)
     {
 	/*
--- a/src/ex_cmds2.c
+++ b/src/ex_cmds2.c
@@ -566,7 +566,7 @@ set_ref_in_timer(int copyID)
     timer_T	*timer;
     typval_T	tv;
 
-    for (timer = first_timer; timer != NULL; timer = timer->tr_next)
+    for (timer = first_timer; !abort && timer != NULL; timer = timer->tr_next)
     {
 	if (timer->tr_callback.cb_partial != NULL)
 	{
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -2140,4 +2140,50 @@ update_popups(void (*win_update)(win_T *
     }
 }
 
+/*
+ * Mark references in callbacks of one popup window.
+ */
+    static int
+set_ref_in_one_popup(win_T *wp, int copyID)
+{
+    int		abort = FALSE;
+    typval_T	tv;
+
+    if (wp->w_close_cb.cb_partial != NULL)
+    {
+	tv.v_type = VAR_PARTIAL;
+	tv.vval.v_partial = wp->w_close_cb.cb_partial;
+	abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
+    }
+    if (wp->w_filter_cb.cb_partial != NULL)
+    {
+	tv.v_type = VAR_PARTIAL;
+	tv.vval.v_partial = wp->w_filter_cb.cb_partial;
+	abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
+    }
+    return abort;
+}
+
+/*
+ * Set reference in callbacks of popup windows.
+ */
+    int
+set_ref_in_popups(int copyID)
+{
+    int		abort = FALSE;
+    win_T	*wp;
+    tabpage_T	*tp;
+
+    for (wp = first_popupwin; !abort && wp != NULL; wp = wp->w_next)
+	abort = abort || set_ref_in_one_popup(wp, copyID);
+
+    FOR_ALL_TABPAGES(tp)
+    {
+	for (wp = tp->tp_first_popupwin; !abort && wp != NULL; wp = wp->w_next)
+	    abort = abort || set_ref_in_one_popup(wp, copyID);
+	if (abort)
+	    break;
+    }
+    return abort;
+}
 #endif // FEAT_TEXT_PROP
--- a/src/proto/buffer.pro
+++ b/src/proto/buffer.pro
@@ -74,4 +74,5 @@ int find_win_for_buf(buf_T *buf, win_T *
 void set_buflisted(int on);
 int buf_contents_changed(buf_T *buf);
 void wipe_buffer(buf_T *buf, int aucmd);
+int set_ref_in_buffers(int copyID);
 /* vim: set ft=c : */
--- a/src/proto/popupwin.pro
+++ b/src/proto/popupwin.pro
@@ -31,4 +31,5 @@ int popup_do_filter(int c);
 void popup_check_cursor_pos(void);
 void may_update_popup_mask(int type);
 void update_popups(void (*win_update)(win_T *wp));
+int set_ref_in_popups(int copyID);
 /* vim: set ft=c : */
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -4051,7 +4051,7 @@ set_ref_in_term(int copyID)
     term_T	*term;
     typval_T	tv;
 
-    for (term = first_term; term != NULL; term = term->tl_next)
+    for (term = first_term; !abort && term != NULL; term = term->tl_next)
 	if (term->tl_job != NULL)
 	{
 	    tv.v_type = VAR_JOB;
--- a/src/testdir/test_listener.vim
+++ b/src/testdir/test_listener.vim
@@ -225,3 +225,20 @@ func Test_listening_other_buf()
   exe "buf " .. bufnr
   bwipe!
 endfunc
+
+func Test_listener_garbage_collect()
+  func MyListener(x, bufnr, start, end, added, changes)
+    " NOP
+  endfunc
+
+  new
+  let id = listener_add(function('MyListener', [{}]), bufnr(''))
+  call test_garbagecollect_now()
+  " must not crach caused by invalid memory access
+  normal ia
+  call assert_true(v:true)
+
+  call listener_remove(id)
+  delfunc MyListener
+  bwipe!
+endfunc
--- a/src/testdir/test_popupwin.vim
+++ b/src/testdir/test_popupwin.vim
@@ -1467,3 +1467,19 @@ func Test_set_get_options()
 
   call popup_close(winid)
 endfunc
+
+func Test_popupwin_garbage_collect()
+  func MyPopupFilter(x, winid, c)
+    " NOP
+  endfunc
+
+  let winid = popup_create('something', {'filter': function('MyPopupFilter', [{}])})
+  call test_garbagecollect_now()
+  redraw
+  " Must not crach caused by invalid memory access
+  call feedkeys('j', 'xt')
+  call assert_true(v:true)
+
+  call popup_close(winid)
+  delfunc MyPopupFilter
+endfunc
--- a/src/testdir/test_prompt_buffer.vim
+++ b/src/testdir/test_prompt_buffer.vim
@@ -102,3 +102,24 @@ func Test_prompt_editing()
   call StopVimInTerminal(buf)
   call delete(scriptName)
 endfunc
+
+func Test_prompt_garbage_collect()
+  func MyPromptCallback(x, text)
+    " NOP
+  endfunc
+  func MyPromptInterrupt(x)
+    " NOP
+  endfunc
+
+  new
+  set buftype=prompt
+  call prompt_setcallback(bufnr(''), function('MyPromptCallback', [{}]))
+  call prompt_setinterrupt(bufnr(''), function('MyPromptInterrupt', [{}]))
+  call test_garbagecollect_now()
+  " Must not crash
+  call feedkeys("\<CR>\<C-C>", 'xt')
+  call assert_true(v:true)
+
+  delfunc MyPromptCallback
+  bwipe!
+endfunc
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -4032,12 +4032,12 @@ set_ref_in_call_stack(int copyID)
     funccall_T		*fc;
     funccal_entry_T	*entry;
 
-    for (fc = current_funccal; fc != NULL; fc = fc->caller)
+    for (fc = current_funccal; !abort && fc != NULL; fc = fc->caller)
 	abort = abort || set_ref_in_funccal(fc, copyID);
 
     // Also go through the funccal_stack.
-    for (entry = funccal_stack; entry != NULL; entry = entry->next)
-	for (fc = entry->top_funccal; fc != NULL; fc = fc->caller)
+    for (entry = funccal_stack; !abort && entry != NULL; entry = entry->next)
+	for (fc = entry->top_funccal; !abort && fc != NULL; fc = fc->caller)
 	    abort = abort || set_ref_in_funccal(fc, copyID);
 
     return abort;
--- a/src/version.c
+++ b/src/version.c
@@ -778,6 +778,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1575,
+/**/
     1574,
 /**/
     1573,