Skip to content

Commit 5801644

Browse files
committed
patch 7.4.2136
Problem: Closure function fails. Solution: Don't reset uf_scoped when it points to another funccal.
1 parent 89eaa41 commit 5801644

3 files changed

Lines changed: 81 additions & 50 deletions

File tree

src/testdir/test_lambda.vim

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,27 @@ function! Test_closure_unlet()
247247
call assert_false(has_key(s:foo(), 'x'))
248248
call test_garbagecollect_now()
249249
endfunction
250+
251+
function! LambdaFoo()
252+
let x = 0
253+
function! LambdaBar() closure
254+
let x += 1
255+
return x
256+
endfunction
257+
return function('LambdaBar')
258+
endfunction
259+
260+
func Test_closure_refcount()
261+
let g:Count = LambdaFoo()
262+
call test_garbagecollect_now()
263+
call assert_equal(1, g:Count())
264+
let g:Count2 = LambdaFoo()
265+
call test_garbagecollect_now()
266+
call assert_equal(1, g:Count2())
267+
call assert_equal(2, g:Count())
268+
call assert_equal(3, g:Count2())
269+
270+
" This causes memory access errors.
271+
" delfunc LambdaFoo
272+
" delfunc LambdaBar
273+
endfunc

src/userfunc.c

Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ static int
150150
# endif
151151
prof_self_cmp(const void *s1, const void *s2);
152152
#endif
153+
static void funccal_unref(funccall_T *fc, ufunc_T *fp);
153154

154155
void
155156
func_init()
@@ -257,6 +258,23 @@ get_function_args(
257258
return FAIL;
258259
}
259260

261+
/*
262+
* Register function "fp" as using "current_funccal" as its scope.
263+
*/
264+
static int
265+
register_closure(ufunc_T *fp)
266+
{
267+
funccal_unref(fp->uf_scoped, NULL);
268+
fp->uf_scoped = current_funccal;
269+
current_funccal->fc_refcount++;
270+
if (ga_grow(&current_funccal->fc_funcs, 1) == FAIL)
271+
return FAIL;
272+
((ufunc_T **)current_funccal->fc_funcs.ga_data)
273+
[current_funccal->fc_funcs.ga_len++] = fp;
274+
func_ref(current_funccal->func->uf_name);
275+
return OK;
276+
}
277+
260278
/*
261279
* Parse a lambda expression and get a Funcref from "*arg".
262280
* Return OK or FAIL. Returns NOTDONE for dict or {expr}.
@@ -318,7 +336,7 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
318336

319337
sprintf((char*)name, "<lambda>%d", ++lambda_no);
320338

321-
fp = (ufunc_T *)alloc((unsigned)(sizeof(ufunc_T) + STRLEN(name)));
339+
fp = (ufunc_T *)alloc_clear((unsigned)(sizeof(ufunc_T) + STRLEN(name)));
322340
if (fp == NULL)
323341
goto errret;
324342

@@ -343,13 +361,8 @@ get_lambda_tv(char_u **arg, typval_T *rettv, int evaluate)
343361
if (current_funccal != NULL && eval_lavars)
344362
{
345363
flags |= FC_CLOSURE;
346-
fp->uf_scoped = current_funccal;
347-
current_funccal->fc_refcount++;
348-
if (ga_grow(&current_funccal->fc_funcs, 1) == FAIL)
364+
if (register_closure(fp) == FAIL)
349365
goto errret;
350-
((ufunc_T **)current_funccal->fc_funcs.ga_data)
351-
[current_funccal->fc_funcs.ga_len++] = fp;
352-
func_ref(current_funccal->func->uf_name);
353366
}
354367
else
355368
fp->uf_scoped = NULL;
@@ -660,8 +673,15 @@ free_funccal(
660673
ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
661674

662675
if (fp != NULL)
663-
fp->uf_scoped = NULL;
676+
{
677+
/* Function may have been redefined and point to another
678+
* funccall_T, don't clear it then. */
679+
if (fp->uf_scoped == fc)
680+
fp->uf_scoped = NULL;
681+
func_unref(fc->func->uf_name);
682+
}
664683
}
684+
ga_clear(&fc->fc_funcs);
665685

666686
/* The a: variables typevals may not have been allocated, only free the
667687
* allocated variables. */
@@ -675,15 +695,6 @@ free_funccal(
675695
for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next)
676696
clear_tv(&li->li_tv);
677697

