diff options
| author | Barrett Ruth <62671086+barrettruth@users.noreply.github.com> | 2026-04-24 14:13:24 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-24 14:13:24 -0400 |
| commit | 393f687503a319a6f521e8335b4dd8030e3ea67b (patch) | |
| tree | 34f6ac7bbf44dbe9df577e3bf363dd37402906d4 | |
| parent | 58aad59e1cf89e2bee0fc2e02c42506d2b1feeaf (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.c | 4 | ||||
| -rw-r--r-- | test/functional/api/command_spec.lua | 19 |
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 = {} |
