Mercurial > vim
diff src/vim9execute.c @ 22541:7d6ba4204f66 v8.2.1819
patch 8.2.1819: Vim9: Memory leak when using a closure
Commit: https://github.com/vim/vim/commit/85d5e2b723e6fc233e53252dd5c523944146fbc2
Author: Bram Moolenaar <Bram@vim.org>
Date: Sat Oct 10 14:13:01 2020 +0200
patch 8.2.1819: Vim9: Memory leak when using a closure
Problem: Vim9: Memory leak when using a closure.
Solution: Compute the mininal refcount in the funcstack. Reenable disabled
tests.
author | Bram Moolenaar <Bram@vim.org> |
---|---|
date | Sat, 10 Oct 2020 14:15:04 +0200 |
parents | c3e3c5707fe9 |
children | 107eae953b87 |
line wrap: on
line diff
--- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -349,8 +349,8 @@ handle_closure_in_use(ectx_T *ectx, int // Move them to the called function. if (funcstack == NULL) return FAIL; - funcstack->fs_ga.ga_len = argcount + STACK_FRAME_SIZE - + dfunc->df_varcount; + funcstack->fs_var_offset = argcount + STACK_FRAME_SIZE; + funcstack->fs_ga.ga_len = funcstack->fs_var_offset + dfunc->df_varcount; stack = ALLOC_CLEAR_MULT(typval_T, funcstack->fs_ga.ga_len); funcstack->fs_ga.ga_data = stack; if (stack == NULL) @@ -376,29 +376,22 @@ handle_closure_in_use(ectx_T *ectx, int { tv = STACK_TV(ectx->ec_frame_idx + STACK_FRAME_SIZE + idx); - // Do not copy a partial created for a local function. - // TODO: This won't work if the closure actually uses it. But when - // keeping it it gets complicated: it will create a reference cycle - // inside the partial, thus needs special handling for garbage - // collection. - // For now, decide on the reference count. + // A partial created for a local function, that is also used as a + // local variable, has a reference count for the variable, thus + // will never go down to zero. When all these refcounts are one + // then the funcstack is unused. We need to count how many we have + // so we need when to check. if (tv->v_type == VAR_PARTIAL && tv->vval.v_partial != NULL) { - int i; + int i; for (i = 0; i < closure_count; ++i) - { - partial_T *pt = ((partial_T **)gap->ga_data)[gap->ga_len - - closure_count + i]; - - if (tv->vval.v_partial == pt && pt->pt_refcount < 2) - break; - } - if (i < closure_count) - continue; + if (tv->vval.v_partial == ((partial_T **)gap->ga_data)[ + gap->ga_len - closure_count + i]) + ++funcstack->fs_min_refcount; } - *(stack + argcount + STACK_FRAME_SIZE + idx) = *tv; + *(stack + funcstack->fs_var_offset + idx) = *tv; tv->v_type = VAR_UNKNOWN; } @@ -427,6 +420,43 @@ handle_closure_in_use(ectx_T *ectx, int } /* + * Called when a partial is freed or its reference count goes down to one. The + * funcstack may be the only reference to the partials in the local variables. + * Go over all of them, the funcref and can be freed if all partials + * referencing the funcstack have a reference count of one. + */ + void +funcstack_check_refcount(funcstack_T *funcstack) +{ + int i; + garray_T *gap = &funcstack->fs_ga; + int done = 0; + + if (funcstack->fs_refcount > funcstack->fs_min_refcount) + return; + for (i = funcstack->fs_var_offset; i < gap->ga_len; ++i) + { + typval_T *tv = ((typval_T *)gap->ga_data) + i; + + if (tv->v_type == VAR_PARTIAL && tv->vval.v_partial != NULL + && tv->vval.v_partial->pt_funcstack == funcstack + && tv->vval.v_partial->pt_refcount == 1) + ++done; + } + if (done == funcstack->fs_min_refcount) + { + typval_T *stack = gap->ga_data; + + // All partials referencing the funcstack have a reference count of + // one, thus the funcstack is no longer of use. + for (i = 0; i < gap->ga_len; ++i) + clear_tv(stack + i); + vim_free(stack); + vim_free(funcstack); + } +} + +/* * Return from the current function. */ static int