summaryrefslogtreecommitdiff
path: root/src/userfunc.c
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2017-02-02 22:59:27 +0100
committerBram Moolenaar <Bram@vim.org>2017-02-02 22:59:27 +0100
commit03ff9bcbc968f7d306e4a4e334e226fdde62ca82 (patch)
tree984162dfb8d3ef4e8535a62a974067979d743836 /src/userfunc.c
parentfd8983b09c64d9bfa8a4bdc16d72c55fbb22b4dc (diff)
downloadvim-git-03ff9bcbc968f7d306e4a4e334e226fdde62ca82.tar.gz
patch 8.0.0297: double free on exit when using a closurev8.0.0297
Problem: Double free on exit when using a closure. (James McCoy) Solution: Split free_al_functions in two parts. (closes #1428)
Diffstat (limited to 'src/userfunc.c')
-rw-r--r--src/userfunc.c78
1 files changed, 66 insertions, 12 deletions
diff --git a/src/userfunc.c b/src/userfunc.c
index 516ab4707..1bf028fa4 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -1075,12 +1075,17 @@ func_remove(ufunc_T *fp)
}
/*
- * Free a function and remove it from the list of functions.
+ * Free all things that a function contains. Does not free the function
+ * itself, use func_free() for that.
* When "force" is TRUE we are exiting.
*/
static void
-func_free(ufunc_T *fp, int force)
+func_clear(ufunc_T *fp, int force)
{
+ if (fp->uf_cleared)
+ return;
+ fp->uf_cleared = TRUE;
+
/* clear this function */
ga_clear_strings(&(fp->uf_args));
ga_clear_strings(&(fp->uf_lines));
@@ -1089,17 +1094,36 @@ func_free(ufunc_T *fp, int force)
vim_free(fp->uf_tml_total);
vim_free(fp->uf_tml_self);
#endif
+ funccal_unref(fp->uf_scoped, fp, force);
+}
+
+/*
+ * Free a function and remove it from the list of functions. Does not free
+ * what a function contains, call func_clear() first.
+ */
+ static void
+func_free(ufunc_T *fp)
+{
/* only remove it when not done already, otherwise we would remove a newer
* version of the function */
if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0)
func_remove(fp);
- funccal_unref(fp->uf_scoped, fp, force);
-
vim_free(fp);
}
/*
+ * Free all things that a function contains and free the function itself.
+ * When "force" is TRUE we are exiting.
+ */
+ static void
+func_clear_free(ufunc_T *fp, int force)
+{
+ func_clear(fp, force);
+ func_free(fp);
+}
+
+/*
* There are two kinds of function names:
* 1. ordinary names, function defined with :function
* 2. numbered functions and lambdas
@@ -1120,10 +1144,40 @@ free_all_functions(void)
hashitem_T *hi;
ufunc_T *fp;
long_u skipped = 0;
- long_u todo;
+ long_u todo = 1;
+ long_u used;
+
+ /* First clear what the functions contain. Since this may lower the
+ * reference count of a function, it may also free a function and change
+ * the hash table. Restart if that happens. */
+ while (todo > 0)
+ {
+ todo = func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0; ++hi)
+ if (!HASHITEM_EMPTY(hi))
+ {
+ /* Only free functions that are not refcounted, those are
+ * supposed to be freed when no longer referenced. */
+ fp = HI2UF(hi);
+ if (func_name_refcount(fp->uf_name))
+ ++skipped;
+ else
+ {
+ used = func_hashtab.ht_used;
+ func_clear(fp, TRUE);
+ if (used != func_hashtab.ht_used)
+ {
+ skipped = 0;
+ break;
+ }
+ }
+ --todo;
+ }
+ }
- /* Need to start all over every time, because func_free() may change the
- * hash table. */
+ /* Now actually free the functions. Need to start all over every time,
+ * because func_free() may change the hash table. */
+ skipped = 0;
while (func_hashtab.ht_used > skipped)
{
todo = func_hashtab.ht_used;
@@ -1138,7 +1192,7 @@ free_all_functions(void)
++skipped;
else
{
- func_free(fp, TRUE);
+ func_free(fp);
skipped = 0;
break;
}
@@ -1356,7 +1410,7 @@ call_func(
if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0)
/* Function was unreferenced while being used, free it
* now. */
- func_free(fp, FALSE);
+ func_clear_free(fp, FALSE);
if (did_save_redo)
restoreRedobuff();
restore_search_patterns();
@@ -2756,7 +2810,7 @@ ex_delfunction(exarg_T *eap)
fp->uf_flags |= FC_DELETED;
}
else
- func_free(fp, FALSE);
+ func_clear_free(fp, FALSE);
}
}
}
@@ -2785,7 +2839,7 @@ func_unref(char_u *name)
/* Only delete it when it's not being used. Otherwise it's done
* when "uf_calls" becomes zero. */
if (fp->uf_calls == 0)
- func_free(fp, FALSE);
+ func_clear_free(fp, FALSE);
}
}
@@ -2801,7 +2855,7 @@ func_ptr_unref(ufunc_T *fp)
/* Only delete it when it's not being used. Otherwise it's done
* when "uf_calls" becomes zero. */
if (fp->uf_calls == 0)
- func_free(fp, FALSE);
+ func_clear_free(fp, FALSE);
}
}