changeset 31085:8c10a0b22015 v9.0.0877

patch 9.0.0877: using freed memory with :comclear while listing commands Commit: https://github.com/vim/vim/commit/cf2594fbf34d9a6776bd9d33f845cb8ceb1e1cd0 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Nov 13 23:30:06 2022 +0000 patch 9.0.0877: using freed memory with :comclear while listing commands Problem: Using freed memory with :comclear while listing commands. Solution: Bail out when the command list has changed. (closes https://github.com/vim/vim/issues/11440)
author Bram Moolenaar <Bram@vim.org>
date Mon, 14 Nov 2022 00:45:03 +0100
parents cb82e8d640a8
children 47d825028c38
files src/errors.h src/testdir/test_usercommands.vim src/usercmd.c src/version.c
diffstat 4 files changed, 72 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/src/errors.h
+++ b/src/errors.h
@@ -3339,3 +3339,5 @@ EXTERN char e_cannot_change_mappings_whi
 EXTERN char e_cannot_change_menus_while_listing[]
 	INIT(= N_("E1310: Cannot change menus while listing"));
 #endif
+EXTERN char e_cannot_change_user_commands_while_listing[]
+	INIT(= N_("E1311: Cannot change user commands while listing"));
--- a/src/testdir/test_usercommands.vim
+++ b/src/testdir/test_usercommands.vim
@@ -2,6 +2,9 @@
 
 import './vim9.vim' as v9
 
+source check.vim
+source screendump.vim
+
 " Test for <mods> in user defined commands
 function Test_cmdmods()
   let g:mods = ''
@@ -373,6 +376,14 @@ func Test_CmdCompletion()
   call feedkeys(":com MyCmd chist\<Tab>\<C-B>\"\<CR>", 'tx')
   call assert_equal("\"com MyCmd chistory", @:)
 
+  " delete the Check commands to avoid them showing up
+  call feedkeys(":com Check\<C-A>\<C-B>\"\<CR>", 'tx')
+  let cmds = substitute(@:, '"com ', '', '')->split()
+  for cmd in cmds
+    exe 'delcommand ' .. cmd
+  endfor
+  delcommand MissingFeature
+
   command! DoCmd1 :
   command! DoCmd2 :
   call feedkeys(":com \<C-A>\<C-B>\"\<CR>", 'tx')
@@ -716,6 +727,7 @@ func Test_usercmd_with_block()
          echo 'hello'
   END
   call v9.CheckScriptFailure(lines, 'E1026:')
+  delcommand DoesNotEnd
 
   let lines =<< trim END
       command HelloThere {
@@ -754,6 +766,7 @@ func Test_usercmd_with_block()
       BadCommand
   END
   call v9.CheckScriptFailure(lines, 'E1128:')
+  delcommand BadCommand
 endfunc
 
 func Test_delcommand_buffer()
@@ -817,7 +830,7 @@ func Test_recursive_define()
   call DefCmd('Command')
 
   let name = 'Command'
-  while len(name) < 30
+  while len(name) <= 30
     exe 'delcommand ' .. name
     let name ..= 'x'
   endwhile
@@ -882,5 +895,30 @@ func Test_block_declaration_legacy_scrip
   delcommand Rename
 endfunc
 
+func Test_comclear_while_listing()
+  call CheckRunVimInTerminal()
+
+  let lines =<< trim END
+      set nocompatible
+      comclear
+      for i in range(1, 999)
+        exe 'command ' .. 'Foo' .. i .. ' bar'
+      endfor
+      au CmdlineLeave : call timer_start(0, {-> execute('comclear')})
+  END
+  call writefile(lines, 'Xcommandclear', 'D')
+  let buf = RunVimInTerminal('-S Xcommandclear', {'rows': 10})
+
+  " this was using freed memory
+  call term_sendkeys(buf, ":command\<CR>")
+  call TermWait(buf, 50)
+  call term_sendkeys(buf, "j")
+  call TermWait(buf, 50)
+  call term_sendkeys(buf, "G")
+  call term_sendkeys(buf, "\<CR>")
+
+  call StopVimInTerminal(buf)
+endfunc
+
 
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/usercmd.c
+++ b/src/usercmd.c
@@ -31,6 +31,9 @@ typedef struct ucmd
 // List of all user commands.
 static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
 
+// When non-zero it is not allowed to add or remove user commands
+static int ucmd_locked = 0;
+
 #define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
 #define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
 
@@ -499,6 +502,9 @@ uc_list(char_u *name, size_t name_len)
     long	a;
     garray_T	*gap;
 
+    // don't allow for adding or removing user commands here
+    ++ucmd_locked;
+
     // In cmdwin, the alternative buffer should be used.
     gap = &prevwin_curwin()->w_buffer->b_ucmds;
     for (;;)
@@ -656,6 +662,8 @@ uc_list(char_u *name, size_t name_len)
 
     if (!found)
 	msg(_("No user-defined commands found"));
+
+    --ucmd_locked;
 }
 
     char *
@@ -1223,6 +1231,21 @@ ex_comclear(exarg_T *eap UNUSED)
 }
 
 /*
+ * If ucmd_locked is set give an error and return TRUE.
+ * Otherwise return FALSE.
+ */
+    static int
+is_ucmd_locked(void)
+{
+    if (ucmd_locked > 0)
+    {
+	emsg(_(e_cannot_change_user_commands_while_listing));
+	return TRUE;
+    }
+    return FALSE;
+}
+
+/*
  * Clear all user commands for "gap".
  */
     void
@@ -1231,6 +1254,9 @@ uc_clear(garray_T *gap)
     int		i;
     ucmd_T	*cmd;
 
+    if (is_ucmd_locked())
+	return;
+
     for (i = 0; i < gap->ga_len; ++i)
     {
 	cmd = USER_CMD_GA(gap, i);
@@ -1285,6 +1311,9 @@ ex_delcommand(exarg_T *eap)
 	return;
     }
 
+    if (is_ucmd_locked())
+	return;
+
     vim_free(cmd->uc_name);
     vim_free(cmd->uc_rep);
 # if defined(FEAT_EVAL)
--- 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 */
 /**/
+    877,
+/**/
     876,
 /**/
     875,