# HG changeset patch # User Bram Moolenaar # Date 1644264002 -3600 # Node ID d91be28bbdbbd2cae80b0b116033dc6c0c2da056 # Parent cc44ec730954fbfe5e88930ba84c987108f928d7 patch 8.2.4322: Vim9: crash when using funcref with closure Commit: https://github.com/vim/vim/commit/7aca5ca6763e50d2c23953b20e30fca7457c9abf Author: Bram Moolenaar Date: Mon Feb 7 19:56:43 2022 +0000 patch 8.2.4322: Vim9: crash when using funcref with closure Problem: Vim9: crash when using funcref with closure. Solution: Keep a reference to the funcref that has the outer context. (closes #9716) diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -4526,6 +4526,9 @@ partial_free(partial_T *pt) // "out_up" is no longer used, decrement refcount on partial that owns it. partial_unref(pt->pt_outer.out_up_partial); + // Using pt_outer from another partial. + partial_unref(pt->pt_outer_partial); + // Decrease the reference count for the context of a closure. If down // to the minimum it may be time to free it. if (pt->pt_funcstack != NULL) diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -4456,7 +4456,10 @@ common_function(typval_T *argvars, typva } if (arg_pt != NULL) - pt->pt_outer = arg_pt->pt_outer; + { + pt->pt_outer_partial = arg_pt; + ++arg_pt->pt_refcount; + } } rettv->v_type = VAR_PARTIAL; rettv->vval.v_partial = pt; diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -2051,6 +2051,9 @@ struct partial_S // For a compiled closure: the arguments and local variables scope outer_T pt_outer; + // For a partial of a partial: use pt_outer values of this partial. + partial_T *pt_outer_partial; + funcstack_T *pt_funcstack; // copy of stack, used after context // function returns diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -3477,6 +3477,25 @@ def Test_nested_closure_funcref() unlet g:result_one g:result_two enddef +def Test_nested_closure_in_dict() + var lines =<< trim END + vim9script + def Func(): dict + var n: number + def Inc(): number + ++n + return n + enddef + return {inc: function(Inc)} + enddef + disas Func + var d = Func() + assert_equal(1, d.inc()) + assert_equal(2, d.inc()) + END + v9.CheckScriptSuccess(lines) +enddef + def Test_check_func_arg_types() var lines =<< trim END vim9script diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -747,6 +747,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 4322, +/**/ 4321, /**/ 4320, diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -235,6 +235,23 @@ dict_stack_clear(int len) } /* + * Get a pointer to useful "pt_outer" of "pt". + */ + static outer_T * +get_pt_outer(partial_T *pt) +{ + partial_T *ptref = pt->pt_outer_partial; + + if (ptref == NULL) + return &pt->pt_outer; + + // partial using partial (recursively) + while (ptref->pt_outer_partial != NULL) + ptref = ptref->pt_outer_partial; + return &ptref->pt_outer; +} + +/* * Call compiled function "cdf_idx" from compiled code. * This adds a stack frame and sets the instruction pointer to the start of the * called function. @@ -421,13 +438,13 @@ call_dfunc( return FAIL; if (pt != NULL) { - ref->or_outer = &pt->pt_outer; + ref->or_outer = get_pt_outer(pt); ++pt->pt_refcount; ref->or_partial = pt; } else if (ufunc->uf_partial != NULL) { - ref->or_outer = &ufunc->uf_partial->pt_outer; + ref->or_outer = get_pt_outer(ufunc->uf_partial); ++ufunc->uf_partial->pt_refcount; ref->or_partial = ufunc->uf_partial; } @@ -5086,7 +5103,9 @@ call_def_function( goto failed_early; if (partial != NULL) { - if (partial->pt_outer.out_stack == NULL) + outer_T *outer = get_pt_outer(partial); + + if (outer->out_stack == NULL) { if (current_ectx != NULL) { @@ -5099,7 +5118,7 @@ call_def_function( } else { - ectx.ec_outer_ref->or_outer = &partial->pt_outer; + ectx.ec_outer_ref->or_outer = outer; ++partial->pt_refcount; ectx.ec_outer_ref->or_partial = partial; }