# HG changeset patch # User Christian Brabandt # Date 1459976405 -7200 # Node ID b76195a1e38ec4be55599a5591dc5531bfa51648 # Parent 183f05a25a5b49ed57320ed01fc80dec16682feb commit https://github.com/vim/vim/commit/ddecc25947dbdd689d5bcaed32f298a08abdd497 Author: Bram Moolenaar Date: Wed Apr 6 22:59:37 2016 +0200 patch 7.4.1715 Problem: Double free when a partial is in a cycle with a list or dict. (Nikolai Pavlov) Solution: Do not free a nested list or dict used by the partial. diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -5929,6 +5929,57 @@ get_lit_string_tv(char_u **arg, typval_T return OK; } + static void +partial_free(partial_T *pt, int recursive) +{ + 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); + } + vim_free(pt->pt_argv); + if (recursive) + dict_unref(pt->pt_dict); + func_unref(pt->pt_name); + vim_free(pt->pt_name); + vim_free(pt); +} + +/* + * Unreference a closure: decrement the reference count and free it when it + * becomes zero. + */ + void +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); +} + /* * Allocate a variable for a List and fill it from "*arg". * Return OK or FAIL. @@ -6070,9 +6121,10 @@ list_free( { /* Remove the item before deleting it. */ l->lv_first = item->li_next; - if (recurse || (item->li_tv.v_type != VAR_LIST - && item->li_tv.v_type != VAR_DICT)) + if (recurse) clear_tv(&item->li_tv); + else + clear_tv_no_recurse(&item->li_tv); vim_free(item); } vim_free(l); @@ -7185,6 +7237,16 @@ 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) + set_ref_in_item(&pt->pt_argv[i], copyID, + ht_stack, list_stack); + } } else if (tv->v_type == VAR_LIST) { @@ -7215,32 +7277,6 @@ set_ref_in_item( return abort; } - static void -partial_free(partial_T *pt, int free_dict) -{ - int i; - - for (i = 0; i < pt->pt_argc; ++i) - clear_tv(&pt->pt_argv[i]); - vim_free(pt->pt_argv); - if (free_dict) - dict_unref(pt->pt_dict); - func_unref(pt->pt_name); - vim_free(pt->pt_name); - vim_free(pt); -} - -/* - * Unreference a closure: decrement the reference count and free it when it - * becomes zero. - */ - void -partial_unref(partial_T *pt) -{ - if (pt != NULL && --pt->pt_refcount <= 0) - partial_free(pt, TRUE); -} - /* * Allocate an empty header for a dictionary. */ @@ -7331,20 +7367,10 @@ dict_free( * something recursive causing trouble. */ di = HI2DI(hi); hash_remove(&d->dv_hashtab, hi); - if (recurse || (di->di_tv.v_type != VAR_LIST - && di->di_tv.v_type != VAR_DICT)) - { - if (!recurse && di->di_tv.v_type == VAR_PARTIAL) - { - partial_T *pt = di->di_tv.vval.v_partial; - - /* We unref the partial but not the dict it refers to. */ - if (pt != NULL && --pt->pt_refcount == 0) - partial_free(pt, FALSE); - } - else - clear_tv(&di->di_tv); - } + if (recurse) + clear_tv(&di->di_tv); + else + clear_tv_no_recurse(&di->di_tv); vim_free(di); --todo; } diff --git a/src/testdir/test_partial.vim b/src/testdir/test_partial.vim --- a/src/testdir/test_partial.vim +++ b/src/testdir/test_partial.vim @@ -220,3 +220,21 @@ func Test_bind_in_python() endtry endif endfunc + +" This causes double free on exit if EXITFREE is defined. +func Test_cyclic_list_arg() + let l = [] + let Pt = function('string', [l]) + call add(l, Pt) + unlet l + unlet Pt +endfunc + +" This causes double free on exit if EXITFREE is defined. +func Test_cyclic_dict_arg() + let d = {} + let Pt = function('string', [d]) + let d.Pt = Pt + unlet d + unlet Pt +endfunc diff --git a/src/version.c b/src/version.c --- 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 */ /**/ + 1715, +/**/ 1714, /**/ 1713,