# HG changeset patch # User Bram Moolenaar # Date 1668383103 -3600 # Node ID 8c10a0b22015d923b632f76a23563bd70de1bf10 # Parent cb82e8d640a8e98084a2d33893a16a9cc183043b patch 9.0.0877: using freed memory with :comclear while listing commands Commit: https://github.com/vim/vim/commit/cf2594fbf34d9a6776bd9d33f845cb8ceb1e1cd0 Author: Bram Moolenaar 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) diff --git a/src/errors.h b/src/errors.h --- 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")); diff --git a/src/testdir/test_usercommands.vim b/src/testdir/test_usercommands.vim --- 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 in user defined commands function Test_cmdmods() let g:mods = '' @@ -373,6 +376,14 @@ func Test_CmdCompletion() call feedkeys(":com MyCmd chist\\\"\", 'tx') call assert_equal("\"com MyCmd chistory", @:) + " delete the Check commands to avoid them showing up + call feedkeys(":com Check\\\"\", 'tx') + let cmds = substitute(@:, '"com ', '', '')->split() + for cmd in cmds + exe 'delcommand ' .. cmd + endfor + delcommand MissingFeature + command! DoCmd1 : command! DoCmd2 : call feedkeys(":com \\\"\", '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\") + call TermWait(buf, 50) + call term_sendkeys(buf, "j") + call TermWait(buf, 50) + call term_sendkeys(buf, "G") + call term_sendkeys(buf, "\") + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/usercmd.c b/src/usercmd.c --- 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 --- 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,