changeset 31081:c12069d28719 v9.0.0875

patch 9.0.0875: using freed memory when executing delfunc at more prompt Commit: https://github.com/vim/vim/commit/398a26f7fcd58fbc6e2329f892edbb7479a971bb Author: Bram Moolenaar <Bram@vim.org> Date: Sun Nov 13 22:13:33 2022 +0000 patch 9.0.0875: using freed memory when executing delfunc at more prompt Problem: Using freed memory when executing delfunc at the more prompt. Solution: Check function list not changed in another place. (closes https://github.com/vim/vim/issues/11437)
author Bram Moolenaar <Bram@vim.org>
date Sun, 13 Nov 2022 23:15:03 +0100
parents df91dcf490f2
children f4347129179d
files src/testdir/test_functions.vim src/userfunc.c src/version.c
diffstat 3 files changed, 89 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/src/testdir/test_functions.vim
+++ b/src/testdir/test_functions.vim
@@ -3026,4 +3026,31 @@ func Test_virtcol()
   bwipe!
 endfunc
 
+func Test_delfunc_while_listing()
+  CheckRunVimInTerminal
+
+  let lines =<< trim END
+      set nocompatible
+      for i in range(1, 999)
+        exe 'func ' .. 'MyFunc' .. i .. '()'
+        endfunc
+      endfor
+      au CmdlineLeave : call timer_start(0, {-> execute('delfunc MyFunc622')})
+  END
+  call writefile(lines, 'Xfunctionclear', 'D')
+  let buf = RunVimInTerminal('-S Xfunctionclear', {'rows': 12})
+
+  " This was using freed memory.  The height of the terminal must be so that
+  " the next function to be listed with "j" is the one that is deleted in the
+  " timer callback, tricky!
+  call term_sendkeys(buf, ":func /MyFunc\<CR>")
+  call TermWait(buf, 50)
+  call term_sendkeys(buf, "j")
+  call TermWait(buf, 50)
+  call term_sendkeys(buf, "\<CR>")
+
+  call StopVimInTerminal(buf)
+endfunc
+
+
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -3793,14 +3793,35 @@ printable_func_name(ufunc_T *fp)
 }
 
 /*
+ * When "prev_ht_changed" does not equal "ht_changed" give an error and return
+ * TRUE.  Otherwise return FALSE.
+ */
+    static int
+function_list_modified(int prev_ht_changed)
+{
+    if (prev_ht_changed != func_hashtab.ht_changed)
+    {
+	emsg(_(e_function_list_was_modified));
+	return TRUE;
+    }
+    return FALSE;
+}
+
+/*
  * List the head of the function: "function name(arg1, arg2)".
  */
-    static void
+    static int
 list_func_head(ufunc_T *fp, int indent)
 {
+    int		prev_ht_changed = func_hashtab.ht_changed;
     int		j;
 
     msg_start();
+
+    // a timer at the more prompt may have deleted the function
+    if (function_list_modified(prev_ht_changed))
+	return FAIL;
+
     if (indent)
 	msg_puts("   ");
     if (fp->uf_def_status != UF_NOT_COMPILED)
@@ -3877,6 +3898,8 @@ list_func_head(ufunc_T *fp, int indent)
     msg_clr_eos();
     if (p_verbose > 0)
 	last_set_msg(fp->uf_script_ctx);
+
+    return OK;
 }
 
 /*
@@ -4315,7 +4338,7 @@ save_function_name(
     void
 list_functions(regmatch_T *regmatch)
 {
-    int		changed = func_hashtab.ht_changed;
+    int		prev_ht_changed = func_hashtab.ht_changed;
     long_u	todo = func_hashtab.ht_used;
     hashitem_T	*hi;
 
@@ -4333,12 +4356,10 @@ list_functions(regmatch_T *regmatch)
 			: !isdigit(*fp->uf_name)
 			    && vim_regexec(regmatch, fp->uf_name, 0)))
 	    {
-		list_func_head(fp, FALSE);
-		if (changed != func_hashtab.ht_changed)
-		{
-		    emsg(_(e_function_list_was_modified));
+		if (list_func_head(fp, FALSE) == FAIL)
 		    return;
-		}
+		if (function_list_modified(prev_ht_changed))
+		    return;
 	    }
 	}
     }
@@ -4542,28 +4563,39 @@ define_function(exarg_T *eap, char_u *na
 
 	    if (fp != NULL)
 	    {
-		list_func_head(fp, TRUE);
-		for (j = 0; j < fp->uf_lines.ga_len && !got_int; ++j)
+		// Check no function was added or removed from a timer, e.g. at
+		// the more prompt.  "fp" may then be invalid.
+		int prev_ht_changed = func_hashtab.ht_changed;
+
+		if (list_func_head(fp, TRUE) == OK)
 		{
-		    if (FUNCLINE(fp, j) == NULL)
-			continue;
-		    msg_putchar('\n');
-		    msg_outnum((long)(j + 1));
-		    if (j < 9)
-			msg_putchar(' ');
-		    if (j < 99)
-			msg_putchar(' ');
-		    msg_prt_line(FUNCLINE(fp, j), FALSE);
-		    out_flush();	// show a line at a time
-		    ui_breakcheck();
-		}
-		if (!got_int)
-		{
-		    msg_putchar('\n');
-		    if (fp->uf_def_status != UF_NOT_COMPILED)
-			msg_puts("   enddef");
-		    else
-			msg_puts("   endfunction");
+		    for (j = 0; j < fp->uf_lines.ga_len && !got_int; ++j)
+		    {
+			if (FUNCLINE(fp, j) == NULL)
+			    continue;
+			msg_putchar('\n');
+			msg_outnum((long)(j + 1));
+			if (j < 9)
+			    msg_putchar(' ');
+			if (j < 99)
+			    msg_putchar(' ');
+			if (function_list_modified(prev_ht_changed))
+			    break;
+			msg_prt_line(FUNCLINE(fp, j), FALSE);
+			out_flush();	// show a line at a time
+			ui_breakcheck();
+		    }
+		    if (!got_int)
+		    {
+			msg_putchar('\n');
+			if (!function_list_modified(prev_ht_changed))
+			{
+			    if (fp->uf_def_status != UF_NOT_COMPILED)
+				msg_puts("   enddef");
+			    else
+				msg_puts("   endfunction");
+			}
+		    }
 		}
 	    }
 	    else
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    875,
+/**/
     874,
 /**/
     873,