fix(channel): unreference list after callback finishes (#37358)

This commit is contained in:
zeertzjq
2026-01-12 08:33:19 +08:00
parent f21c169a02
commit c469cba162
4 changed files with 53 additions and 5 deletions

View File

@@ -807,6 +807,10 @@ static void channel_callback_call(Channel *chan, CallbackReader *reader)
typval_T rettv = TV_INITIAL_VALUE;
callback_call(cb, 3, argv, &rettv);
tv_clear(&rettv);
if (reader) {
tv_list_unref(argv[1].vval.v_list);
}
}
/// Open terminal for channel

View File

@@ -7,6 +7,6 @@
#endif
/// Head of list of all dictionaries
dict_T *gc_first_dict = NULL;
DLLEXPORT dict_T *gc_first_dict = NULL;
/// Head of list of all lists
list_T *gc_first_list = NULL;
DLLEXPORT list_T *gc_first_list = NULL;

View File

@@ -2,9 +2,9 @@
#include "nvim/eval/typval_defs.h"
extern dict_T *gc_first_dict;
extern list_T *gc_first_list;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/gc.h.generated.h"
#endif
DLLEXPORT extern dict_T *gc_first_dict;
DLLEXPORT extern list_T *gc_first_list;

View File

@@ -712,6 +712,50 @@ describe('jobs', function()
)
end)
it('lists passed to callbacks are freed if not stored #25891', function()
if not exec_lua('return pcall(require, "ffi")') then
pending('missing LuaJIT FFI')
end
source([[
let g:stdout = ''
func AppendStrOnEvent(id, data, event)
let g:stdout ..= join(a:data, "\n")
endfunc
let g:job_opts = {'on_stdout': function('AppendStrOnEvent')}
]])
local job = eval([[jobstart(['cat', '-'], g:job_opts)]])
exec_lua(function()
local ffi = require('ffi')
ffi.cdef([[
typedef struct listvar_S list_T;
list_T *gc_first_list;
list_T *tv_list_alloc(ptrdiff_t len);
void tv_list_free(list_T *const l);
]])
_G.L = ffi.C.tv_list_alloc(1)
_G.L_val = ffi.cast('uintptr_t', _G.L)
assert(ffi.cast('uintptr_t', ffi.C.gc_first_list) == _G.L_val)
end)
local str_all = ''
for _, str in ipairs({ 'LINE1\nLINE2\nLINE3\n', 'LINE4\n', 'LINE5\nLINE6\n' }) do
str_all = str_all .. str
api.nvim_chan_send(job, str)
retry(nil, 1000, function()
eq(str_all, api.nvim_get_var('stdout'))
end)
end
exec_lua(function()
local ffi = require('ffi')
assert(ffi.cast('uintptr_t', ffi.C.gc_first_list) == _G.L_val)
ffi.C.tv_list_free(_G.L)
assert(ffi.cast('uintptr_t', ffi.C.gc_first_list) ~= _G.L_val)
end)
end)
it('jobstart() environment: $NVIM, $NVIM_LISTEN_ADDRESS #11009', function()
local function get_child_env(envname, env)
return exec_lua(