changeset 26452:65b4109a4297 v8.2.3756

patch 8.2.3756: might crash when callback is not valid Commit: https://github.com/vim/vim/commit/4dc24eb5adbcc76838fae1e900936dd230209d96 Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Tue Dec 7 12:23:57 2021 +0000 patch 8.2.3756: might crash when callback is not valid Problem: might crash when callback is not valid. Solution: Check for valid callback. (Yegappan Lakshmanan, closes https://github.com/vim/vim/issues/9293)
author Bram Moolenaar <Bram@vim.org>
date Tue, 07 Dec 2021 13:30:05 +0100
parents 27720705109b
children 71ff3e252eb6
files src/insexpand.c src/job.c src/option.c src/tag.c src/testdir/test_iminsert.vim src/testdir/test_ins_complete.vim src/testdir/test_tagfunc.vim src/userfunc.c src/version.c
diffstat 9 files changed, 59 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -2329,14 +2329,12 @@ set_thesaurusfunc_option(void)
     if (*curbuf->b_p_tsrfu != NUL)
     {
 	// buffer-local option set
-	free_callback(&curbuf->b_tsrfu_cb);
 	retval = option_set_callback_func(curbuf->b_p_tsrfu,
 							&curbuf->b_tsrfu_cb);
     }
     else
     {
 	// global option set
-	free_callback(&tsrfu_cb);
 	retval = option_set_callback_func(p_tsrfu, &tsrfu_cb);
     }
 
