diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 06746f8813..2b82c14ca4 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -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 diff --git a/src/nvim/eval/gc.c b/src/nvim/eval/gc.c index bcebd87f71..97be0ba756 100644 --- a/src/nvim/eval/gc.c +++ b/src/nvim/eval/gc.c @@ -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; diff --git a/src/nvim/eval/gc.h b/src/nvim/eval/gc.h index ea91952fff..8fdb715f02 100644 --- a/src/nvim/eval/gc.h +++ b/src/nvim/eval/gc.h @@ -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; diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 7f984b1e24..560989085b 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -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(