forked from aniani/vim
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.
This commit is contained in:
140
src/channel.c
140
src/channel.c
@@ -367,6 +367,39 @@ channel_still_useful(channel_T *channel)
|
|||||||
|| (channel->ch_part[PART_ERR].ch_callback != NULL && has_err_msg);
|
|| (channel->ch_part[PART_ERR].ch_callback != NULL && has_err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
* 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
|
* possible, there is no callback to be invoked or the associated job was
|
||||||
@@ -397,22 +430,39 @@ channel_unref(channel_T *channel)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
int
|
||||||
* Close a channel and free all its resources.
|
free_unused_channels_contents(int copyID, int mask)
|
||||||
*/
|
|
||||||
void
|
|
||||||
channel_free(channel_T *channel)
|
|
||||||
{
|
{
|
||||||
channel_close(channel, TRUE);
|
int did_free = FALSE;
|
||||||
channel_clear(channel);
|
channel_T *ch;
|
||||||
ch_log(channel, "Freeing channel");
|
|
||||||
if (channel->ch_next != NULL)
|
for (ch = first_channel; ch != NULL; ch = ch->ch_next)
|
||||||
channel->ch_next->ch_prev = channel->ch_prev;
|
if ((ch->ch_copyID & mask) != (copyID & mask))
|
||||||
if (channel->ch_prev == NULL)
|
{
|
||||||
first_channel = channel->ch_next;
|
/* Free the channel and ordinary items it contains, but don't
|
||||||
else
|
* recurse into Lists, Dictionaries etc. */
|
||||||
channel->ch_prev->ch_next = channel->ch_next;
|
channel_free_contents(ch);
|
||||||
vim_free(channel);
|
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)
|
#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_SOCK);
|
||||||
channel_clear_one(channel, PART_OUT);
|
channel_clear_one(channel, PART_OUT);
|
||||||
channel_clear_one(channel, PART_ERR);
|
channel_clear_one(channel, PART_ERR);
|
||||||
|
/* there is no callback or queue for PART_IN */
|
||||||
vim_free(channel->ch_callback);
|
vim_free(channel->ch_callback);
|
||||||
channel->ch_callback = NULL;
|
channel->ch_callback = NULL;
|
||||||
partial_unref(channel->ch_partial);
|
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 job_T *first_job = NULL;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
job_free(job_T *job)
|
job_free_contents(job_T *job)
|
||||||
{
|
{
|
||||||
ch_log(job->jv_channel, "Freeing job");
|
ch_log(job->jv_channel, "Freeing job");
|
||||||
if (job->jv_channel != NULL)
|
if (job->jv_channel != NULL)
|
||||||
@@ -3928,19 +3979,33 @@ job_free(job_T *job)
|
|||||||
}
|
}
|
||||||
mch_clear_job(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)
|
if (job->jv_next != NULL)
|
||||||
job->jv_next->jv_prev = job->jv_prev;
|
job->jv_next->jv_prev = job->jv_prev;
|
||||||
if (job->jv_prev == NULL)
|
if (job->jv_prev == NULL)
|
||||||
first_job = job->jv_next;
|
first_job = job->jv_next;
|
||||||
else
|
else
|
||||||
job->jv_prev->jv_next = job->jv_next;
|
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);
|
vim_free(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
job_free(job_T *job)
|
||||||
|
{
|
||||||
|
if (!in_free_unref_items)
|
||||||
|
{
|
||||||
|
job_free_contents(job);
|
||||||
|
job_free_job(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
job_unref(job_T *job)
|
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.
|
* Allocate a job. Sets the refcount to one and sets options default.
|
||||||
*/
|
*/
|
||||||
|
|||||||
319
src/eval.c
319
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_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_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 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 long list_len(list_T *l);
|
||||||
static int list_equal(list_T *l1, list_T *l2, int ic, int recursive);
|
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);
|
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 void emsg_funcname(char *ermsg, char_u *name);
|
||||||
static int non_zero_arg(typval_T *argvars);
|
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
|
#ifdef FEAT_FLOAT
|
||||||
static void f_abs(typval_T *argvars, typval_T *rettv);
|
static void f_abs(typval_T *argvars, typval_T *rettv);
|
||||||
static void f_acos(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)
|
if (list_append_tv(l, &item->li_tv) == FAIL)
|
||||||
{
|
{
|
||||||
list_free(l, TRUE);
|
list_free(l);
|
||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
item = item->li_next;
|
item = item->li_next;
|
||||||
@@ -5930,19 +5935,13 @@ get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
partial_free(partial_T *pt, int recursive)
|
partial_free(partial_T *pt)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < pt->pt_argc; ++i)
|
for (i = 0; i < pt->pt_argc; ++i)
|
||||||
{
|
clear_tv(&pt->pt_argv[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);
|
vim_free(pt->pt_argv);
|
||||||
if (recursive)
|
|
||||||
dict_unref(pt->pt_dict);
|
dict_unref(pt->pt_dict);
|
||||||
func_unref(pt->pt_name);
|
func_unref(pt->pt_name);
|
||||||
vim_free(pt->pt_name);
|
vim_free(pt->pt_name);
|
||||||
@@ -5957,27 +5956,7 @@ partial_free(partial_T *pt, int recursive)
|
|||||||
partial_unref(partial_T *pt)
|
partial_unref(partial_T *pt)
|
||||||
{
|
{
|
||||||
if (pt != NULL && --pt->pt_refcount <= 0)
|
if (pt != NULL && --pt->pt_refcount <= 0)
|
||||||
partial_free(pt, TRUE);
|
partial_free(pt);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -6031,7 +6010,7 @@ get_list_tv(char_u **arg, typval_T *rettv, int evaluate)
|
|||||||
EMSG2(_("E697: Missing end of List ']': %s"), *arg);
|
EMSG2(_("E697: Missing end of List ']': %s"), *arg);
|
||||||
failret:
|
failret:
|
||||||
if (evaluate)
|
if (evaluate)
|
||||||
list_free(l, TRUE);
|
list_free(l);
|
||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6095,20 +6074,30 @@ rettv_list_alloc(typval_T *rettv)
|
|||||||
list_unref(list_T *l)
|
list_unref(list_T *l)
|
||||||
{
|
{
|
||||||
if (l != NULL && --l->lv_refcount <= 0)
|
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.
|
* Free a list, including all non-container items it points to.
|
||||||
* Ignores the reference count.
|
* Ignores the reference count.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
list_free(
|
list_free_contents(list_T *l)
|
||||||
list_T *l,
|
|
||||||
int recurse) /* Free Lists and Dictionaries recursively. */
|
|
||||||
{
|
{
|
||||||
listitem_T *item;
|
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. */
|
/* Remove the list from the list of lists for garbage collection. */
|
||||||
if (l->lv_used_prev == NULL)
|
if (l->lv_used_prev == NULL)
|
||||||
first_list = l->lv_used_next;
|
first_list = l->lv_used_next;
|
||||||
@@ -6117,19 +6106,19 @@ list_free(
|
|||||||
if (l->lv_used_next != NULL)
|
if (l->lv_used_next != NULL)
|
||||||
l->lv_used_next->lv_used_prev = l->lv_used_prev;
|
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);
|
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.
|
* Allocate a list item.
|
||||||
* It is not initialized, don't forget to set v_lock.
|
* It is not initialized, don't forget to set v_lock.
|
||||||
@@ -7016,7 +7005,7 @@ garbage_collect(void)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef FEAT_JOB_CHANNEL
|
#ifdef FEAT_JOB_CHANNEL
|
||||||
abort = abort || set_ref_in_channel(copyID);
|
// abort = abort || set_ref_in_channel(copyID);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!abort)
|
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
|
static int
|
||||||
free_unref_items(int copyID)
|
free_unref_items(int copyID)
|
||||||
@@ -7065,29 +7054,66 @@ free_unref_items(int copyID)
|
|||||||
list_T *ll, *ll_next;
|
list_T *ll, *ll_next;
|
||||||
int did_free = FALSE;
|
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.
|
* Go through the list of dicts and free items without the copyID.
|
||||||
*/
|
*/
|
||||||
for (dd = first_dict; dd != NULL; )
|
for (dd = first_dict; dd != NULL; dd = dd->dv_used_next)
|
||||||
{
|
|
||||||
dd_next = dd->dv_used_next;
|
|
||||||
if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK))
|
if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK))
|
||||||
{
|
{
|
||||||
/* Free the Dictionary and ordinary items it contains, but don't
|
/* Free the Dictionary and ordinary items it contains, but don't
|
||||||
* recurse into Lists and Dictionaries, they will be in the list
|
* recurse into Lists and Dictionaries, they will be in the list
|
||||||
* of dicts or list of lists. */
|
* of dicts or list of lists. */
|
||||||
dict_free(dd, FALSE);
|
dict_free_contents(dd);
|
||||||
did_free = TRUE;
|
did_free = TRUE;
|
||||||
}
|
}
|
||||||
dd = dd_next;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Go through the list of lists and free items without the copyID.
|
* 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
|
* But don't free a list that has a watcher (used in a for loop), these
|
||||||
* are not referenced anywhere.
|
* 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;
|
ll_next = ll->lv_used_next;
|
||||||
if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)
|
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
|
/* Free the List and ordinary items it contains, but don't recurse
|
||||||
* into Lists and Dictionaries, they will be in the list of dicts
|
* into Lists and Dictionaries, they will be in the list of dicts
|
||||||
* or list of lists. */
|
* or list of lists. */
|
||||||
list_free(ll, FALSE);
|
list_free_list(ll);
|
||||||
did_free = TRUE;
|
|
||||||
}
|
}
|
||||||
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;
|
return did_free;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7204,18 +7240,12 @@ set_ref_in_item(
|
|||||||
ht_stack_T **ht_stack,
|
ht_stack_T **ht_stack,
|
||||||
list_stack_T **list_stack)
|
list_stack_T **list_stack)
|
||||||
{
|
{
|
||||||
dict_T *dd;
|
|
||||||
list_T *ll;
|
|
||||||
int abort = FALSE;
|
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)
|
dict_T *dd = tv->vval.v_dict;
|
||||||
dd = tv->vval.v_partial->pt_dict;
|
|
||||||
else
|
|
||||||
dd = NULL;
|
|
||||||
if (dd != NULL && dd->dv_copyID != copyID)
|
if (dd != NULL && dd->dv_copyID != copyID)
|
||||||
{
|
{
|
||||||
/* Didn't see this dict yet. */
|
/* 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)
|
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)
|
if (ll != NULL && ll->lv_copyID != copyID)
|
||||||
{
|
{
|
||||||
/* Didn't see this list yet. */
|
/* 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;
|
return abort;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7332,30 +7443,20 @@ rettv_dict_alloc(typval_T *rettv)
|
|||||||
dict_unref(dict_T *d)
|
dict_unref(dict_T *d)
|
||||||
{
|
{
|
||||||
if (d != NULL && --d->dv_refcount <= 0)
|
if (d != NULL && --d->dv_refcount <= 0)
|
||||||
dict_free(d, TRUE);
|
dict_free(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Free a Dictionary, including all non-container items it contains.
|
* Free a Dictionary, including all non-container items it contains.
|
||||||
* Ignores the reference count.
|
* Ignores the reference count.
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
dict_free(
|
dict_free_contents(dict_T *d)
|
||||||
dict_T *d,
|
|
||||||
int recurse) /* Free Lists and Dictionaries recursively. */
|
|
||||||
{
|
{
|
||||||
int todo;
|
int todo;
|
||||||
hashitem_T *hi;
|
hashitem_T *hi;
|
||||||
dictitem_T *di;
|
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. */
|
/* Lock the hashtab, we don't want it to resize while freeing items. */
|
||||||
hash_lock(&d->dv_hashtab);
|
hash_lock(&d->dv_hashtab);
|
||||||
todo = (int)d->dv_hashtab.ht_used;
|
todo = (int)d->dv_hashtab.ht_used;
|
||||||
@@ -7367,18 +7468,37 @@ dict_free(
|
|||||||
* something recursive causing trouble. */
|
* something recursive causing trouble. */
|
||||||
di = HI2DI(hi);
|
di = HI2DI(hi);
|
||||||
hash_remove(&d->dv_hashtab, hi);
|
hash_remove(&d->dv_hashtab, hi);
|
||||||
if (recurse)
|
|
||||||
clear_tv(&di->di_tv);
|
clear_tv(&di->di_tv);
|
||||||
else
|
|
||||||
clear_tv_no_recurse(&di->di_tv);
|
|
||||||
vim_free(di);
|
vim_free(di);
|
||||||
--todo;
|
--todo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hash_clear(&d->dv_hashtab);
|
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);
|
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.
|
* Allocate a Dictionary item.
|
||||||
* The "key" is copied to the new 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);
|
EMSG2(_("E723: Missing end of Dictionary '}': %s"), *arg);
|
||||||
failret:
|
failret:
|
||||||
if (evaluate)
|
if (evaluate)
|
||||||
dict_free(d, TRUE);
|
dict_free(d);
|
||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7842,9 +7962,6 @@ failret:
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(FEAT_JOB_CHANNEL) || defined(PROTO)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
get_var_special_name(int nr)
|
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,
|
|| list_append_number(rettv->vval.v_list,
|
||||||
(varnumber_T)-1) == FAIL))
|
(varnumber_T)-1) == FAIL))
|
||||||
{
|
{
|
||||||
list_free(rettv->vval.v_list, TRUE);
|
list_free(rettv->vval.v_list);
|
||||||
rettv->vval.v_list = NULL;
|
rettv->vval.v_list = NULL;
|
||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
@@ -16488,7 +16605,7 @@ f_readfile(typval_T *argvars, typval_T *rettv)
|
|||||||
|
|
||||||
if (failed)
|
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 */
|
/* readfile doc says an empty list is returned on error */
|
||||||
rettv->vval.v_list = list_alloc();
|
rettv->vval.v_list = list_alloc();
|
||||||
}
|
}
|
||||||
@@ -20070,7 +20187,7 @@ errret:
|
|||||||
if (res != NULL)
|
if (res != NULL)
|
||||||
vim_free(res);
|
vim_free(res);
|
||||||
if (list != NULL)
|
if (list != NULL)
|
||||||
list_free(list, TRUE);
|
list_free(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1619,6 +1619,8 @@ EXTERN int alloc_fail_countdown INIT(= -1);
|
|||||||
EXTERN int alloc_fail_repeat INIT(= 0);
|
EXTERN int alloc_fail_repeat INIT(= 0);
|
||||||
|
|
||||||
EXTERN int disable_char_avail_for_testing INIT(= 0);
|
EXTERN int disable_char_avail_for_testing INIT(= 0);
|
||||||
|
|
||||||
|
EXTERN int in_free_unref_items INIT(= FALSE);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -6391,7 +6391,7 @@ getreg_wrap_one_line(char_u *s, int flags)
|
|||||||
{
|
{
|
||||||
if (list_append_string(list, NULL, -1) == FAIL)
|
if (list_append_string(list, NULL, -1) == FAIL)
|
||||||
{
|
{
|
||||||
list_free(list, TRUE);
|
list_free(list);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
list->lv_first->li_tv.vval.v_string = s;
|
list->lv_first->li_tv.vval.v_string = s;
|
||||||
@@ -6465,7 +6465,7 @@ get_reg_contents(int regname, int flags)
|
|||||||
error = TRUE;
|
error = TRUE;
|
||||||
if (error)
|
if (error)
|
||||||
{
|
{
|
||||||
list_free(list, TRUE);
|
list_free(list);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return (char_u *)list;
|
return (char_u *)list;
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ void ch_log(channel_T *ch, char *msg);
|
|||||||
void ch_logs(channel_T *ch, char *msg, char *name);
|
void ch_logs(channel_T *ch, char *msg, char *name);
|
||||||
channel_T *add_channel(void);
|
channel_T *add_channel(void);
|
||||||
int channel_unref(channel_T *channel);
|
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);
|
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(char *hostname, int port_in, int waittime, void (*nb_close_cb)(void));
|
||||||
channel_T *channel_open_func(typval_T *argvars);
|
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);
|
int get_job_options(typval_T *tv, jobopt_T *opt, int supported);
|
||||||
channel_T *get_channel_arg(typval_T *tv, int check_open);
|
channel_T *get_channel_arg(typval_T *tv, int check_open);
|
||||||
void job_unref(job_T *job);
|
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_set_options(job_T *job, jobopt_T *opt);
|
||||||
void job_stop_on_exit(void);
|
void job_stop_on_exit(void);
|
||||||
void job_check_ended(void);
|
void job_check_ended(void);
|
||||||
|
|||||||
@@ -45,10 +45,12 @@ void ex_lockvar(exarg_T *eap);
|
|||||||
int do_unlet(char_u *name, int forceit);
|
int do_unlet(char_u *name, int forceit);
|
||||||
void del_menutrans_vars(void);
|
void del_menutrans_vars(void);
|
||||||
char_u *get_user_var_name(expand_T *xp, int idx);
|
char_u *get_user_var_name(expand_T *xp, int idx);
|
||||||
|
void partial_unref(partial_T *pt);
|
||||||
list_T *list_alloc(void);
|
list_T *list_alloc(void);
|
||||||
int rettv_list_alloc(typval_T *rettv);
|
int rettv_list_alloc(typval_T *rettv);
|
||||||
void list_unref(list_T *l);
|
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);
|
listitem_T *listitem_alloc(void);
|
||||||
void listitem_free(listitem_T *item);
|
void listitem_free(listitem_T *item);
|
||||||
void listitem_remove(list_T *l, 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);
|
dict_T *dict_alloc(void);
|
||||||
int rettv_dict_alloc(typval_T *rettv);
|
int rettv_dict_alloc(typval_T *rettv);
|
||||||
void dict_unref(dict_T *d);
|
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);
|
dictitem_T *dictitem_alloc(char_u *key);
|
||||||
void dictitem_free(dictitem_T *item);
|
void dictitem_free(dictitem_T *item);
|
||||||
int dict_add(dict_T *d, 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);
|
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);
|
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 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);
|
void mzscheme_call_vim(char_u *name, typval_T *args, typval_T *rettv);
|
||||||
float_T vim_round(float_T f);
|
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);
|
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);
|
||||||
|
|||||||
@@ -7910,7 +7910,7 @@ reg_submatch_list(int no)
|
|||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
{
|
{
|
||||||
list_free(list, TRUE);
|
list_free(list);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
|
|||||||
@@ -1290,6 +1290,8 @@ struct jobvar_S
|
|||||||
buf_T *jv_in_buf; /* buffer from "in-name" */
|
buf_T *jv_in_buf; /* buffer from "in-name" */
|
||||||
|
|
||||||
int jv_refcount; /* reference count */
|
int jv_refcount; /* reference count */
|
||||||
|
int jv_copyID;
|
||||||
|
|
||||||
channel_T *jv_channel; /* channel for I/O, reference counted */
|
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
|
job_T *ch_job; /* Job that uses this channel; this does not
|
||||||
* count as a reference to avoid a circular
|
* 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
|
int ch_job_killed; /* TRUE when there was a job and it was killed
|
||||||
* or we know it died. */
|
* or we know it died. */
|
||||||
|
|
||||||
int ch_refcount; /* reference count */
|
int ch_refcount; /* reference count */
|
||||||
|
int ch_copyID;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define JO_MODE 0x0001 /* channel mode */
|
#define JO_MODE 0x0001 /* channel mode */
|
||||||
|
|||||||
@@ -792,7 +792,7 @@ do_tag(
|
|||||||
vim_free(cmd);
|
vim_free(cmd);
|
||||||
vim_free(fname);
|
vim_free(fname);
|
||||||
if (list != NULL)
|
if (list != NULL)
|
||||||
list_free(list, TRUE);
|
list_free(list);
|
||||||
goto end_do_tag;
|
goto end_do_tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -919,7 +919,7 @@ do_tag(
|
|||||||
vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag);
|
vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag);
|
||||||
set_errorlist(curwin, list, ' ', IObuff);
|
set_errorlist(curwin, list, ' ', IObuff);
|
||||||
|
|
||||||
list_free(list, TRUE);
|
list_free(list);
|
||||||
vim_free(fname);
|
vim_free(fname);
|
||||||
vim_free(cmd);
|
vim_free(cmd);
|
||||||
|
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ func Test_bind_in_python()
|
|||||||
endif
|
endif
|
||||||
endfunc
|
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()
|
func Test_cyclic_list_arg()
|
||||||
let l = []
|
let l = []
|
||||||
let Pt = function('string', [l])
|
let Pt = function('string', [l])
|
||||||
@@ -230,7 +230,7 @@ func Test_cyclic_list_arg()
|
|||||||
unlet Pt
|
unlet Pt
|
||||||
endfunc
|
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()
|
func Test_cyclic_dict_arg()
|
||||||
let d = {}
|
let d = {}
|
||||||
let Pt = function('string', [d])
|
let Pt = function('string', [d])
|
||||||
@@ -238,3 +238,18 @@ func Test_cyclic_dict_arg()
|
|||||||
unlet d
|
unlet d
|
||||||
unlet Pt
|
unlet Pt
|
||||||
endfunc
|
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
|
||||||
|
|||||||
@@ -748,6 +748,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
1719,
|
||||||
/**/
|
/**/
|
||||||
1718,
|
1718,
|
||||||
/**/
|
/**/
|
||||||
|
|||||||
Reference in New Issue
Block a user