From 904d0995837a2569ae640f5253da3dd4569fec6f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 24 Feb 2023 15:03:28 +0800 Subject: [PATCH 1/4] vim-patch:8.2.2449: Vim9: flatten() always changes the list type Problem: Vim9: flatten() always changes the list type. Solution: Disallow using flatten() and add flattennew(). https://github.com/vim/vim/commit/3b690069730805a147d45d92eaca4dc838272d1d Co-authored-by: Bram Moolenaar --- runtime/doc/builtin.txt | 8 +++++- runtime/doc/usr_41.txt | 1 + src/nvim/eval.lua | 1 + src/nvim/eval/funcs.c | 41 +++++++++++++++++++++++++------ src/nvim/eval/typval.c | 9 +++---- src/nvim/testdir/test_flatten.vim | 11 +++++++++ 6 files changed, 57 insertions(+), 14 deletions(-) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index af5ba9ab8f..5f279ed09c 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -148,6 +148,8 @@ finddir({name} [, {path} [, {count}]]) findfile({name} [, {path} [, {count}]]) String find file {name} in {path} flatten({list} [, {maxdepth}]) List flatten {list} up to {maxdepth} levels +flattennew({list} [, {maxdepth}]) + List flatten a copy of {list} float2nr({expr}) Number convert Float {expr} to a Number floor({expr}) Float round {expr} down fmod({expr1}, {expr2}) Float remainder of {expr1} / {expr2} @@ -2277,7 +2279,7 @@ flatten({list} [, {maxdepth}]) *flatten()* Flatten {list} up to {maxdepth} levels. Without {maxdepth} the result is a |List| without nesting, as if {maxdepth} is a very large number. - The {list} is changed in place, make a copy first if you do + The {list} is changed in place, use |flattennew()| if you do not want that. *E900* {maxdepth} means how deep in nested lists changes are made. @@ -2295,6 +2297,10 @@ flatten({list} [, {maxdepth}]) *flatten()* Can also be used as a |method|: > mylist->flatten() < +flattennew({list} [, {maxdepth}]) *flattennew()* + Like |flatten()| but first make a copy of {list}. + + float2nr({expr}) *float2nr()* Convert {expr} to a Number by omitting the part after the decimal point. diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 910aebae70..27c5927cf9 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -670,6 +670,7 @@ List manipulation: *list-functions* count() count number of times a value appears in a List repeat() repeat a List multiple times flatten() flatten a List + flattennew() flatten a copy of a List Dictionary manipulation: *dict-functions* get() get an entry without an error for a wrong key diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 9a5ab51c71..a1caeea95a 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -128,6 +128,7 @@ return { finddir={args={1, 3}, base=1}, findfile={args={1, 3}, base=1}, flatten={args={1, 2}, base=1}, + flattennew={args={1, 2}, base=1}, float2nr={args=1, base=1}, floor={args=1, base=1, float_func="floor"}, fmod={args=2, base=1}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 6f983d3208..3a58b90f96 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1870,8 +1870,8 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = cmdstr; } -/// "flatten(list[, {maxdepth}])" function -static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +/// "flatten()" and "flattennew()" functions +static void flatten_common(typval_T *argvars, typval_T *rettv, bool make_copy) { bool error = false; @@ -1895,13 +1895,38 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } list_T *list = argvars[0].vval.v_list; - if (list != NULL - && !value_check_lock(tv_list_locked(list), - N_("flatten() argument"), - TV_TRANSLATE) - && tv_list_flatten(list, maxdepth) == OK) { - tv_copy(&argvars[0], rettv); + rettv->v_type = VAR_LIST; + rettv->vval.v_list = list; + if (list == NULL) { + return; } + + if (make_copy) { + list = tv_list_copy(NULL, list, true, get_copyID()); + rettv->vval.v_list = list; + if (list == NULL) { + return; + } + } else { + if (value_check_lock(tv_list_locked(list), N_("flatten() argument"), TV_TRANSLATE)) { + return; + } + tv_list_ref(list); + } + + tv_list_flatten(list, maxdepth); +} + +/// "flatten(list[, {maxdepth}])" function +static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + flatten_common(argvars, rettv, false); +} + +/// "flattennew(list[, {maxdepth}])" function +static void f_flattennew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + flatten_common(argvars, rettv, true); } /// "extend(list, list [, idx])" function diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 9a3a1c3c0f..ab4245b59a 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -654,14 +654,14 @@ tv_list_copy_error: /// @param[in] maxdepth Maximum depth that will be flattened /// /// @return OK or FAIL -int tv_list_flatten(list_T *list, long maxdepth) - FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT +void tv_list_flatten(list_T *list, long maxdepth) + FUNC_ATTR_NONNULL_ARG(1) { listitem_T *item; listitem_T *to_free; int n; if (maxdepth == 0) { - return OK; + return; } n = 0; @@ -669,7 +669,7 @@ int tv_list_flatten(list_T *list, long maxdepth) while (item != NULL) { fast_breakcheck(); if (got_int) { - return FAIL; + return; } if (item->li_tv.v_type == VAR_LIST) { listitem_T *next = item->li_next; @@ -695,7 +695,6 @@ int tv_list_flatten(list_T *list, long maxdepth) item = item->li_next; } } - return OK; } /// Extend first list with the second diff --git a/src/nvim/testdir/test_flatten.vim b/src/nvim/testdir/test_flatten.vim index 99086611e1..d8abc1ad68 100644 --- a/src/nvim/testdir/test_flatten.vim +++ b/src/nvim/testdir/test_flatten.vim @@ -79,3 +79,14 @@ func Test_flatten() call assert_equal([1, 2, 1, 2], flatten(l:x, 2)) call assert_equal([2, l:x], l:y) endfunc + +func Test_flattennew() + let l = [1, [2, [3, 4]], 5] + call assert_equal([1, 2, 3, 4, 5], flattennew(l)) + call assert_equal([1, [2, [3, 4]], 5], l) + + call assert_equal([1, 2, [3, 4], 5], flattennew(l, 1)) + call assert_equal([1, [2, [3, 4]], 5], l) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab From d9263688bf0282918f5a9801dae8b85e4c85bd7e Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 24 Feb 2023 15:24:38 +0800 Subject: [PATCH 2/4] vim-patch:8.2.4627: flatten() does not use maxdepth correctly Problem: flatten() does not use maxdepth correctly. Solution: Use a recursive implementation. (closes vim/vim#10020) https://github.com/vim/vim/commit/acf7d73a7f5cdd63b34de777a4ce5eb3e2ba0ab3 Co-authored-by: Bram Moolenaar --- src/nvim/eval/funcs.c | 2 +- src/nvim/eval/typval.c | 42 ++++++++++++++++--------------- src/nvim/testdir/test_flatten.vim | 16 ++++++++++++ 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 3a58b90f96..6ac94706c7 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1914,7 +1914,7 @@ static void flatten_common(typval_T *argvars, typval_T *rettv, bool make_copy) tv_list_ref(list); } - tv_list_flatten(list, maxdepth); + tv_list_flatten(list, NULL, tv_list_len(list), maxdepth); } /// "flatten(list[, {maxdepth}])" function diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ab4245b59a..125a4310d8 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -647,53 +647,55 @@ tv_list_copy_error: return NULL; } -/// Flatten "list" in place to depth "maxdepth". +/// Flatten up to "maxitems" in "list", starting at "first" to depth "maxdepth". +/// When "first" is NULL use the first item. /// Does nothing if "maxdepth" is 0. /// /// @param[in,out] list List to flatten /// @param[in] maxdepth Maximum depth that will be flattened /// /// @return OK or FAIL -void tv_list_flatten(list_T *list, long maxdepth) +void tv_list_flatten(list_T *list, listitem_T *first, long maxitems, long maxdepth) FUNC_ATTR_NONNULL_ARG(1) { listitem_T *item; listitem_T *to_free; - int n; + int done = 0; if (maxdepth == 0) { return; } - n = 0; - item = list->lv_first; - while (item != NULL) { + if (first == NULL) { + item = list->lv_first; + } else { + item = first; + } + + while (item != NULL && done < maxitems) { + listitem_T *next = item->li_next; + fast_breakcheck(); if (got_int) { return; } if (item->li_tv.v_type == VAR_LIST) { - listitem_T *next = item->li_next; + list_T *itemlist = item->li_tv.vval.v_list; tv_list_drop_items(list, item, item); - tv_list_extend(list, item->li_tv.vval.v_list, next); + tv_list_extend(list, itemlist, next); tv_clear(&item->li_tv); to_free = item; - if (item->li_prev == NULL) { - item = list->lv_first; - } else { - item = item->li_prev->li_next; + if (maxdepth > 0) { + tv_list_flatten(list, + item->li_prev == NULL ? list->lv_first : item->li_prev->li_next, + itemlist->lv_len, maxdepth - 1); } xfree(to_free); - - if (++n >= maxdepth) { - n = 0; - item = next; - } - } else { - n = 0; - item = item->li_next; } + + done++; + item = next; } } diff --git a/src/nvim/testdir/test_flatten.vim b/src/nvim/testdir/test_flatten.vim index d8abc1ad68..aa91060313 100644 --- a/src/nvim/testdir/test_flatten.vim +++ b/src/nvim/testdir/test_flatten.vim @@ -78,6 +78,14 @@ func Test_flatten() call add(y, x) " l:y = [2, [1, [...]]] call assert_equal([1, 2, 1, 2], flatten(l:x, 2)) call assert_equal([2, l:x], l:y) + + let l4 = [ 1, [ 11, [ 101, [ 1001 ] ] ] ] + call assert_equal(l4, flatten(deepcopy(l4), 0)) + call assert_equal([1, 11, [101, [1001]]], flatten(deepcopy(l4), 1)) + call assert_equal([1, 11, 101, [1001]], flatten(deepcopy(l4), 2)) + call assert_equal([1, 11, 101, 1001], flatten(deepcopy(l4), 3)) + call assert_equal([1, 11, 101, 1001], flatten(deepcopy(l4), 4)) + call assert_equal([1, 11, 101, 1001], flatten(deepcopy(l4))) endfunc func Test_flattennew() @@ -87,6 +95,14 @@ func Test_flattennew() call assert_equal([1, 2, [3, 4], 5], flattennew(l, 1)) call assert_equal([1, [2, [3, 4]], 5], l) + + let l4 = [ 1, [ 11, [ 101, [ 1001 ] ] ] ] + call assert_equal(l4, flatten(deepcopy(l4), 0)) + call assert_equal([1, 11, [101, [1001]]], flattennew(l4, 1)) + call assert_equal([1, 11, 101, [1001]], flattennew(l4, 2)) + call assert_equal([1, 11, 101, 1001], flattennew(l4, 3)) + call assert_equal([1, 11, 101, 1001], flattennew(l4, 4)) + call assert_equal([1, 11, 101, 1001], flattennew(l4)) endfunc " vim: shiftwidth=2 sts=2 expandtab From ec2557236710f3e062164e7ff4b137b2a0d8dfa4 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 24 Feb 2023 15:31:43 +0800 Subject: [PATCH 3/4] vim-patch:8.2.4629: flattennew() makes a deep copy unnecessarily Problem: flattennew() makes a deep copy unnecessarily. Solution: Use a shallow copy. (issue vim/vim#10012) https://github.com/vim/vim/commit/c6c1ec4da53db9d292fa3dd081c20123f8261178 Co-authored-by: Bram Moolenaar --- src/nvim/eval/funcs.c | 2 +- src/nvim/eval/typval.c | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 6ac94706c7..f1852f1b6d 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1902,7 +1902,7 @@ static void flatten_common(typval_T *argvars, typval_T *rettv, bool make_copy) } if (make_copy) { - list = tv_list_copy(NULL, list, true, get_copyID()); + list = tv_list_copy(NULL, list, false, get_copyID()); rettv->vval.v_list = list; if (list == NULL) { return; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 125a4310d8..9faf19c364 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -659,7 +659,6 @@ void tv_list_flatten(list_T *list, listitem_T *first, long maxitems, long maxdep FUNC_ATTR_NONNULL_ARG(1) { listitem_T *item; - listitem_T *to_free; int done = 0; if (maxdepth == 0) { return; @@ -684,14 +683,13 @@ void tv_list_flatten(list_T *list, listitem_T *first, long maxitems, long maxdep tv_list_drop_items(list, item, item); tv_list_extend(list, itemlist, next); tv_clear(&item->li_tv); - to_free = item; if (maxdepth > 0) { tv_list_flatten(list, item->li_prev == NULL ? list->lv_first : item->li_prev->li_next, itemlist->lv_len, maxdepth - 1); } - xfree(to_free); + xfree(item); } done++; From c76dfe14b1422a1caccf13c3bc86754902eb0302 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 24 Feb 2023 15:33:51 +0800 Subject: [PATCH 4/4] vim-patch:8.2.4632: using freed memory in flatten() Problem: Using freed memory in flatten(). Solution: Clear typval after recursing into list. https://github.com/vim/vim/commit/f3980dc5d0a5f873cf764b8ba3e567e42259e4e5 Co-authored-by: Bram Moolenaar --- src/nvim/eval/typval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 9faf19c364..17499480ed 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -682,13 +682,13 @@ void tv_list_flatten(list_T *list, listitem_T *first, long maxitems, long maxdep tv_list_drop_items(list, item, item); tv_list_extend(list, itemlist, next); - tv_clear(&item->li_tv); if (maxdepth > 0) { tv_list_flatten(list, item->li_prev == NULL ? list->lv_first : item->li_prev->li_next, itemlist->lv_len, maxdepth - 1); } + tv_clear(&item->li_tv); xfree(item); }