changeset 21303:7c50dfe302f8 v8.2.1202

patch 8.2.1202: Vim9: crash when calling a closure from a builtin function Commit: https://github.com/vim/vim/commit/08f7a41b0a280e5901eb4ee4bbfe682113863492 Author: Bram Moolenaar <Bram@vim.org> Date: Mon Jul 13 20:41:08 2020 +0200 patch 8.2.1202: Vim9: crash when calling a closure from a builtin function Problem: Vim9: crash when calling a closure from a builtin function. Solution: Use the current execution context. (closes https://github.com/vim/vim/issues/6441)
author Bram Moolenaar <Bram@vim.org>
date Mon, 13 Jul 2020 20:45:04 +0200
parents cc426acfb89d
children 6816e7e6663b
files src/testdir/test_vim9_func.vim src/version.c src/vim9execute.c
diffstat 3 files changed, 41 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -1019,5 +1019,24 @@ def Test_recursive_call()
   assert_equal(6765, Fibonacci(20))
 enddef
 
+def TreeWalk(dir: string): list<any>
+  return readdir(dir)->map({_, val ->
+            fnamemodify(dir .. '/' .. val, ':p')->isdirectory()
+               ? {val : TreeWalk(dir .. '/' .. val)}
+               : val
+             })
+enddef
+
+def Test_closure_in_map()
+  mkdir('XclosureDir/tdir', 'p')
+  writefile(['111'], 'XclosureDir/file1')
+  writefile(['222'], 'XclosureDir/file2')
+  writefile(['333'], 'XclosureDir/tdir/file3')
+
+  assert_equal(['file1', 'file2', {'tdir': ['file3']}], TreeWalk('XclosureDir'))
+
+  delete('XclosureDir', 'rf')
+enddef
+
 
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
--- a/src/version.c
+++ b/src/version.c
@@ -755,6 +755,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1202,
+/**/
     1201,
 /**/
     1200,
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -461,6 +461,10 @@ call_prepare(int argcount, typval_T *arg
     return OK;
 }
 
+// Ugly global to avoid passing the execution context around through many
+// layers.
+static ectx_T *current_ectx = NULL;
+
 /*
  * Call a builtin function by index.
  */
@@ -470,12 +474,16 @@ call_bfunc(int func_idx, int argcount, e
     typval_T	argvars[MAX_FUNC_ARGS];
     int		idx;
     int		did_emsg_before = did_emsg;
+    ectx_T	*prev_ectx = current_ectx;
 
     if (call_prepare(argcount, argvars, ectx) == FAIL)
 	return FAIL;
 
-    // Call the builtin function.
+    // Call the builtin function.  Set "current_ectx" so that when it
+    // recursively invokes call_def_function() a closure context can be set.
+    current_ectx = ectx;
     call_internal_func_by_idx(func_idx, argvars, STACK_TV_BOT(-1));
+    current_ectx = prev_ectx;
 
     // Clear the arguments.
     for (idx = 0; idx < argcount; ++idx)
@@ -749,8 +757,17 @@ call_def_function(
 
     if (partial != NULL)
     {
-	ectx.ec_outer_stack = partial->pt_ectx_stack;
-	ectx.ec_outer_frame = partial->pt_ectx_frame;
+	if (partial->pt_ectx_stack == NULL && current_ectx != NULL)
+	{
+	    // TODO: is this always the right way?
+	    ectx.ec_outer_stack = &current_ectx->ec_stack;
+	    ectx.ec_outer_frame = current_ectx->ec_frame_idx;
+	}
+	else
+	{
+	    ectx.ec_outer_stack = partial->pt_ectx_stack;
+	    ectx.ec_outer_frame = partial->pt_ectx_frame;
+	}
     }
 
     // dummy frame entries