Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Crash by GC during specific processing #4579

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/dict.c
Expand Up @@ -811,7 +811,7 @@ dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
{
semsg(_("E723: Missing end of Dictionary '}': %s"), *arg);
failret:
if (d != NULL)
if (evaluate)
dict_free(d);
return FAIL;
}
Expand Down
9 changes: 7 additions & 2 deletions src/ex_cmds2.c
Expand Up @@ -367,9 +367,10 @@ check_due_timer(void)
int save_vgetc_busy = vgetc_busy;
int save_did_emsg = did_emsg;
int save_called_emsg = called_emsg;
int save_must_redraw = must_redraw;
int save_trylevel = trylevel;
int save_must_redraw = must_redraw;
int save_trylevel = trylevel;
int save_did_throw = did_throw;
int save_may_garbage_collect = may_garbage_collect;
int save_ex_pressedreturn = get_pressedreturn();
except_T *save_current_exception = current_exception;
vimvars_save_T vvsave;
Expand All @@ -384,14 +385,17 @@ check_due_timer(void)
must_redraw = 0;
trylevel = 0;
did_throw = FALSE;
may_garbage_collect = FALSE;
current_exception = NULL;
save_vimvars(&vvsave);

timer->tr_firing = TRUE;
timer_callback(timer);
timer->tr_firing = FALSE;

timer_next = timer->tr_next;
did_one = TRUE;

timer_busy = save_timer_busy;
vgetc_busy = save_vgetc_busy;
if (did_uncaught_emsg)
Expand All @@ -400,6 +404,7 @@ check_due_timer(void)
called_emsg = save_called_emsg;
trylevel = save_trylevel;
did_throw = save_did_throw;
may_garbage_collect = save_may_garbage_collect;
current_exception = save_current_exception;
restore_vimvars(&vvsave);
if (must_redraw != 0)
Expand Down
7 changes: 6 additions & 1 deletion src/getchar.c
Expand Up @@ -2540,13 +2540,18 @@ vgetorpeek(int advance)
*/
if (mp->m_expr)
{
int save_vgetc_busy = vgetc_busy;
int save_vgetc_busy = vgetc_busy;
int save_may_garbage_collect = may_garbage_collect;

vgetc_busy = 0;
may_garbage_collect = FALSE;

save_m_keys = vim_strsave(mp->m_keys);
save_m_str = vim_strsave(mp->m_str);
s = eval_map_expr(save_m_str, NUL);

vgetc_busy = save_vgetc_busy;
may_garbage_collect = save_may_garbage_collect;
}
else
#endif
Expand Down
36 changes: 36 additions & 0 deletions src/testdir/test_mapping.vim
Expand Up @@ -397,3 +397,39 @@ func Test_motionforce_omap()
delfunc Select
delfunc GetCommand
endfunc

func Test_error_in_map_expr()
if !has('terminal') || (has('win32') && has('gui_running'))
throw 'Skipped: cannot run Vim in a terminal window'
endif

let lines =<< trim [CODE]
func Func()
" fail to create list
let x = [
endfunc
nmap <expr> ! Func()
set updatetime=50
[CODE]
call writefile(lines, 'Xtest.vim')

let buf = term_start(GetVimCommandClean() .. ' -S Xtest.vim', {'term_rows': 8})
let job = term_getjob(buf)
call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))})

" GC must not run during map-expr processing, which can make Vim crash.
call term_sendkeys(buf, '!')
call term_wait(buf, 100)
call term_sendkeys(buf, "\<CR>")
call term_wait(buf, 100)
call assert_equal('run', job_status(job))

call term_sendkeys(buf, ":qall!\<CR>")
call WaitFor({-> job_status(job) ==# 'dead'})
if has('unix')
call assert_equal('', job_info(job).termsig)
endif

call delete('Xtest.vim')
exe buf .. 'bwipe!'
endfunc
35 changes: 35 additions & 0 deletions src/testdir/test_timers.vim
Expand Up @@ -333,4 +333,39 @@ func Test_nocatch_garbage_collect()
delfunc FeedChar
endfunc

func Test_error_in_timer_callback()
if !has('terminal') || (has('win32') && has('gui_running'))
throw 'Skipped: cannot run Vim in a terminal window'
endif

let lines =<< trim [CODE]
func Func(timer)
" fail to create list
let x = [
endfunc
set updatetime=50
call timer_start(1, 'Func')
[CODE]
call writefile(lines, 'Xtest.vim')

let buf = term_start(GetVimCommandClean() .. ' -S Xtest.vim', {'term_rows': 8})
let job = term_getjob(buf)
call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))})

" GC must not run during timer callback, which can make Vim crash.
call term_wait(buf, 100)
call term_sendkeys(buf, "\<CR>")
call term_wait(buf, 100)
call assert_equal('run', job_status(job))

call term_sendkeys(buf, ":qall!\<CR>")
call WaitFor({-> job_status(job) ==# 'dead'})
if has('unix')
call assert_equal('', job_info(job).termsig)
endif

call delete('Xtest.vim')
exe buf .. 'bwipe!'
endfunc

" vim: shiftwidth=2 sts=2 expandtab
2 changes: 1 addition & 1 deletion src/testdir/test_vimscript.vim
Expand Up @@ -1665,7 +1665,7 @@ func Test_refcount()
delfunc DictFunc
endfunc

func! Test_funccall_garbage_collect()
func Test_funccall_garbage_collect()
func Func(x, ...)
call add(a:x, a:000)
endfunc
Expand Down