# HG changeset patch # User Bram Moolenaar # Date 1560996007 -7200 # Node ID ebe9aab818985a8bb9c105929ded1d55733d6987 # Parent 96373bb9c0eb4d028f67a460f089fb60e0e240b1 patch 8.1.1575: callbacks may be garbage collected commit https://github.com/vim/vim/commit/75a1a9415b9c207de5a29b25c0d1949c6c9c5c61 Author: Bram Moolenaar 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) diff --git a/src/buffer.c b/src/buffer.c --- 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 diff --git a/src/channel.c b/src/channel.c --- 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; diff --git a/src/eval.c b/src/eval.c --- 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) { /* diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c --- 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) { diff --git a/src/popupwin.c b/src/popupwin.c --- 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 diff --git a/src/proto/buffer.pro b/src/proto/buffer.pro --- 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 : */ diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro --- 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 : */ diff --git a/src/terminal.c b/src/terminal.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; diff --git a/src/testdir/test_listener.vim b/src/testdir/test_listener.vim --- 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 diff --git a/src/testdir/test_popupwin.vim b/src/testdir/test_popupwin.vim --- 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 diff --git a/src/testdir/test_prompt_buffer.vim b/src/testdir/test_prompt_buffer.vim --- 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("\\", 'xt') + call assert_true(v:true) + + delfunc MyPromptCallback + bwipe! +endfunc diff --git a/src/userfunc.c b/src/userfunc.c --- 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; diff --git a/src/version.c b/src/version.c --- 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,