changeset 20709:a2262c80a4e0 v8.2.0908

patch 8.2.0908: crash when changing the function table while listing it Commit: https://github.com/vim/vim/commit/3fffa97159a427067b60c80ed4645e168cc5c4bd Author: Bram Moolenaar <Bram@vim.org> Date: Fri Jun 5 21:06:10 2020 +0200 patch 8.2.0908: crash when changing the function table while listing it Problem: Crash when changing the function table while listing it. Solution: Bail out when the function table changes. (closes https://github.com/vim/vim/issues/6209)
author Bram Moolenaar <Bram@vim.org>
date Fri, 05 Jun 2020 21:15:04 +0200
parents 66b20c503d2c
children 24fa685e72e9
files src/testdir/test_timers.vim src/userfunc.c src/version.c
diffstat 3 files changed, 67 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/src/testdir/test_timers.vim
+++ b/src/testdir/test_timers.vim
@@ -3,6 +3,7 @@
 source check.vim
 CheckFeature timers
 
+source screendump.vim
 source shared.vim
 source term_util.vim
 
@@ -424,4 +425,28 @@ func Test_timer_invalid_callback()
   call assert_fails('call timer_start(0, "0")', 'E921')
 endfunc
 
+func Test_timer_changing_function_list()
+  CheckRunVimInTerminal
+
+  " Create a large number of functions.  Should get the "more" prompt.
+  " The typing "G" triggers the timer, which changes the function table.
+  let lines =<< trim END
+    for func in map(range(1,99), "'Func' .. v:val")
+      exe "func " .. func .. "()"
+      endfunc
+    endfor
+    au CmdlineLeave : call timer_start(0, {-> 0})
+  END
+  call writefile(lines, 'XTest_timerchange')
+  let buf = RunVimInTerminal('-S XTest_timerchange', #{rows: 10})
+  call term_sendkeys(buf, ":fu\<CR>")
+  call WaitForAssert({-> assert_match('-- More --', term_getline(buf, 10))})
+  call term_sendkeys(buf, "G")
+  call WaitForAssert({-> assert_match('E454', term_getline(buf, 9))})
+  call term_sendkeys(buf, "\<Esc>")
+
+  call StopVimInTerminal(buf)
+  call delete('XTest_timerchange')
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -2373,6 +2373,44 @@ untrans_function_name(char_u *name)
 }
 
 /*
+ * List functions.  When "regmatch" is NULL all of then.
+ * Otherwise functions matching "regmatch".
+ */
+    static void
+list_functions(regmatch_T *regmatch)
+{
+    long_u	used = func_hashtab.ht_used;
+    long_u	todo = used;
+    hashitem_T	*ht_array = func_hashtab.ht_array;
+    hashitem_T	*hi;
+
+    for (hi = ht_array; todo > 0 && !got_int; ++hi)
+    {
+	if (!HASHITEM_EMPTY(hi))
+	{
+	    ufunc_T	*fp = HI2UF(hi);
+
+	    --todo;
+	    if ((fp->uf_flags & FC_DEAD) == 0
+		    && (regmatch == NULL
+			? !message_filtered(fp->uf_name)
+			    && !func_name_refcount(fp->uf_name)
+			: !isdigit(*fp->uf_name)
+			    && vim_regexec(regmatch, fp->uf_name, 0)))
+	    {
+		list_func_head(fp, FALSE);
+		if (used != func_hashtab.ht_used
+			|| ht_array != func_hashtab.ht_array)
+		{
+		    emsg(_("E454: function list was modified"));
+		    return;
+		}
+	    }
+	}
+    }
+}
+
+/*
  * ":function" also supporting nested ":def".
  * Returns a pointer to the function or NULL if no function defined.
  */
@@ -2407,7 +2445,6 @@ def_function(exarg_T *eap, char_u *name_
     funcdict_T	fudi;
     static int	func_nr = 0;	    // number for nameless function
     int		paren;
-    int		todo;
     hashitem_T	*hi;
     int		do_concat = TRUE;
     linenr_T	sourcing_lnum_off;
@@ -2428,22 +2465,7 @@ def_function(exarg_T *eap, char_u *name_
     if (ends_excmd2(eap->cmd, eap->arg))
     {
 	if (!eap->skip)
-	{
-	    todo = (int)func_hashtab.ht_used;
-	    for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi)
-	    {
-		if (!HASHITEM_EMPTY(hi))
-		{
-		    --todo;
-		    fp = HI2UF(hi);
-		    if ((fp->uf_flags & FC_DEAD)
-					      || message_filtered(fp->uf_name))
-			continue;
-		    if (!func_name_refcount(fp->uf_name))
-			list_func_head(fp, FALSE);
-		}
-	    }
-	}
+	    list_functions(NULL);
 	eap->nextcmd = check_nextcmd(eap->arg);
 	return NULL;
     }
@@ -2465,20 +2487,7 @@ def_function(exarg_T *eap, char_u *name_
 	    if (regmatch.regprog != NULL)
 	    {
 		regmatch.rm_ic = p_ic;
-
-		todo = (int)func_hashtab.ht_used;
-		for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi)
-		{
-		    if (!HASHITEM_EMPTY(hi))
-		    {
-			--todo;
-			fp = HI2UF(hi);
-			if ((fp->uf_flags & FC_DEAD) == 0
-				&& !isdigit(*fp->uf_name)
-				&& vim_regexec(&regmatch, fp->uf_name, 0))
-			    list_func_head(fp, FALSE);
-		    }
-		}
+		list_functions(&regmatch);
 		vim_regfree(regmatch.regprog);
 	    }
 	}
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    908,
+/**/
     907,
 /**/
     906,