summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2022-11-13 23:30:06 +0000
committerBram Moolenaar <Bram@vim.org>2022-11-13 23:30:06 +0000
commitcf2594fbf34d9a6776bd9d33f845cb8ceb1e1cd0 (patch)
treeba645afa4188118a25f5d183a60740a710f96b1d
parent68353e5270fca45daffee5b5f882853cdfa40f76 (diff)
patch 9.0.0877: using freed memory with :comclear while listing commandsv9.0.0877
Problem: Using freed memory with :comclear while listing commands. Solution: Bail out when the command list has changed. (closes #11440)
-rw-r--r--src/errors.h2
-rw-r--r--src/testdir/test_usercommands.vim40
-rw-r--r--src/usercmd.c29
-rw-r--r--src/version.c2
4 files changed, 72 insertions, 1 deletions
diff --git a/src/errors.h b/src/errors.h
index 99247b6d19..0f54eba265 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -3339,3 +3339,5 @@ EXTERN char e_cannot_change_mappings_while_listing[]
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"));
diff --git a/src/testdir/test_usercommands.vim b/src/testdir/test_usercommands.vim
index f8cc1f53a1..57953ced8a 100644
--- 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_script()
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
diff --git a/src/usercmd.c b/src/usercmd.c
index 9f16680f58..d8783321d8 100644
--- 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)
diff --git a/src/version.c b/src/version.c
index 868118d0c7..a764712188 100644
--- 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,