neovim

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

commit c7c3f9fc9c3a97cc8f48ea5cfffa1d0fc79abb5d
parent 3eab5bd38a5680066166a4e94c788ef345081dc7
Author: glepnir <glephunter@gmail.com>
Date:   Wed, 13 Aug 2025 04:43:56 +0800

fix(api): fix crash in command preview with % #35228

Problem: parse_cmdline() sets eap->cmdlinep to address of local parameter,
causing invalid memory access when expand_filename() tries to modify it.
This leads to crashes when typing '%' in user commands with preview=true
and complete=file.

Solution: Change parse_cmdline() signature to accept char **cmdline,
allowing cmdlinep to point to caller's variable for safe reallocation.
Diffstat:
Msrc/nvim/api/command.c | 2+-
Msrc/nvim/ex_docmd.c | 8++++----
Msrc/nvim/ex_getln.c | 2+-
Mtest/functional/ui/inccommand_user_spec.lua | 19+++++++++++++++++++
4 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c @@ -134,7 +134,7 @@ Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err char *cmdline = arena_memdupz(arena, str.data, str.size); const char *errormsg = NULL; - if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) { + if (!parse_cmdline(&cmdline, &ea, &cmdinfo, &errormsg)) { if (errormsg != NULL) { api_set_error(err, kErrorTypeException, "Parsing command-line: %s", errormsg); } else { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c @@ -1489,7 +1489,7 @@ bool is_cmd_ni(cmdidx_T cmdidx) /// @param[out] errormsg Error message, if any /// /// @return Success or failure -bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, const char **errormsg) +bool parse_cmdline(char **cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, const char **errormsg) { char *after_modifier = NULL; bool retval = false; @@ -1507,8 +1507,8 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, const cha *eap = (exarg_T){ .line1 = 1, .line2 = 1, - .cmd = cmdline, - .cmdlinep = &cmdline, + .cmd = *cmdline, + .cmdlinep = cmdline, .ea_getline = NULL, .cookie = NULL, }; @@ -1549,7 +1549,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, const cha if (eap->cmdidx == CMD_SIZE) { xstrlcpy(IObuff, _(e_not_an_editor_command), IOSIZE); // If the modifier was parsed OK the error must be in the following command - char *cmdname = after_modifier ? after_modifier : cmdline; + char *cmdname = after_modifier ? after_modifier : *cmdline; append_command(cmdname); *errormsg = IObuff; goto end; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c @@ -2678,7 +2678,7 @@ static bool cmdpreview_may_show(CommandLineState *s) char *cmdline = xstrdup(ccline.cmdbuff); const char *errormsg = NULL; emsg_off++; // Block errors when parsing the command line, and don't update v:errmsg - if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) { + if (!parse_cmdline(&cmdline, &ea, &cmdinfo, &errormsg)) { emsg_off--; goto end; } diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua @@ -615,6 +615,25 @@ describe("'inccommand' for user commands", function() :Repro abc^ | ]]) end) + + it('no crash with % + preview + file completion #28851', function() + exec_lua([[ + local function callback() end + local function preview() + return 0 + end + + vim.api.nvim_create_user_command('TestCommand', callback, { + nargs = '?', + complete = 'file', + preview = preview, + }) + + vim.cmd.edit('Xtestscript') + ]]) + feed(':TestCommand %') + assert_alive() + end) end) describe("'inccommand' with multiple buffers", function()