summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2016-04-08 17:07:19 +0200
committerBram Moolenaar <Bram@vim.org>2016-04-08 17:07:19 +0200
commit107e1eef1df3b786ad3ad49fbdb9e058649303b5 (patch)
tree1b645e4de0cc6a8021a5c48ca97897edf7e48adf
parentd56374e25df0b317b01423a01f158157faa647fa (diff)
downloadvim-git-107e1eef1df3b786ad3ad49fbdb9e058649303b5.tar.gz
patch 7.4.1719v7.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.
-rw-r--r--src/channel.c138
-rw-r--r--src/eval.c321
-rw-r--r--src/globals.h2
-rw-r--r--src/ops.c4
-rw-r--r--src/proto/channel.pro5
-rw-r--r--src/proto/eval.pro8
-rw-r--r--src/regexp.c2
-rw-r--r--src/structs.h5
-rw-r--r--src/tag.c4
-rw-r--r--src/testdir/test_partial.vim19
-rw-r--r--src/version.c2
11 files changed, 377 insertions, 133 deletions
diff --git a/src/channel.c b/src/channel.c
index 7b811dbce..83d057dbb 100644
--- 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.
- */
+ 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
-channel_free(channel_T *channel)
+free_unused_channels(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);
+ 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_open)
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
index 3dc3e210d..1c5a31658 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -430,6 +430,8 @@ static int get_option_tv(char_u **arg, typval_T *rettv, int evaluate);
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 len, typval_T *rettv, char_u **arg, lin
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 *rettv, int evaluate)
}
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 recursive)
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 *rettv, int evaluate)
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,17 +6106,17 @@ 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)
+ vim_free(l);
+}
+
+ void
+list_free(list_T *l)
+{
+ if (!in_free_unref_items)
{
- /* 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);
+ list_free_contents(l);
+ list_free_list(l);
}
- vim_free(l);
}
/*
@@ -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,12 +7122,22 @@ 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;
+ list_free_list(ll);
}
- ll = ll_next;
}
+#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)
{
- 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;
+ 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 *rettv, int evaluate)
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, typval_T *rettv, int type)
|| 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 *rettv)
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
index 05dec4a0e..3278ad977 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -1619,6 +1619,8 @@ EXTERN int alloc_fail_countdown INIT(= -1);
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
index b1910d95d..0584877eb 100644
--- a/src/ops.c
+++ b/src/ops.c
@@ -6391,7 +6391,7 @@ getreg_wrap_one_line(char_u *s, int flags)
{
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
index e4ef00309..d5114cba4 100644
--- 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
index 1de7a6d9d..38392b9bd 100644
--- 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 copyID, ht_stack_T **ht_stack, list_stack_
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, typval_T *rettv, int argcount_in, typva
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
index b20e9c5ec..fada9fe95 100644
--- 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
index e753860b8..adee3e807 100644
--- 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
index 154125950..4224c6fc2 100644
--- 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
index 2d53e8207..404bb748d 100644
--- 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
index a808bb727..dd50f983d 100644
--- 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,