678-
for (i = 0; i < fc->fc_funcs.ga_len; ++i)
679-
{
680-
ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
681-
682-
if (fp != NULL)
683-
func_unref(fc->func->uf_name);
684-
}
685-
ga_clear(&fc->fc_funcs);
686-
687698
func_unref(fc->func->uf_name);
688699
vim_free(fc);
689700
}
@@ -1046,8 +1057,8 @@ call_user_func(
10461057
current_funccal = fc->caller;
10471058
--depth;
10481059

1049-
/* If the a:000 list and the l: and a: dicts are not referenced we can
1050-
* free the funccall_T and what's in it. */
1060+
/* If the a:000 list and the l: and a: dicts are not referenced and there
1061+
* is no closure using it, we can free the funccall_T and what's in it. */
10511062
if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT
10521063
&& fc->l_vars.dv_refcount == DO_NOT_FREE_CNT
10531064
&& fc->l_avars.dv_refcount == DO_NOT_FREE_CNT
@@ -1061,8 +1072,8 @@ call_user_func(
10611072
listitem_T *li;
10621073
int todo;
10631074

1064-
/* "fc" is still in use. This can happen when returning "a:000" or
1065-
* assigning "l:" to a global variable.
1075+
/* "fc" is still in use. This can happen when returning "a:000",
1076+
* assigning "l:" to a global variable or defining a closure.
10661077
* Link "fc" in the list for garbage collection later. */
10671078
fc->caller = previous_funccal;
10681079
previous_funccal = fc;
@@ -1121,13 +1132,11 @@ funccal_unref(funccall_T *fc, ufunc_T *fp)
11211132
func_unref(fc->func->uf_name);
11221133

11231134
if (fp != NULL)
1124-
{
11251135
for (i = 0; i < fc->fc_funcs.ga_len; ++i)
11261136
{
11271137
if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp)
11281138
((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL;
11291139
}
1130-
}
11311140
}
11321141
}
11331142

@@ -1976,6 +1985,12 @@ ex_function(exarg_T *eap)
19761985
{
19771986
flags |= FC_CLOSURE;
19781987
p += 7;
1988+
if (current_funccal == NULL)
1989+
{
1990+
emsg_funcname(N_("E932 Closure function should not be at top level: %s"),
1991+
name == NULL ? (char_u *)"" : name);
1992+
goto erret;
1993+
}
19791994
}
19801995
else
19811996
break;
@@ -2265,7 +2280,7 @@ ex_function(exarg_T *eap)
22652280
}
22662281
}
22672282

2268-
fp = (ufunc_T *)alloc((unsigned)(sizeof(ufunc_T) + STRLEN(name)));
2283+
fp = (ufunc_T *)alloc_clear((unsigned)(sizeof(ufunc_T) + STRLEN(name)));
22692284
if (fp == NULL)
22702285
goto erret;
22712286

@@ -2311,19 +2326,9 @@ ex_function(exarg_T *eap)
23112326
fp->uf_lines = newlines;
23122327
if ((flags & FC_CLOSURE) != 0)
23132328
{
2314-
if (current_funccal == NULL)
2315-
{
2316-
emsg_funcname(N_("E932 Closure function should not be at top level: %s"),
2317-
name);
2329+
++fp->uf_refcount;
2330+
if (register_closure(fp) == FAIL)
23182331
goto erret;
2319-
}
2320-
fp->uf_scoped = current_funccal;
2321-
current_funccal->fc_refcount++;
2322-
if (ga_grow(&current_funccal->fc_funcs, 1) == FAIL)
2323-
goto erret;
2324-
((ufunc_T **)current_funccal->fc_funcs.ga_data)
2325-
[current_funccal->fc_funcs.ga_len++] = fp;
2326-
func_ref(current_funccal->func->uf_name);
23272332
}
23282333
else
23292334
fp->uf_scoped = NULL;
@@ -3582,21 +3587,21 @@ find_hi_in_scoped_ht(char_u *name, char_u **varname, hashtab_T **pht)
35823587

35833588
/* Search in parent scope which is possible to reference from lambda */
35843589
current_funccal = current_funccal->func->uf_scoped;
3585-
while (current_funccal)
3590+
while (current_funccal != NULL)
35863591
{
3587-
ht = find_var_ht(name, varname);
3588-
if (ht != NULL && **varname != NUL)
3589-
{
3590-
hi = hash_find(ht, *varname);
3591-
if (!HASHITEM_EMPTY(hi))
3592-
{
3593-
*pht = ht;
3594-
break;
3595-
}
3596-
}
3597-
if (current_funccal == current_funccal->func->uf_scoped)
3598-
break;
3599-
current_funccal = current_funccal->func->uf_scoped;
3592+
ht = find_var_ht(name, varname);
3593+
if (ht != NULL && **varname != NUL)
3594+
{
3595+
hi = hash_find(ht, *varname);
3596+
if (!HASHITEM_EMPTY(hi))
3597+
{
3598+
*pht = ht;
3599+
break;
3600+
}
3601+
}
3602+
if (current_funccal == current_funccal->func->uf_scoped)
3603+
break;
3604+
current_funccal = current_funccal->func->uf_scoped;
36003605
}
36013606
current_funccal = old_current_funccal;
36023607

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,8 @@ static char *(features[]) =
763763

764764
static int included_patches[] =
765765
{ /* Add new patch number below this line */
766+
/**/
767+
2136,
766768
/**/
767769
2135,
768770
/**/

0 commit comments

Comments
 (0)