Skip to content

Commit 5f436fc

Browse files
committed
patch 7.4.1639
Problem: Invoking garbage collection may cause a double free. Solution: Don't free the dict in a partial when recursive is FALSE.
1 parent e4eb6ff commit 5f436fc

2 files changed

Lines changed: 50 additions & 29 deletions

File tree

src/eval.c

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,9 @@ static hashtab_T func_hashtab;
209209
/* The names of packages that once were loaded are remembered. */
210210
static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL};
211211

212-
/* list heads for garbage collection */
212+
/* List heads for garbage collection. Although there can be a reference loop
213+
* from partial to dict to partial, we don't need to keep track of the partial,
214+
* since it will get freed when the dict is unused and gets freed. */
213215
static dict_T *first_dict = NULL; /* list of all dicts */
214216
static list_T *first_list = NULL; /* list of all lists */
215217

@@ -7130,9 +7132,14 @@ set_ref_in_item(
71307132
list_T *ll;
71317133
int abort = FALSE;
71327134

7133-
if (tv->v_type == VAR_DICT)
7135+
if (tv->v_type == VAR_DICT || tv->v_type == VAR_PARTIAL)
71347136
{
7135-
dd = tv->vval.v_dict;
7137+
if (tv->v_type == VAR_DICT)
7138+
dd = tv->vval.v_dict;
7139+
else if (tv->vval.v_partial != NULL)
7140+
dd = tv->vval.v_partial->pt_dict;
7141+
else
7142+
dd = NULL;
71367143
if (dd != NULL && dd->dv_copyID != copyID)
71377144
{
71387145
/* Didn't see this dict yet. */
@@ -7184,6 +7191,32 @@ set_ref_in_item(
71847191
return abort;
71857192
}
71867193

7194+
static void
7195+
partial_free(partial_T *pt, int free_dict)
7196+
{
7197+
int i;
7198+
7199+
for (i = 0; i < pt->pt_argc; ++i)
7200+
clear_tv(&pt->pt_argv[i]);
7201+
vim_free(pt->pt_argv);
7202+
if (free_dict)
7203+
dict_unref(pt->pt_dict);
7204+
func_unref(pt->pt_name);
7205+
vim_free(pt->pt_name);
7206+
vim_free(pt);
7207+
}
7208+
7209+
/*
7210+
* Unreference a closure: decrement the reference count and free it when it
7211+
* becomes zero.
7212+
*/
7213+
void
7214+
partial_unref(partial_T *pt)
7215+
{
7216+
if (pt != NULL && --pt->pt_refcount <= 0)
7217+
partial_free(pt, TRUE);
7218+
}
7219+
71877220
/*
71887221
* Allocate an empty header for a dictionary.
71897222
*/
@@ -7275,7 +7308,18 @@ dict_free(
72757308
hash_remove(&d->dv_hashtab, hi);
72767309
if (recurse || (di->di_tv.v_type != VAR_LIST
72777310
&& di->di_tv.v_type != VAR_DICT))
7278-
clear_tv(&di->di_tv);
7311+
{
7312+
if (!recurse && di->di_tv.v_type == VAR_PARTIAL)
7313+
{
7314+
partial_T *pt = di->di_tv.vval.v_partial;
7315+
7316+
/* We unref the partial but not the dict it refers to. */
7317+
if (pt != NULL && --pt->pt_refcount == 0)
7318+
partial_free(pt, FALSE);
7319+
}
7320+
else
7321+
clear_tv(&di->di_tv);
7322+
}
72797323
vim_free(di);
72807324
--todo;
72817325
}
@@ -12011,31 +12055,6 @@ f_function(typval_T *argvars, typval_T *rettv)
1201112055
}
1201212056
}
1201312057

12014-
static void
12015-
partial_free(partial_T *pt)
12016-
{
12017-
int i;
12018-
12019-
for (i = 0; i < pt->pt_argc; ++i)
12020-
clear_tv(&pt->pt_argv[i]);
12021-
vim_free(pt->pt_argv);
12022-
dict_unref(pt->pt_dict);
12023-
func_unref(pt->pt_name);
12024-
vim_free(pt->pt_name);
12025-
vim_free(pt);
12026-
}
12027-
12028-
/*
12029-
* Unreference a closure: decrement the reference count and free it when it
12030-
* becomes zero.
12031-
*/
12032-
void
12033-
partial_unref(partial_T *pt)
12034-
{
12035-
if (pt != NULL && --pt->pt_refcount <= 0)
12036-
partial_free(pt);
12037-
}
12038-
1203912058
/*
1204012059
* "garbagecollect()" function
1204112060
*/

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,8 @@ static char *(features[]) =
748748

749749
static int included_patches[] =
750750
{ /* Add new patch number below this line */
751+
/**/
752+
1639,
751753
/**/
752754
1638,
753755
/**/

0 commit comments

Comments
 (0)