aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/eval.c
diff options
context:
space:
mode:
authorMichael Ennen <mike.ennen@gmail.com>2017-02-02 16:09:09 -0700
committerMichael Ennen <mike.ennen@gmail.com>2017-02-14 17:38:19 -0700
commit10c9ecc2117a69d2b83e983082f53c1779547035 (patch)
tree87684d2e8d0cffecd483e389a479ffa809c20289 /src/nvim/eval.c
parentef8701610baa18ecf2568990eab4ecf02ca8f6c1 (diff)
downloadrneovim-10c9ecc2117a69d2b83e983082f53c1779547035.tar.gz
rneovim-10c9ecc2117a69d2b83e983082f53c1779547035.tar.bz2
rneovim-10c9ecc2117a69d2b83e983082f53c1779547035.zip
vim-patch:8.0.0297
Problem: Double free on exit when using a closure. (James McCoy) Solution: Split free_al_functions in two parts. (closes #1428) https://github.com/vim/vim/commit/03ff9bcbc968f7d306e4a4e334e226fdde62ca82
Diffstat (limited to 'src/nvim/eval.c')
-rw-r--r--src/nvim/eval.c71
1 files changed, 60 insertions, 11 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index ac0e25ff2c..49ce9f3144 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -21776,10 +21776,37 @@ void free_all_functions(void)
hashitem_T *hi;
ufunc_T *fp;
uint64_t skipped = 0;
- uint64_t todo;
+ uint64_t todo = 1;
+ uint64_t used;
- // Need to start all over every time, because func_free() may change the
- // hash table.
+ // 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--;
+ }
+ }
+ }
+
+ // 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;
for (hi = func_hashtab.ht_array; todo > 0; hi++) {
@@ -21791,7 +21818,7 @@ void free_all_functions(void)
if (func_name_refcount(fp->uf_name)) {
skipped++;
} else {
- func_free(fp, true);
+ func_free(fp);
skipped = 0;
break;
}
@@ -22219,7 +22246,7 @@ void ex_delfunction(exarg_T *eap)
}
fp->uf_flags |= FC_DELETED;
} else {
- func_free(fp, false);
+ func_clear_free(fp, false);
}
}
}
@@ -22241,27 +22268,49 @@ static bool func_remove(ufunc_T *fp)
return false;
}
-/// 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.
///
/// param[in] force When true, we are exiting.
-static void func_free(ufunc_T *fp, bool force)
+static void func_clear(ufunc_T *fp, bool 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));
xfree(fp->uf_tml_count);
xfree(fp->uf_tml_total);
xfree(fp->uf_tml_self);
+ 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.
+///
+/// param[in] fp The function to free.
+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);
xfree(fp);
}
+/// Free all things that a function contains and free the function itself.
+///
+/// param[in] force When true, we are exiting.
+static void func_clear_free(ufunc_T *fp, bool force)
+{
+ func_clear(fp, force);
+ func_free(fp);
+}
+
/*
* Unreference a Function: decrement the reference count and free it when it
* becomes zero.
@@ -22290,7 +22339,7 @@ void 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);
}
}
}
@@ -22303,7 +22352,7 @@ void 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);
}
}
}
@@ -22714,7 +22763,7 @@ call_user_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);
}
// restore search patterns and redo buffer
if (did_save_redo) {