Mercurial > vim
diff src/eval.c @ 8863:e1b84109506a v7.4.1719
commit https://github.com/vim/vim/commit/107e1eef1df3b786ad3ad49fbdb9e058649303b5
Author: Bram Moolenaar <Bram@vim.org>
Date: Fri Apr 8 17:07:19 2016 +0200
patch 7.4.1719
Problem: Leaking memory when there is a cycle involving a job and a
partial.
Solution: Add a copyID to job and channel. Set references in items referred
by them. Go through all jobs and channels to find unreferenced
items. Also, decrement reference counts when garbage collecting.
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Fri, 08 Apr 2016 17:15:06 +0200 |
parents | 45fe799c9672 |
children | 30988ffb7498 |
line wrap: on
line diff
--- a/src/eval.c +++ b/src/eval.c @@ -430,6 +430,8 @@ static int get_option_tv(char_u **arg, t static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate); static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate); static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate); +static void list_free_contents(list_T *l); +static void list_free_list(list_T *l); static long list_len(list_T *l); static int list_equal(list_T *l1, list_T *l2, int ic, int recursive); static int dict_equal(dict_T *d1, dict_T *d2, int ic, int recursive); @@ -459,6 +461,9 @@ static int get_func_tv(char_u *name, int static void emsg_funcname(char *ermsg, char_u *name); static int non_zero_arg(typval_T *argvars); +static void dict_free_contents(dict_T *d); +static void dict_free_dict(dict_T *d); + #ifdef FEAT_FLOAT static void f_abs(typval_T *argvars, typval_T *rettv); static void f_acos(typval_T *argvars, typval_T *rettv); @@ -5589,7 +5594,7 @@ eval_index( { if (list_append_tv(l, &item->li_tv) == FAIL) { - list_free(l, TRUE); + list_free(l); return FAIL; } item = item->li_next; @@ -5930,20 +5935,14 @@ get_lit_string_tv(char_u **arg, typval_T } static void -partial_free(partial_T *pt, int recursive) +partial_free(partial_T *pt) { int i; for (i = 0; i < pt->pt_argc; ++i) - { - typval_T *tv = &pt->pt_argv[i]; - - if (recursive || (tv->v_type != VAR_DICT && tv->v_type != VAR_LIST)) - clear_tv(tv); - } + clear_tv(&pt->pt_argv[i]); vim_free(pt->pt_argv); - if (recursive) - dict_unref(pt->pt_dict); + dict_unref(pt->pt_dict); func_unref(pt->pt_name); vim_free(pt->pt_name); vim_free(pt); @@ -5957,27 +5956,7 @@ partial_free(partial_T *pt, int recursiv partial_unref(partial_T *pt) { if (pt != NULL && --pt->pt_refcount <= 0) - partial_free(pt, TRUE); -} - -/* - * Like clear_tv(), but do not free lists or dictionaries. - * This is when called via free_unref_items(). - */ - static void -clear_tv_no_recurse(typval_T *tv) -{ - if (tv->v_type == VAR_PARTIAL) - { - partial_T *pt = tv->vval.v_partial; - - /* We unref the partial but not the dict or any list it - * refers to. */ - if (pt != NULL && --pt->pt_refcount == 0) - partial_free(pt, FALSE); - } - else if (tv->v_type != VAR_LIST && tv->v_type != VAR_DICT) - clear_tv(tv); + partial_free(pt); } /* @@ -6031,7 +6010,7 @@ get_list_tv(char_u **arg, typval_T *rett EMSG2(_("E697: Missing end of List ']': %s"), *arg); failret: if (evaluate) - list_free(l, TRUE); + list_free(l); return FAIL; } @@ -6095,20 +6074,30 @@ rettv_list_alloc(typval_T *rettv) list_unref(list_T *l) { if (l != NULL && --l->lv_refcount <= 0) - list_free(l, TRUE); + list_free(l); } /* * Free a list, including all non-container items it points to. * Ignores the reference count. */ - void -list_free( - list_T *l, - int recurse) /* Free Lists and Dictionaries recursively. */ + static void +list_free_contents(list_T *l) { listitem_T *item; + for (item = l->lv_first; item != NULL; item = l->lv_first) + { + /* Remove the item before deleting it. */ + l->lv_first = item->li_next; + clear_tv(&item->li_tv); + vim_free(item); + } +} + + static void +list_free_list(list_T *l) +{ /* Remove the list from the list of lists for garbage collection. */ if (l->lv_used_prev == NULL) first_list = l->lv_used_next; @@ -6117,19 +6106,19 @@ list_free( if (l->lv_used_next != NULL) l->lv_used_next->lv_used_prev = l->lv_used_prev; - for (item = l->lv_first; item != NULL; item = l->lv_first) - { - /* Remove the item before deleting it. */ - l->lv_first = item->li_next; - if (recurse) - clear_tv(&item->li_tv); - else - clear_tv_no_recurse(&item->li_tv); - vim_free(item); - } vim_free(l); } + void +list_free(list_T *l) +{ + if (!in_free_unref_items) + { + list_free_contents(l); + list_free_list(l); + } +} + /* * Allocate a list item. * It is not initialized, don't forget to set v_lock. @@ -7016,7 +7005,7 @@ garbage_collect(void) #endif #ifdef FEAT_JOB_CHANNEL - abort = abort || set_ref_in_channel(copyID); +// abort = abort || set_ref_in_channel(copyID); #endif if (!abort) @@ -7056,7 +7045,7 @@ garbage_collect(void) } /* - * Free lists, dictionaries and jobs that are no longer referenced. + * Free lists, dictionaries, channels and jobs that are no longer referenced. */ static int free_unref_items(int copyID) @@ -7065,29 +7054,66 @@ free_unref_items(int copyID) list_T *ll, *ll_next; int did_free = FALSE; + /* Let all "free" functions know that we are here. This means no + * dictionaries, lists, channels or jobs are to be freed, because we will + * do that here. */ + in_free_unref_items = TRUE; + + /* + * PASS 1: free the contents of the items. We don't free the items + * themselves yet, so that it is possible to decrement refcount counters + */ + /* * Go through the list of dicts and free items without the copyID. */ - for (dd = first_dict; dd != NULL; ) - { - dd_next = dd->dv_used_next; + for (dd = first_dict; dd != NULL; dd = dd->dv_used_next) if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) { /* Free the Dictionary and ordinary items it contains, but don't * recurse into Lists and Dictionaries, they will be in the list * of dicts or list of lists. */ - dict_free(dd, FALSE); + dict_free_contents(dd); did_free = TRUE; } - dd = dd_next; - } /* * Go through the list of lists and free items without the copyID. * But don't free a list that has a watcher (used in a for loop), these * are not referenced anywhere. */ - for (ll = first_list; ll != NULL; ) + for (ll = first_list; ll != NULL; ll = ll->lv_used_next) + if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) + && ll->lv_watch == NULL) + { + /* Free the List and ordinary items it contains, but don't recurse + * into Lists and Dictionaries, they will be in the list of dicts + * or list of lists. */ + list_free_contents(ll); + did_free = TRUE; + } + +#ifdef FEAT_JOB_CHANNEL + /* Go through the list of jobs and free items without the copyID. This + * must happen before doing channels, because jobs refer to channels, but + * the reference from the channel to the job isn't tracked. */ + did_free |= free_unused_jobs_contents(copyID, COPYID_MASK); + + /* Go through the list of channels and free items without the copyID. */ + did_free |= free_unused_channels_contents(copyID, COPYID_MASK); +#endif + + /* + * PASS 2: free the items themselves. + */ + for (dd = first_dict; dd != NULL; dd = dd_next) + { + dd_next = dd->dv_used_next; + if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) + dict_free_dict(dd); + } + + for (ll = first_list; ll != NULL; ll = ll_next) { ll_next = ll->lv_used_next; if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) @@ -7096,11 +7122,21 @@ free_unref_items(int copyID) /* Free the List and ordinary items it contains, but don't recurse * into Lists and Dictionaries, they will be in the list of dicts * or list of lists. */ - list_free(ll, FALSE); - did_free = TRUE; - } - ll = ll_next; - } + list_free_list(ll); + } + } + +#ifdef FEAT_JOB_CHANNEL + /* Go through the list of jobs and free items without the copyID. This + * must happen before doing channels, because jobs refer to channels, but + * the reference from the channel to the job isn't tracked. */ + free_unused_jobs(copyID, COPYID_MASK); + + /* Go through the list of channels and free items without the copyID. */ + free_unused_channels(copyID, COPYID_MASK); +#endif + + in_free_unref_items = FALSE; return did_free; } @@ -7204,18 +7240,12 @@ set_ref_in_item( ht_stack_T **ht_stack, list_stack_T **list_stack) { - dict_T *dd; - list_T *ll; int abort = FALSE; - if (tv->v_type == VAR_DICT || tv->v_type == VAR_PARTIAL) - { - if (tv->v_type == VAR_DICT) - dd = tv->vval.v_dict; - else if (tv->vval.v_partial != NULL) - dd = tv->vval.v_partial->pt_dict; - else - dd = NULL; + if (tv->v_type == VAR_DICT) + { + dict_T *dd = tv->vval.v_dict; + if (dd != NULL && dd->dv_copyID != copyID) { /* Didn't see this dict yet. */ @@ -7237,20 +7267,11 @@ set_ref_in_item( } } } - if (tv->v_type == VAR_PARTIAL) - { - partial_T *pt = tv->vval.v_partial; - int i; - - if (pt != NULL) - for (i = 0; i < pt->pt_argc; ++i) - abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, - ht_stack, list_stack); - } } else if (tv->v_type == VAR_LIST) { - ll = tv->vval.v_list; + list_T *ll = tv->vval.v_list; + if (ll != NULL && ll->lv_copyID != copyID) { /* Didn't see this list yet. */ @@ -7274,6 +7295,96 @@ set_ref_in_item( } } } + else if (tv->v_type == VAR_PARTIAL) + { + partial_T *pt = tv->vval.v_partial; + int i; + + /* A partial does not have a copyID, because it cannot contain itself. + */ + if (pt != NULL) + { + if (pt->pt_dict != NULL) + { + typval_T dtv; + + dtv.v_type = VAR_DICT; + dtv.vval.v_dict = pt->pt_dict; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + + for (i = 0; i < pt->pt_argc; ++i) + abort = abort || set_ref_in_item(&pt->pt_argv[i], copyID, + ht_stack, list_stack); + } + } +#ifdef FEAT_JOB_CHANNEL + else if (tv->v_type == VAR_JOB) + { + job_T *job = tv->vval.v_job; + typval_T dtv; + + if (job != NULL && job->jv_copyID != copyID) + { + if (job->jv_channel != NULL) + { + dtv.v_type = VAR_CHANNEL; + dtv.vval.v_channel = job->jv_channel; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + if (job->jv_exit_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = job->jv_exit_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + } + } + else if (tv->v_type == VAR_CHANNEL) + { + channel_T *ch =tv->vval.v_channel; + int part; + typval_T dtv; + jsonq_T *jq; + cbq_T *cq; + + if (ch != NULL && ch->ch_copyID != copyID) + { + for (part = PART_SOCK; part <= PART_IN; ++part) + { + for (jq = ch->ch_part[part].ch_json_head.jq_next; jq != NULL; + jq = jq->jq_next) + set_ref_in_item(jq->jq_value, copyID, ht_stack, list_stack); + for (cq = ch->ch_part[part].ch_cb_head.cq_next; cq != NULL; + cq = cq->cq_next) + if (cq->cq_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = cq->cq_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + if (ch->ch_part[part].ch_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = ch->ch_part[part].ch_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + } + if (ch->ch_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = ch->ch_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + if (ch->ch_close_partial != NULL) + { + dtv.v_type = VAR_PARTIAL; + dtv.vval.v_partial = ch->ch_close_partial; + set_ref_in_item(&dtv, copyID, ht_stack, list_stack); + } + } + } +#endif return abort; } @@ -7332,30 +7443,20 @@ rettv_dict_alloc(typval_T *rettv) dict_unref(dict_T *d) { if (d != NULL && --d->dv_refcount <= 0) - dict_free(d, TRUE); + dict_free(d); } /* * Free a Dictionary, including all non-container items it contains. * Ignores the reference count. */ - void -dict_free( - dict_T *d, - int recurse) /* Free Lists and Dictionaries recursively. */ + static void +dict_free_contents(dict_T *d) { int todo; hashitem_T *hi; dictitem_T *di; - /* Remove the dict from the list of dicts for garbage collection. */ - if (d->dv_used_prev == NULL) - first_dict = d->dv_used_next; - else - d->dv_used_prev->dv_used_next = d->dv_used_next; - if (d->dv_used_next != NULL) - d->dv_used_next->dv_used_prev = d->dv_used_prev; - /* Lock the hashtab, we don't want it to resize while freeing items. */ hash_lock(&d->dv_hashtab); todo = (int)d->dv_hashtab.ht_used; @@ -7367,18 +7468,37 @@ dict_free( * something recursive causing trouble. */ di = HI2DI(hi); hash_remove(&d->dv_hashtab, hi); - if (recurse) - clear_tv(&di->di_tv); - else - clear_tv_no_recurse(&di->di_tv); + clear_tv(&di->di_tv); vim_free(di); --todo; } } hash_clear(&d->dv_hashtab); +} + + static void +dict_free_dict(dict_T *d) +{ + /* Remove the dict from the list of dicts for garbage collection. */ + if (d->dv_used_prev == NULL) + first_dict = d->dv_used_next; + else + d->dv_used_prev->dv_used_next = d->dv_used_next; + if (d->dv_used_next != NULL) + d->dv_used_next->dv_used_prev = d->dv_used_prev; vim_free(d); } + void +dict_free(dict_T *d) +{ + if (!in_free_unref_items) + { + dict_free_contents(d); + dict_free_dict(d); + } +} + /* * Allocate a Dictionary item. * The "key" is copied to the new item. @@ -7827,7 +7947,7 @@ get_dict_tv(char_u **arg, typval_T *rett EMSG2(_("E723: Missing end of Dictionary '}': %s"), *arg); failret: if (evaluate) - dict_free(d, TRUE); + dict_free(d); return FAIL; } @@ -7842,9 +7962,6 @@ failret: return OK; } -#if defined(FEAT_JOB_CHANNEL) || defined(PROTO) -#endif - static char * get_var_special_name(int nr) { @@ -15391,7 +15508,7 @@ find_some_match(typval_T *argvars, typva || list_append_number(rettv->vval.v_list, (varnumber_T)-1) == FAIL)) { - list_free(rettv->vval.v_list, TRUE); + list_free(rettv->vval.v_list); rettv->vval.v_list = NULL; goto theend; } @@ -16488,7 +16605,7 @@ f_readfile(typval_T *argvars, typval_T * if (failed) { - list_free(rettv->vval.v_list, TRUE); + list_free(rettv->vval.v_list); /* readfile doc says an empty list is returned on error */ rettv->vval.v_list = list_alloc(); } @@ -20070,7 +20187,7 @@ errret: if (res != NULL) vim_free(res); if (list != NULL) - list_free(list, TRUE); + list_free(list); } /*