neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit 6fd2a3040f7d6b522d403b66ad34e0710235052a
parent 2e2ac49c57e5c7b35a78ad57a614615e01157623
Author: zeertzjq <zeertzjq@outlook.com>
Date:   Mon,  7 Jul 2025 06:46:05 +0800

vim-patch:9.1.1518: getcompletiontype() may crash (#34819)

Problem:  getcompletiontype() crashes when no completion is available
          (after v9.1.1509).
Solution: Don't call set_expand_context() (zeertzjq)

fixes: vim/vim#17681
closes: vim/vim#17684

https://github.com/vim/vim/commit/e2c0f81dd014b03a40af6b2c42463b7a0d92f5d6
Diffstat:
Mruntime/doc/vimfn.txt | 4++--
Mruntime/lua/vim/_meta/vimfn.lua | 4++--
Msrc/nvim/cmdexpand.c | 5+----
Msrc/nvim/eval.lua | 4++--
Msrc/nvim/ex_getln.c | 38++++++++++++++------------------------
Msrc/nvim/usercmd.c | 22+++++++++++++++++++++-
Mtest/old/testdir/test_cmdline.vim | 15+++++++++------
7 files changed, 51 insertions(+), 41 deletions(-)

diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt @@ -3351,8 +3351,8 @@ getcmdcompltype() *getcmdcompltype()* |getcmdprompt()|, |getcmdcomplpat()| and |setcmdline()|. Returns an empty string when completion is not defined. - To get the type of the command-line completion for the - specified string, use |getcompletiontype()|. + To get the type of the command-line completion for a specified + string, use |getcompletiontype()|. Return: ~ (`string`) diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua @@ -3002,8 +3002,8 @@ function vim.fn.getcmdcomplpat() end --- |getcmdprompt()|, |getcmdcomplpat()| and |setcmdline()|. --- Returns an empty string when completion is not defined. --- ---- To get the type of the command-line completion for the ---- specified string, use |getcompletiontype()|. +--- To get the type of the command-line completion for a specified +--- string, use |getcompletiontype()|. --- --- @return string function vim.fn.getcmdcompltype() end diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c @@ -3854,10 +3854,7 @@ void f_getcompletiontype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) int cmdline_len = (int)strlen(pat); set_cmd_context(&xpc, (char *)pat, cmdline_len, cmdline_len, false); - xpc.xp_pattern_len = strlen(xpc.xp_pattern); - xpc.xp_col = cmdline_len; - - rettv->vval.v_string = get_cmdline_completion(&xpc); + rettv->vval.v_string = cmdcomplete_type_to_str(xpc.xp_context, xpc.xp_arg); ExpandCleanup(&xpc); } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua @@ -3760,8 +3760,8 @@ M.funcs = { |getcmdprompt()|, |getcmdcomplpat()| and |setcmdline()|. Returns an empty string when completion is not defined. - To get the type of the command-line completion for the - specified string, use |getcompletiontype()|. + To get the type of the command-line completion for a specified + string, use |getcompletiontype()|. ]=], name = 'getcmdcompltype', params = {}, diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c @@ -4198,31 +4198,28 @@ static char *get_cmdline_completion_pattern(void) } /// Get the command-line completion type. -char *get_cmdline_completion(expand_T *xpc) +static char *get_cmdline_completion(void) { - int xp_context = xpc->xp_context; - if (xp_context == EXPAND_NOTHING) { - set_expand_context(xpc); - xp_context = xpc->xp_context; - xpc->xp_context = EXPAND_NOTHING; - } - if (xp_context == EXPAND_UNSUCCESSFUL) { + if (cmdline_star > 0) { return NULL; } - char *cmd_compl = get_user_cmd_complete(NULL, xp_context); - if (cmd_compl == NULL) { + CmdlineInfo *p = get_ccline_ptr(); + if (p == NULL || p->xpc == NULL) { return NULL; } - if (xp_context == EXPAND_USER_LIST || xp_context == EXPAND_USER_DEFINED) { - size_t buflen = strlen(cmd_compl) + strlen(xpc->xp_arg) + 2; - char *buffer = xmalloc(buflen); - snprintf(buffer, buflen, "%s,%s", cmd_compl, xpc->xp_arg); - return buffer; + int xp_context = p->xpc->xp_context; + if (xp_context == EXPAND_NOTHING) { + set_expand_context(p->xpc); + xp_context = p->xpc->xp_context; + p->xpc->xp_context = EXPAND_NOTHING; + } + if (xp_context == EXPAND_UNSUCCESSFUL) { + return NULL; } - return xstrdup(cmd_compl); + return cmdcomplete_type_to_str(xp_context, p->xpc->xp_arg); } /// "getcmdcomplpat()" function @@ -4236,14 +4233,7 @@ void f_getcmdcomplpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) void f_getcmdcompltype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - CmdlineInfo *p = get_ccline_ptr(); - if (cmdline_star > 0 || p == NULL || p->xpc == NULL) { - return; - } - - rettv->vval.v_string = get_cmdline_completion(p->xpc); + rettv->vval.v_string = get_cmdline_completion(); } /// "getcmdline()" function diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c @@ -403,7 +403,7 @@ char *get_user_cmd_nargs(expand_T *xp, int idx) static char *get_command_complete(int arg) { - if (arg >= (int)(ARRAY_SIZE(command_complete))) { + if (arg < 0 || arg >= (int)(ARRAY_SIZE(command_complete))) { return NULL; } return (char *)command_complete[arg]; @@ -422,6 +422,26 @@ char *get_user_cmd_complete(expand_T *xp, int idx) return cmd_compl; } +/// Get the name of completion type "expand" as an allocated string. +/// "compl_arg" is the function name for "custom" and "customlist" types. +/// Returns NULL if no completion is available. +char *cmdcomplete_type_to_str(int expand, const char *compl_arg) +{ + char *cmd_compl = get_command_complete(expand); + if (cmd_compl == NULL || expand == EXPAND_USER_LUA) { + return NULL; + } + + if (expand == EXPAND_USER_LIST || expand == EXPAND_USER_DEFINED) { + size_t buflen = strlen(cmd_compl) + strlen(compl_arg) + 2; + char *buffer = xmalloc(buflen); + snprintf(buffer, buflen, "%s,%s", cmd_compl, compl_arg); + return buffer; + } + + return xstrdup(cmd_compl); +} + int cmdcomplete_str_to_type(const char *complete_str) { if (strncmp(complete_str, "custom,", 7) == 0) { diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim @@ -740,12 +740,13 @@ endfunc func Test_getcompletiontype() call assert_fails('call getcompletiontype()', 'E119:') call assert_fails('call getcompletiontype({})', 'E1174:') - call assert_equal(getcompletiontype(''), 'command') - call assert_equal(getcompletiontype('dummy '), '') - call assert_equal(getcompletiontype('cd '), 'dir_in_path') - call assert_equal(getcompletiontype('let v:n'), 'var') - call assert_equal(getcompletiontype('call tag'), 'function') - call assert_equal(getcompletiontype('help '), 'help') + call assert_equal('command', getcompletiontype('')) + call assert_equal('', getcompletiontype('dummy ')) + call assert_equal('', getcompletiontype('ls ')) + call assert_equal('dir_in_path', getcompletiontype('cd ')) + call assert_equal('var', getcompletiontype('let v:n')) + call assert_equal('function', getcompletiontype('call tag')) + call assert_equal('help', getcompletiontype('help ')) endfunc func Test_multibyte_expression() @@ -4251,6 +4252,8 @@ func Test_custom_completion() call feedkeys(":Test1 \<C-R>=Check_custom_completion()\<CR>\<Esc>", "xt") call feedkeys(":Test2 \<C-R>=Check_customlist_completion()\<CR>\<Esc>", "xt") + call assert_equal('custom,CustomComplete1', getcompletiontype('Test1 ')) + call assert_equal('customlist,CustomComplete2', getcompletiontype('Test2 ')) call assert_fails("call getcompletion('', 'custom')", 'E475:') call assert_fails("call getcompletion('', 'customlist')", 'E475:')