--- a/src/job.c
+++ b/src/job.c
@@ -1578,6 +1578,7 @@ invoke_prompt_interrupt(void)
 {
     typval_T	rettv;
     typval_T	argv[1];
+    int		ret;
 
     if (curbuf->b_prompt_interrupt.cb_name == NULL
 	    || *curbuf->b_prompt_interrupt.cb_name == NUL)
@@ -1585,9 +1586,9 @@ invoke_prompt_interrupt(void)
     argv[0].v_type = VAR_UNKNOWN;
 
     got_int = FALSE; // don't skip executing commands
-    call_callback(&curbuf->b_prompt_interrupt, -1, &rettv, 0, argv);
+    ret = call_callback(&curbuf->b_prompt_interrupt, -1, &rettv, 0, argv);
     clear_tv(&rettv);
-    return TRUE;
+    return ret == FAIL ? FALSE : TRUE;
 }
 
 /*
--- a/src/option.c
+++ b/src/option.c
@@ -7210,7 +7210,7 @@ option_set_callback_func(char_u *optval 
 	return FAIL;
 
     cb = get_callback(tv);
-    if (cb.cb_name == NULL)
+    if (cb.cb_name == NULL || *cb.cb_name == NUL)
     {
 	free_tv(tv);
 	return FAIL;
--- a/src/tag.c
+++ b/src/tag.c
@@ -1361,7 +1361,8 @@ find_tagfunc_tags(
     dict_T	*d;
     taggy_T	*tag = &curwin->w_tagstack[curwin->w_tagstackidx];
 
-    if (*curbuf->b_p_tfu == NUL)
+    if (*curbuf->b_p_tfu == NUL || curbuf->b_tfu_cb.cb_name == NULL
+					   || *curbuf->b_tfu_cb.cb_name == NUL)
 	return FAIL;
 
     args[0].v_type = VAR_STRING;
--- a/src/testdir/test_iminsert.vim
+++ b/src/testdir/test_iminsert.vim
@@ -257,6 +257,19 @@ func Test_imactivatefunc_imstatusfunc_ca
   set imstatusfunc=()\ =>\ IMstatusfunc1(a)
   call assert_fails('normal! i', 'E117:')
 
+  " set 'imactivatefunc' and 'imstatusfunc' to a non-existing function
+  set imactivatefunc=IMactivatefunc1
+  set imstatusfunc=IMstatusfunc1
+  call assert_fails("set imactivatefunc=function('NonExistingFunc')", 'E700:')
+  call assert_fails("set imstatusfunc=function('NonExistingFunc')", 'E700:')
+  call assert_fails("let &imactivatefunc = function('NonExistingFunc')", 'E700:')
+  call assert_fails("let &imstatusfunc = function('NonExistingFunc')", 'E700:')
+  let g:IMactivatefunc_called = 0
+  let g:IMstatusfunc_called = 0
+  normal! i
+  call assert_equal(2, g:IMactivatefunc_called)
+  call assert_equal(2, g:IMstatusfunc_called)
+
   " cleanup
   delfunc IMactivatefunc1
   delfunc IMstatusfunc1
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -1074,6 +1074,15 @@ func Test_completefunc_callback()
   call assert_fails('call feedkeys("A\<C-X>\<C-U>\<Esc>", "x")', 'E117:')
   call assert_equal([], g:MycompleteFunc2_args)
 
+  " set 'completefunc' to a non-existing function
+  set completefunc=MycompleteFunc2
+  call setline(1, 'five')
+  call assert_fails("set completefunc=function('NonExistingFunc')", 'E700:')
+  call assert_fails("let &completefunc = function('NonExistingFunc')", 'E700:')
+  let g:MycompleteFunc2_args = []
+  call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'five']], g:MycompleteFunc2_args)
+
   " cleanup
   delfunc MycompleteFunc1
   delfunc MycompleteFunc2
@@ -1285,6 +1294,15 @@ func Test_omnifunc_callback()
   call assert_fails('call feedkeys("A\<C-X>\<C-O>\<Esc>", "x")', 'E117:')
   call assert_equal([], g:MyomniFunc2_args)
 
+  " set 'omnifunc' to a non-existing function
+  set omnifunc=MyomniFunc2
+  call setline(1, 'nine')
+  call assert_fails("set omnifunc=function('NonExistingFunc')", 'E700:')
+  call assert_fails("let &omnifunc = function('NonExistingFunc')", 'E700:')
+  let g:MyomniFunc2_args = []
+  call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'nine']], g:MyomniFunc2_args)
+
   " cleanup
   delfunc MyomniFunc1
   delfunc MyomniFunc2
@@ -1522,6 +1540,16 @@ func Test_thesaurusfunc_callback()
   call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")
   call assert_equal('sunday', getline(1))
   call assert_equal([[1, ''], [0, 'sun']], g:MytsrFunc4_args)
+  %bw!
+
+  " set 'thesaurusfunc' to a non-existing function
+  set thesaurusfunc=MytsrFunc2
+  call setline(1, 'ten')
+  call assert_fails("set thesaurusfunc=function('NonExistingFunc')", 'E700:')
+  call assert_fails("let &thesaurusfunc = function('NonExistingFunc')", 'E700:')
+  let g:MytsrFunc2_args = []
+  call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x')
+  call assert_equal([[1, ''], [0, 'ten']], g:MytsrFunc2_args)
 
   " cleanup
   set thesaurusfunc&
--- a/src/testdir/test_tagfunc.vim
+++ b/src/testdir/test_tagfunc.vim
@@ -317,6 +317,11 @@ func Test_tagfunc_callback()
   call assert_fails("tag a17", "E117:")
   call assert_equal([], g:MytagFunc3_args)
 
+  " set 'tagfunc' to a non-existing function
+  call assert_fails("set tagfunc=function('NonExistingFunc')", 'E700:')
+  call assert_fails("let &tagfunc = function('NonExistingFunc')", 'E700:')
+  call assert_fails("tag axb123", 'E426:')
+
   " cleanup
   delfunc MytagFunc1
   delfunc MytagFunc2
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -3146,6 +3146,7 @@ get_callback_depth(void)
 
 /*
  * Invoke call_func() with a callback.
+ * Returns FAIL if the callback could not be called.
  */
     int
 call_callback(
@@ -3159,6 +3160,8 @@ call_callback(
     funcexe_T	funcexe;
     int		ret;
 
+    if (callback->cb_name == NULL || *callback->cb_name == NUL)
+	return FAIL;
     CLEAR_FIELD(funcexe);
     funcexe.evaluate = TRUE;
     funcexe.partial = callback->cb_partial;
@@ -3170,7 +3173,7 @@ call_callback(
 
 /*
  * call the 'callback' function and return the result as a number.
- * Returns -1 when calling the function fails.  Uses argv[0] to argv[argc - 1]
+ * Returns -2 when calling the function fails.  Uses argv[0] to argv[argc - 1]
  * for the function arguments. argv[argc] should have type VAR_UNKNOWN.
  */
     varnumber_T
@@ -3184,7 +3187,7 @@ call_callback_retnr(
     varnumber_T	retval;
 
     if (call_callback(callback, 0, &rettv, argcount, argvars) == FAIL)
-	return -1;
+	return -2;
 
     retval = tv_get_number_chk(&rettv, NULL);
     clear_tv(&rettv);
--- a/src/version.c
+++ b/src/version.c
@@ -754,6 +754,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3756,
+/**/
     3755,
 /**/
     3754,