summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorBarrett Ruth <62671086+barrettruth@users.noreply.github.com>2026-04-24 14:13:24 -0400
committerGitHub <noreply@github.com>2026-04-24 14:13:24 -0400
commit393f687503a319a6f521e8335b4dd8030e3ea67b (patch)
tree34f6ac7bbf44dbe9df577e3bf363dd37402906d4
parent58aad59e1cf89e2bee0fc2e02c42506d2b1feeaf (diff)
fix(api): leak preview callback LuaRef in nvim_create_user_command #39357
Problem: Invalid `nvim_create_user_command` calls can leak the `preview` callback reference after Neovim has taken ownership of it. 1. build with {a,l}san 2. run: ```sh <path/to/nvim> --headless -u NONE --clean +'lua for i = 1, 100 do pcall(vim.api.nvim_create_user_command, "some very epic stuff" .. i, {}, -- NOTE: this is INVALID (not a function or string) { preview = function() end }) end vim.cmd("qa!") ' +qa ``` 3. see: ``` 100 lua references were leaked! ``` Solution: Clear `preview_luaref` in `err:`.
-rw-r--r--src/nvim/api/command.c4
-rw-r--r--test/functional/api/command_spec.lua19
2 files changed, 22 insertions, 1 deletions
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index 50febf1af4..063b99c794 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -1279,7 +1279,8 @@ void create_user_command(uint64_t channel_id, String name, Union(String, LuaRef)
if (uc_add_command(name.data, name.size, rep, argt, def, flags, context, compl_arg,
compl_luaref, preview_luaref, addr_type_arg, luaref, force) != OK) {
api_set_error(err, kErrorTypeException, "Failed to create user command");
- // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
+ // Do not goto err, since uc_add_command now owns luaref, compl_luaref, preview_luaref,
+ // and compl_arg
}
});
@@ -1288,6 +1289,7 @@ void create_user_command(uint64_t channel_id, String name, Union(String, LuaRef)
err:
NLUA_CLEAR_REF(luaref);
NLUA_CLEAR_REF(compl_luaref);
+ NLUA_CLEAR_REF(preview_luaref);
xfree(compl_arg);
}
/// Gets a map of global (non-buffer-local) Ex commands.
diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua
index 503b4a48b3..f02f041b24 100644
--- a/test/functional/api/command_spec.lua
+++ b/test/functional/api/command_spec.lua
@@ -274,6 +274,25 @@ describe('nvim_create_user_command', function()
eq(42, api.nvim_eval('g:command_fired'))
end)
+ it('does not leak `preview` LuaRef on invalid `cmd`', function()
+ local released = exec_lua(function()
+ local weak = setmetatable({}, { __mode = 'v' })
+ for i = 1, 10 do
+ local cb = function() end
+ weak[i] = cb
+ pcall(vim.api.nvim_create_user_command, 'Bogus' .. i, {}, { preview = cb })
+ end
+ collectgarbage('collect')
+ collectgarbage('collect')
+ local n = 0
+ for _ in pairs(weak) do
+ n = n + 1
+ end
+ return n
+ end)
+ eq(0, released)
+ end)
+
it('works with Lua functions', function()
exec_lua [[
result = {}