# HG changeset patch # User Christian Brabandt # Date 1460128506 -7200 # Node ID e1b84109506ab265c7e8b8dacbab252bb7337ae1 # Parent e759159b44326f9decc41d4c45646ec7b4140bac commit https://github.com/vim/vim/commit/107e1eef1df3b786ad3ad49fbdb9e058649303b5 Author: Bram Moolenaar 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. diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -368,6 +368,39 @@ channel_still_useful(channel_T *channel) } /* + * 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,22 +430,39 @@ channel_unref(channel_T *channel) return FALSE; } -/* - * Close a channel and free all its resources. - */ - void -channel_free(channel_T *channel) + int +free_unused_channels_contents(int copyID, int mask) { - 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); + 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,6 +2507,7 @@ channel_clear(channel_T *channel) 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,7 +3964,7 @@ get_channel_arg(typval_T *tv, int check_ static job_T *first_job = NULL; static void -job_free(job_T *job) +job_free_contents(job_T *job) { ch_log(job->jv_channel, "Freeing job"); if (job->jv_channel != NULL) @@ -3928,19 +3979,33 @@ job_free(job_T *job) } 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->jv_stoponexit); - vim_free(job->jv_exit_cb); - partial_unref(job->jv_exit_partial); 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) { @@ -3964,6 +4029,41 @@ job_unref(job_T *job) } } + 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. */ diff --git a/src/eval.c b/src/eval.c --- 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); } /* diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -1619,6 +1619,8 @@ EXTERN int alloc_fail_countdown INIT(= 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 /* diff --git a/src/ops.c b/src/ops.c --- a/src/ops.c +++ b/src/ops.c @@ -6391,7 +6391,7 @@ getreg_wrap_one_line(char_u *s, int flag { if (list_append_string(list, NULL, -1) == FAIL) { - list_free(list, TRUE); + list_free(list); return NULL; } list->lv_first->li_tv.vval.v_string = s; @@ -6465,7 +6465,7 @@ get_reg_contents(int regname, int flags) error = TRUE; if (error) { - list_free(list, TRUE); + list_free(list); return NULL; } return (char_u *)list; diff --git a/src/proto/channel.pro b/src/proto/channel.pro --- a/src/proto/channel.pro +++ b/src/proto/channel.pro @@ -5,7 +5,8 @@ void ch_log(channel_T *ch, char *msg); 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); +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,6 +51,8 @@ void free_job_options(jobopt_T *opt); 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); diff --git a/src/proto/eval.pro b/src/proto/eval.pro --- a/src/proto/eval.pro +++ b/src/proto/eval.pro @@ -45,10 +45,12 @@ void ex_lockvar(exarg_T *eap); 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(list_T *l, int recurse); +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,7 +73,8 @@ int set_ref_in_item(typval_T *tv, int co 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); +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,7 +90,6 @@ int call_func(char_u *funcname, int len, 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); diff --git a/src/regexp.c b/src/regexp.c --- a/src/regexp.c +++ b/src/regexp.c @@ -7910,7 +7910,7 @@ reg_submatch_list(int no) if (error) { - list_free(list, TRUE); + list_free(list); return NULL; } return list; diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1290,6 +1290,8 @@ struct jobvar_S 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,11 +1427,12 @@ struct channel_S { job_T *ch_job; /* Job that uses this channel; this does not * count as a reference to avoid a circular - * reference. */ + * 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 */ diff --git a/src/tag.c b/src/tag.c --- a/src/tag.c +++ b/src/tag.c @@ -792,7 +792,7 @@ do_tag( vim_free(cmd); vim_free(fname); if (list != NULL) - list_free(list, TRUE); + list_free(list); goto end_do_tag; } @@ -919,7 +919,7 @@ do_tag( vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); set_errorlist(curwin, list, ' ', IObuff); - list_free(list, TRUE); + list_free(list); vim_free(fname); vim_free(cmd); diff --git a/src/testdir/test_partial.vim b/src/testdir/test_partial.vim --- a/src/testdir/test_partial.vim +++ b/src/testdir/test_partial.vim @@ -221,7 +221,7 @@ func Test_bind_in_python() endif endfunc -" This causes double free on exit if EXITFREE is defined. +" This caused double free on exit if EXITFREE is defined. func Test_cyclic_list_arg() let l = [] let Pt = function('string', [l]) @@ -230,7 +230,7 @@ func Test_cyclic_list_arg() unlet Pt endfunc -" This causes double free on exit if EXITFREE is defined. +" This caused double free on exit if EXITFREE is defined. func Test_cyclic_dict_arg() let d = {} let Pt = function('string', [d]) @@ -238,3 +238,18 @@ func Test_cyclic_dict_arg() 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 diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -749,6 +749,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1719, +/**/ 1718, /**/ 1717,