diff options
author | Bram Moolenaar <Bram@vim.org> | 2016-08-01 20:46:25 +0200 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2016-08-01 20:46:25 +0200 |
commit | 8dd3a43d75550e9b5736066124c97697564f769e (patch) | |
tree | 5768a06605c7ffaacfbb8aa385060cc7be223fec /src/userfunc.c | |
parent | ba96e9af388804364425185b47eed14988302865 (diff) | |
download | vim-8dd3a43d75550e9b5736066124c97697564f769e.zip |
patch 7.4.2142
Problem: Leaking memory when redefining a function.
Solution: Don't increment the function reference count when it's found by
name. Don't remove the wrong function from the hashtab. More
reference counting fixes.
Diffstat (limited to 'src/userfunc.c')
-rw-r--r-- | src/userfunc.c | 100 |
1 files changed, 59 insertions, 41 deletions
diff --git a/src/userfunc.c b/src/userfunc.c index 65cc5909f..e08e53a78 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -15,11 +15,12 @@ #if defined(FEAT_EVAL) || defined(PROTO) /* function flags */ -#define FC_ABORT 1 /* abort function on error */ -#define FC_RANGE 2 /* function accepts range */ -#define FC_DICT 4 /* Dict function, uses "self" */ -#define FC_CLOSURE 8 /* closure, uses outer scope variables */ -#define FC_DELETED 16 /* :delfunction used while uf_refcount > 0 */ +#define FC_ABORT 0x01 /* abort function on error */ +#define FC_RANGE 0x02 /* function accepts range */ +#define FC_DICT 0x04 /* Dict function, uses "self" */ +#define FC_CLOSURE 0x08 /* closure, uses outer scope variables */ +#define FC_DELETED 0x10 /* :delfunction used while uf_refcount > 0 */ +#define FC_REMOVED 0x20 /* function redefined while uf_refcount > 0 */ /* From user function to hashitem and back. */ #define UF2HIKEY(fp) ((fp)->uf_name) @@ -178,14 +179,18 @@ err_ret: static int register_closure(ufunc_T *fp) { - funccal_unref(fp->uf_scoped, NULL); + if (fp->uf_scoped == current_funccal) + /* no change */ + return OK; + funccal_unref(fp->uf_scoped, fp); fp->uf_scoped = current_funccal; current_funccal->fc_refcount++; + func_ptr_ref(current_funccal->func); + if (ga_grow(¤t_funccal->fc_funcs, 1) == FAIL) return FAIL; ((ufunc_T **)current_funccal->fc_funcs.ga_data) [current_funccal->fc_funcs.ga_len++] = fp; - func_ptr_ref(current_funccal->func); return OK; } @@ -1024,60 +1029,56 @@ call_user_func( /* * Unreference "fc": decrement the reference count and free it when it - * becomes zero. If "fp" is not NULL, "fp" is detached from "fc". + * becomes zero. "fp" is detached from "fc". */ static void funccal_unref(funccall_T *fc, ufunc_T *fp) { funccall_T **pfc; int i; - int freed = FALSE; if (fc == NULL) return; - if (--fc->fc_refcount <= 0) - { + if (--fc->fc_refcount <= 0 + && fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT + && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT + && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { if (fc == *pfc) { - if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT - && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT - && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) - { - *pfc = fc->caller; - free_funccal(fc, TRUE); - freed = TRUE; - } - break; + *pfc = fc->caller; + free_funccal(fc, TRUE); + return; } } - } - if (!freed) + for (i = 0; i < fc->fc_funcs.ga_len; ++i) { - func_ptr_unref(fc->func); - - if (fp != NULL) - for (i = 0; i < fc->fc_funcs.ga_len; ++i) - { - if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) - ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; - } + if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) + { + func_ptr_unref(fc->func); + ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL; + } } } /* * Remove the function from the function hashtable. If the function was * deleted while it still has references this was already done. + * Return TRUE if the entry was deleted, FALSE if it wasn't found. */ - static void + static int func_remove(ufunc_T *fp) { hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp)); if (!HASHITEM_EMPTY(hi)) + { hash_remove(&func_hashtab, hi); + return TRUE; + } + return FALSE; } /* @@ -1094,7 +1095,10 @@ func_free(ufunc_T *fp) vim_free(fp->uf_tml_total); vim_free(fp->uf_tml_self); #endif - func_remove(fp); + /* only remove it when not done already, otherwise we would remove a newer + * version of the function */ + if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) + func_remove(fp); funccal_unref(fp->uf_scoped, fp); @@ -2158,6 +2162,7 @@ ex_function(exarg_T *eap) /* This function is referenced somewhere, don't redefine it but * create a new one. */ --fp->uf_refcount; + fp->uf_flags |= FC_REMOVED; fp = NULL; overwrite = TRUE; } @@ -2651,6 +2656,20 @@ get_user_func_name(expand_T *xp, int idx) #endif /* FEAT_CMDL_COMPL */ /* + * There are two kinds of function names: + * 1. ordinary names, function defined with :function + * 2. numbered functions and lambdas + * For the first we only count the name stored in func_hashtab as a reference, + * using function() does not count as a reference, because the function is + * looked up by name. + */ + static int +func_name_refcount(char_u *name) +{ + return isdigit(*name) || *name == '<'; +} + +/* * ":delfunction {name}" */ void @@ -2705,19 +2724,18 @@ ex_delfunction(exarg_T *eap) } else { - /* Normal functions (not numbered functions and lambdas) have a + /* A normal function (not a numbered function or lambda) has a * refcount of 1 for the entry in the hashtable. When deleting - * them and the refcount is more than one, it should be kept. - * Numbered functions and lambdas snould be kept if the refcount is + * it and the refcount is more than one, it should be kept. + * A numbered function and lambda snould be kept if the refcount is * one or more. */ - if (fp->uf_refcount > (isdigit(fp->uf_name[0]) - || fp->uf_name[0] == '<' ? 0 : 1)) + if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) { /* Function is still referenced somewhere. Don't free it but * do remove it from the hashtable. */ - func_remove(fp); + if (func_remove(fp)) + fp->uf_refcount--; fp->uf_flags |= FC_DELETED; - fp->uf_refcount--; } else func_free(fp); @@ -2734,7 +2752,7 @@ func_unref(char_u *name) { ufunc_T *fp = NULL; - if (name == NULL) + if (name == NULL || !func_name_refcount(name)) return; fp = find_func(name); if (fp == NULL && isdigit(*name)) @@ -2777,7 +2795,7 @@ func_ref(char_u *name) { ufunc_T *fp; - if (name == NULL) + if (name == NULL || !func_name_refcount(name)) return; fp = find_func(name); if (fp != NULL) |