To: vim_dev@googlegroups.com Subject: Patch 7.4.1719 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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. Files: src/eval.c, src/channel.c, src/globals.h, src/ops.c, src/regexp.c, src/tag.c, src/proto/channel.pro, src/proto/eval.pro, src/testdir/test_partial.vim, src/structs.h *** ../vim-7.4.1718/src/eval.c 2016-04-07 22:16:26.484303131 +0200 --- src/eval.c 2016-04-08 16:03:27.602304722 +0200 *************** *** 430,435 **** --- 430,437 ---- 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,464 **** --- 461,469 ---- 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,5595 **** { if (list_append_tv(l, &item->li_tv) == FAIL) { ! list_free(l, TRUE); return FAIL; } item = item->li_next; --- 5594,5600 ---- { if (list_append_tv(l, &item->li_tv) == FAIL) { ! list_free(l); return FAIL; } item = item->li_next; *************** *** 5930,5949 **** } static void ! partial_free(partial_T *pt, int recursive) { 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); ! } vim_free(pt->pt_argv); ! if (recursive) ! dict_unref(pt->pt_dict); func_unref(pt->pt_name); vim_free(pt->pt_name); vim_free(pt); --- 5935,5948 ---- } static void ! partial_free(partial_T *pt) { int i; for (i = 0; i < pt->pt_argc; ++i) ! clear_tv(&pt->pt_argv[i]); vim_free(pt->pt_argv); ! dict_unref(pt->pt_dict); func_unref(pt->pt_name); vim_free(pt->pt_name); vim_free(pt); *************** *** 5957,5983 **** 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); } /* --- 5956,5962 ---- partial_unref(partial_T *pt) { if (pt != NULL && --pt->pt_refcount <= 0) ! partial_free(pt); } /* *************** *** 6031,6037 **** EMSG2(_("E697: Missing end of List ']': %s"), *arg); failret: if (evaluate) ! list_free(l, TRUE); return FAIL; } --- 6010,6016 ---- EMSG2(_("E697: Missing end of List ']': %s"), *arg); failret: if (evaluate) ! list_free(l); return FAIL; } *************** *** 6095,6114 **** list_unref(list_T *l) { if (l != NULL && --l->lv_refcount <= 0) ! list_free(l, TRUE); } /* * 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. */ { listitem_T *item; /* Remove the list from the list of lists for garbage collection. */ if (l->lv_used_prev == NULL) first_list = l->lv_used_next; --- 6074,6103 ---- list_unref(list_T *l) { if (l != NULL && --l->lv_refcount <= 0) ! list_free(l); } /* * Free a list, including all non-container items it points to. * Ignores the reference count. */ ! 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,6133 **** 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); } /* --- 6106,6122 ---- if (l->lv_used_next != NULL) l->lv_used_next->lv_used_prev = l->lv_used_prev; ! vim_free(l); ! } ! ! void ! list_free(list_T *l) ! { ! if (!in_free_unref_items) { ! list_free_contents(l); ! list_free_list(l); } } /* *************** *** 7016,7022 **** #endif #ifdef FEAT_JOB_CHANNEL ! abort = abort || set_ref_in_channel(copyID); #endif if (!abort) --- 7005,7011 ---- #endif #ifdef FEAT_JOB_CHANNEL ! // abort = abort || set_ref_in_channel(copyID); #endif if (!abort) *************** *** 7056,7062 **** } /* ! * Free lists, dictionaries and jobs that are no longer referenced. */ static int free_unref_items(int copyID) --- 7045,7051 ---- } /* ! * Free lists, dictionaries, channels and jobs that are no longer referenced. */ static int free_unref_items(int copyID) *************** *** 7065,7093 **** list_T *ll, *ll_next; int did_free = FALSE; /* * Go through the list of dicts and free items without the copyID. */ ! for (dd = first_dict; dd != NULL; ) ! { ! dd_next = 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); 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; ) { ll_next = ll->lv_used_next; if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) --- 7054,7119 ---- 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 = 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_contents(dd); did_free = TRUE; } /* * 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; 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,7107 **** /* 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; } return did_free; } --- 7122,7143 ---- /* 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_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,7221 **** 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 (dd != NULL && dd->dv_copyID != copyID) { /* Didn't see this dict yet. */ --- 7240,7251 ---- ht_stack_T **ht_stack, list_stack_T **list_stack) { int abort = FALSE; ! 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,7256 **** } } } - 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; if (ll != NULL && ll->lv_copyID != copyID) { /* Didn't see this list yet. */ --- 7267,7277 ---- } } } } else if (tv->v_type == VAR_LIST) { ! list_T *ll = tv->vval.v_list; ! if (ll != NULL && ll->lv_copyID != copyID) { /* Didn't see this list yet. */ *************** *** 7274,7279 **** --- 7295,7390 ---- } } } + 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,7361 **** dict_unref(dict_T *d) { if (d != NULL && --d->dv_refcount <= 0) ! dict_free(d, TRUE); } /* * 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. */ { 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; --- 7443,7462 ---- dict_unref(dict_T *d) { if (d != NULL && --d->dv_refcount <= 0) ! dict_free(d); } /* * Free a Dictionary, including all non-container items it contains. * Ignores the reference count. */ ! static void ! dict_free_contents(dict_T *d) { int todo; hashitem_T *hi; dictitem_T *di; /* 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,7384 **** * 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); vim_free(di); --todo; } } hash_clear(&d->dv_hashtab); vim_free(d); } /* * Allocate a Dictionary item. * The "key" is copied to the new item. --- 7468,7504 ---- * something recursive causing trouble. */ di = HI2DI(hi); hash_remove(&d->dv_hashtab, hi); ! 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,7833 **** EMSG2(_("E723: Missing end of Dictionary '}': %s"), *arg); failret: if (evaluate) ! dict_free(d, TRUE); return FAIL; } --- 7947,7953 ---- EMSG2(_("E723: Missing end of Dictionary '}': %s"), *arg); failret: if (evaluate) ! dict_free(d); return FAIL; } *************** *** 7842,7850 **** return OK; } - #if defined(FEAT_JOB_CHANNEL) || defined(PROTO) - #endif - static char * get_var_special_name(int nr) { --- 7962,7967 ---- *************** *** 15391,15397 **** || list_append_number(rettv->vval.v_list, (varnumber_T)-1) == FAIL)) { ! list_free(rettv->vval.v_list, TRUE); rettv->vval.v_list = NULL; goto theend; } --- 15508,15514 ---- || list_append_number(rettv->vval.v_list, (varnumber_T)-1) == FAIL)) { ! list_free(rettv->vval.v_list); rettv->vval.v_list = NULL; goto theend; } *************** *** 16488,16494 **** if (failed) { ! list_free(rettv->vval.v_list, TRUE); /* readfile doc says an empty list is returned on error */ rettv->vval.v_list = list_alloc(); } --- 16605,16611 ---- if (failed) { ! list_free(rettv->vval.v_list); /* readfile doc says an empty list is returned on error */ rettv->vval.v_list = list_alloc(); } *************** *** 20070,20076 **** if (res != NULL) vim_free(res); if (list != NULL) ! list_free(list, TRUE); } /* --- 20187,20193 ---- if (res != NULL) vim_free(res); if (list != NULL) ! list_free(list); } /* *** ../vim-7.4.1718/src/channel.c 2016-04-07 21:40:34.066966030 +0200 --- src/channel.c 2016-04-08 16:08:07.127390181 +0200 *************** *** 368,373 **** --- 368,406 ---- } /* + * Close a channel and free all its resources. + */ + static void + channel_free_contents(channel_T *channel) + { + channel_close(channel, TRUE); + channel_clear(channel); + ch_log(channel, "Freeing channel"); + } + + static void + channel_free_channel(channel_T *channel) + { + if (channel->ch_next != NULL) + channel->ch_next->ch_prev = channel->ch_prev; + if (channel->ch_prev == NULL) + first_channel = channel->ch_next; + else + channel->ch_prev->ch_next = channel->ch_next; + vim_free(channel); + } + + static void + channel_free(channel_T *channel) + { + if (!in_free_unref_items) + { + channel_free_contents(channel); + channel_free_channel(channel); + } + } + + /* * Close a channel and free all its resources if there is no further action * possible, there is no callback to be invoked or the associated job was * killed. *************** *** 397,418 **** return FALSE; } ! /* ! * Close a channel and free all its resources. ! */ void ! channel_free(channel_T *channel) { ! channel_close(channel, TRUE); ! channel_clear(channel); ! ch_log(channel, "Freeing channel"); ! if (channel->ch_next != NULL) ! channel->ch_next->ch_prev = channel->ch_prev; ! if (channel->ch_prev == NULL) ! first_channel = channel->ch_next; ! else ! channel->ch_prev->ch_next = channel->ch_next; ! vim_free(channel); } #if defined(FEAT_GUI) || defined(PROTO) --- 430,468 ---- return FALSE; } ! int ! free_unused_channels_contents(int copyID, int mask) ! { ! int did_free = FALSE; ! channel_T *ch; ! ! for (ch = first_channel; ch != NULL; ch = ch->ch_next) ! if ((ch->ch_copyID & mask) != (copyID & mask)) ! { ! /* Free the channel and ordinary items it contains, but don't ! * recurse into Lists, Dictionaries etc. */ ! channel_free_contents(ch); ! did_free = TRUE; ! } ! return did_free; ! } ! void ! free_unused_channels(int copyID, int mask) { ! channel_T *ch; ! channel_T *ch_next; ! ! for (ch = first_channel; ch != NULL; ch = ch_next) ! { ! ch_next = ch->ch_next; ! if ((ch->ch_copyID & mask) != (copyID & mask)) ! { ! /* Free the channel and ordinary items it contains, but don't ! * recurse into Lists, Dictionaries etc. */ ! channel_free_channel(ch); ! } ! } } #if defined(FEAT_GUI) || defined(PROTO) *************** *** 2457,2462 **** --- 2507,2513 ---- channel_clear_one(channel, PART_SOCK); channel_clear_one(channel, PART_OUT); channel_clear_one(channel, PART_ERR); + /* there is no callback or queue for PART_IN */ vim_free(channel->ch_callback); channel->ch_callback = NULL; partial_unref(channel->ch_partial); *************** *** 3913,3919 **** static job_T *first_job = NULL; static void ! job_free(job_T *job) { ch_log(job->jv_channel, "Freeing job"); if (job->jv_channel != NULL) --- 3964,3970 ---- static job_T *first_job = NULL; static void ! job_free_contents(job_T *job) { ch_log(job->jv_channel, "Freeing job"); if (job->jv_channel != NULL) *************** *** 3928,3946 **** } mch_clear_job(job); if (job->jv_next != NULL) job->jv_next->jv_prev = job->jv_prev; if (job->jv_prev == NULL) first_job = job->jv_next; else job->jv_prev->jv_next = job->jv_next; - - vim_free(job->jv_stoponexit); - vim_free(job->jv_exit_cb); - partial_unref(job->jv_exit_partial); vim_free(job); } void job_unref(job_T *job) { --- 3979,4011 ---- } mch_clear_job(job); + vim_free(job->jv_stoponexit); + vim_free(job->jv_exit_cb); + partial_unref(job->jv_exit_partial); + } + + static void + job_free_job(job_T *job) + { if (job->jv_next != NULL) job->jv_next->jv_prev = job->jv_prev; if (job->jv_prev == NULL) first_job = job->jv_next; else job->jv_prev->jv_next = job->jv_next; vim_free(job); } + static void + job_free(job_T *job) + { + if (!in_free_unref_items) + { + job_free_contents(job); + job_free_job(job); + } + } + void job_unref(job_T *job) { *************** *** 3963,3968 **** --- 4028,4068 ---- } } } + + int + free_unused_jobs_contents(int copyID, int mask) + { + int did_free = FALSE; + job_T *job; + + for (job = first_job; job != NULL; job = job->jv_next) + if ((job->jv_copyID & mask) != (copyID & mask)) + { + /* Free the channel and ordinary items it contains, but don't + * recurse into Lists, Dictionaries etc. */ + job_free_contents(job); + did_free = TRUE; + } + return did_free; + } + + void + free_unused_jobs(int copyID, int mask) + { + job_T *job; + job_T *job_next; + + for (job = first_job; job != NULL; job = job_next) + { + job_next = job->jv_next; + if ((job->jv_copyID & mask) != (copyID & mask)) + { + /* Free the channel and ordinary items it contains, but don't + * recurse into Lists, Dictionaries etc. */ + job_free_job(job); + } + } + } /* * Allocate a job. Sets the refcount to one and sets options default. *** ../vim-7.4.1718/src/globals.h 2016-03-19 22:11:47.436674835 +0100 --- src/globals.h 2016-04-08 15:18:00.510711678 +0200 *************** *** 1619,1624 **** --- 1619,1626 ---- EXTERN int alloc_fail_repeat INIT(= 0); EXTERN int disable_char_avail_for_testing INIT(= 0); + + EXTERN int in_free_unref_items INIT(= FALSE); #endif /* *** ../vim-7.4.1718/src/ops.c 2016-03-21 23:13:28.432710474 +0100 --- src/ops.c 2016-04-08 15:20:21.385237139 +0200 *************** *** 6391,6397 **** { if (list_append_string(list, NULL, -1) == FAIL) { ! list_free(list, TRUE); return NULL; } list->lv_first->li_tv.vval.v_string = s; --- 6391,6397 ---- { if (list_append_string(list, NULL, -1) == FAIL) { ! list_free(list); return NULL; } list->lv_first->li_tv.vval.v_string = s; *************** *** 6465,6471 **** error = TRUE; if (error) { ! list_free(list, TRUE); return NULL; } return (char_u *)list; --- 6465,6471 ---- error = TRUE; if (error) { ! list_free(list); return NULL; } return (char_u *)list; *** ../vim-7.4.1718/src/regexp.c 2016-04-03 14:00:29.320148959 +0200 --- src/regexp.c 2016-04-08 15:20:25.981189037 +0200 *************** *** 7910,7916 **** if (error) { ! list_free(list, TRUE); return NULL; } return list; --- 7910,7916 ---- if (error) { ! list_free(list); return NULL; } return list; *** ../vim-7.4.1718/src/tag.c 2016-03-12 22:11:34.243300238 +0100 --- src/tag.c 2016-04-08 15:20:36.077083375 +0200 *************** *** 792,798 **** vim_free(cmd); vim_free(fname); if (list != NULL) ! list_free(list, TRUE); goto end_do_tag; } --- 792,798 ---- vim_free(cmd); vim_free(fname); if (list != NULL) ! list_free(list); goto end_do_tag; } *************** *** 919,925 **** vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); set_errorlist(curwin, list, ' ', IObuff); ! list_free(list, TRUE); vim_free(fname); vim_free(cmd); --- 919,925 ---- vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); set_errorlist(curwin, list, ' ', IObuff); ! list_free(list); vim_free(fname); vim_free(cmd); *** ../vim-7.4.1718/src/proto/channel.pro 2016-04-07 21:40:34.066966030 +0200 --- src/proto/channel.pro 2016-04-08 16:08:10.363356443 +0200 *************** *** 5,11 **** void ch_logs(channel_T *ch, char *msg, char *name); channel_T *add_channel(void); int channel_unref(channel_T *channel); ! void channel_free(channel_T *channel); void channel_gui_register_all(void); channel_T *channel_open(char *hostname, int port_in, int waittime, void (*nb_close_cb)(void)); channel_T *channel_open_func(typval_T *argvars); --- 5,12 ---- void ch_logs(channel_T *ch, char *msg, char *name); channel_T *add_channel(void); int channel_unref(channel_T *channel); ! int free_unused_channels_contents(int copyID, int mask); ! void free_unused_channels(int copyID, int mask); void channel_gui_register_all(void); channel_T *channel_open(char *hostname, int port_in, int waittime, void (*nb_close_cb)(void)); channel_T *channel_open_func(typval_T *argvars); *************** *** 50,55 **** --- 51,58 ---- int get_job_options(typval_T *tv, jobopt_T *opt, int supported); channel_T *get_channel_arg(typval_T *tv, int check_open); void job_unref(job_T *job); + int free_unused_jobs_contents(int copyID, int mask); + void free_unused_jobs(int copyID, int mask); void job_set_options(job_T *job, jobopt_T *opt); void job_stop_on_exit(void); void job_check_ended(void); *** ../vim-7.4.1718/src/proto/eval.pro 2016-03-15 23:10:26.412712095 +0100 --- src/proto/eval.pro 2016-04-08 15:28:53.151882878 +0200 *************** *** 45,54 **** int do_unlet(char_u *name, int forceit); void del_menutrans_vars(void); char_u *get_user_var_name(expand_T *xp, int idx); list_T *list_alloc(void); int rettv_list_alloc(typval_T *rettv); void list_unref(list_T *l); ! void list_free(list_T *l, int recurse); listitem_T *listitem_alloc(void); void listitem_free(listitem_T *item); void listitem_remove(list_T *l, listitem_T *item); --- 45,56 ---- int do_unlet(char_u *name, int forceit); void del_menutrans_vars(void); char_u *get_user_var_name(expand_T *xp, int idx); + void partial_unref(partial_T *pt); list_T *list_alloc(void); int rettv_list_alloc(typval_T *rettv); void list_unref(list_T *l); ! void list_free_internal(list_T *l); ! void list_free(list_T *l); listitem_T *listitem_alloc(void); void listitem_free(listitem_T *item); void listitem_remove(list_T *l, listitem_T *item); *************** *** 71,77 **** dict_T *dict_alloc(void); int rettv_dict_alloc(typval_T *rettv); void dict_unref(dict_T *d); ! void dict_free(dict_T *d, int recurse); dictitem_T *dictitem_alloc(char_u *key); void dictitem_free(dictitem_T *item); int dict_add(dict_T *d, dictitem_T *item); --- 73,80 ---- dict_T *dict_alloc(void); int rettv_dict_alloc(typval_T *rettv); void dict_unref(dict_T *d); ! void dict_free_internal(dict_T *d); ! void dict_free(dict_T *d); dictitem_T *dictitem_alloc(char_u *key); void dictitem_free(dictitem_T *item); int dict_add(dict_T *d, dictitem_T *item); *************** *** 87,93 **** buf_T *buflist_find_by_name(char_u *name, int curtab_only); int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv); void dict_extend(dict_T *d1, dict_T *d2, char_u *action); - void partial_unref(partial_T *pt); void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv); float_T vim_round(float_T f); long do_searchpair(char_u *spat, char_u *mpat, char_u *epat, int dir, char_u *skip, int flags, pos_T *match_pos, linenr_T lnum_stop, long time_limit); --- 90,95 ---- *** ../vim-7.4.1718/src/testdir/test_partial.vim 2016-04-06 22:59:33.503226978 +0200 --- src/testdir/test_partial.vim 2016-04-08 16:44:37.140414326 +0200 *************** *** 221,227 **** endif endfunc ! " This causes double free on exit if EXITFREE is defined. func Test_cyclic_list_arg() let l = [] let Pt = function('string', [l]) --- 221,227 ---- endif endfunc ! " This caused double free on exit if EXITFREE is defined. func Test_cyclic_list_arg() let l = [] let Pt = function('string', [l]) *************** *** 230,236 **** unlet Pt endfunc ! " This causes double free on exit if EXITFREE is defined. func Test_cyclic_dict_arg() let d = {} let Pt = function('string', [d]) --- 230,236 ---- unlet Pt endfunc ! " This caused double free on exit if EXITFREE is defined. func Test_cyclic_dict_arg() let d = {} let Pt = function('string', [d]) *************** *** 238,240 **** --- 238,255 ---- unlet d unlet Pt endfunc + + func Ignored(job1, job2, status) + endfunc + + func Test_cycle_partial_job() + let job = job_start('echo') + call job_setoptions(job, {'exit_cb': function('Ignored', [job])}) + unlet job + endfunc + + func Test_ref_job_partial_dict() + let g:ref_job = job_start('echo') + let d = {'a': 'b'} + call job_setoptions(g:ref_job, {'exit_cb': function('string', [], d)}) + endfunc *** ../vim-7.4.1718/src/structs.h 2016-03-28 19:16:15.669846492 +0200 --- src/structs.h 2016-04-08 13:16:05.658512920 +0200 *************** *** 1290,1295 **** --- 1290,1297 ---- buf_T *jv_in_buf; /* buffer from "in-name" */ int jv_refcount; /* reference count */ + int jv_copyID; + channel_T *jv_channel; /* channel for I/O, reference counted */ }; *************** *** 1425,1435 **** job_T *ch_job; /* Job that uses this channel; this does not * count as a reference to avoid a circular ! * reference. */ int ch_job_killed; /* TRUE when there was a job and it was killed * or we know it died. */ int ch_refcount; /* reference count */ }; #define JO_MODE 0x0001 /* channel mode */ --- 1427,1438 ---- job_T *ch_job; /* Job that uses this channel; this does not * count as a reference to avoid a circular ! * reference, the job refers to the channel. */ int ch_job_killed; /* TRUE when there was a job and it was killed * or we know it died. */ int ch_refcount; /* reference count */ + int ch_copyID; }; #define JO_MODE 0x0001 /* channel mode */ *** ../vim-7.4.1718/src/version.c 2016-04-07 22:16:26.484303131 +0200 --- src/version.c 2016-04-08 16:10:03.498177031 +0200 *************** *** 750,751 **** --- 750,753 ---- { /* Add new patch number below this line */ + /**/ + 1719, /**/ -- hundred-and-one symptoms of being an internet addict: 246. You use up your free 1 Gbyte in two days. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///