changeset 32274:83caf07aedd6 v9.0.1468

patch 9.0.1468: recursively calling :defer function if it does :qa Commit: https://github.com/vim/vim/commit/a1f2b5ddc63d4e2b6ab047d8c839dd8477b36aba Author: zeertzjq <zeertzjq@outlook.com> Date: Tue Apr 18 21:04:53 2023 +0100 patch 9.0.1468: recursively calling :defer function if it does :qa Problem: Recursively calling :defer function if it does :qa in a compiled function. Solution: Clear the defer entry before calling the function. (closes #12271)
author Bram Moolenaar <Bram@vim.org>
date Tue, 18 Apr 2023 22:15:04 +0200
parents a7e63171cbfb
children 07141765bae8
files src/testdir/test_user_func.vim src/version.c src/vim9execute.c
diffstat 3 files changed, 51 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/src/testdir/test_user_func.vim
+++ b/src/testdir/test_user_func.vim
@@ -651,30 +651,55 @@ func Test_defer_throw()
   call assert_false(filereadable('XDeleteTwo'))
 endfunc
 
-func Test_defer_quitall()
+func Test_defer_quitall_func()
   let lines =<< trim END
-      vim9script
       func DeferLevelTwo()
-        call writefile(['text'], 'XQuitallTwo', 'D')
-        call writefile(['quit'], 'XQuitallThree', 'a')
+        call writefile(['text'], 'XQuitallFuncTwo', 'D')
+        call writefile(['quit'], 'XQuitallFuncThree', 'a')
         qa!
       endfunc
 
+      func DeferLevelOne()
+        call writefile(['text'], 'XQuitalFunclOne', 'D')
+        defer DeferLevelTwo()
+      endfunc
+
+      call DeferLevelOne()
+  END
+  call writefile(lines, 'XdeferQuitallFunc', 'D')
+  call system(GetVimCommand() .. ' -X -S XdeferQuitallFunc')
+  call assert_equal(0, v:shell_error)
+  call assert_false(filereadable('XQuitallFuncOne'))
+  call assert_false(filereadable('XQuitallFuncTwo'))
+  call assert_equal(['quit'], readfile('XQuitallFuncThree'))
+
+  call delete('XQuitallFuncThree')
+endfunc
+
+func Test_defer_quitall_def()
+  let lines =<< trim END
+      vim9script
+      def DeferLevelTwo()
+        call writefile(['text'], 'XQuitallDefTwo', 'D')
+        call writefile(['quit'], 'XQuitallDefThree', 'a')
+        qa!
+      enddef
+
       def DeferLevelOne()
-        call writefile(['text'], 'XQuitallOne', 'D')
-        call DeferLevelTwo()
+        call writefile(['text'], 'XQuitallDefOne', 'D')
+        defer DeferLevelTwo()
       enddef
 
       DeferLevelOne()
   END
-  call writefile(lines, 'XdeferQuitall', 'D')
-  let res = system(GetVimCommand() .. ' -X -S XdeferQuitall')
+  call writefile(lines, 'XdeferQuitallDef', 'D')
+  call system(GetVimCommand() .. ' -X -S XdeferQuitallDef')
   call assert_equal(0, v:shell_error)
-  call assert_false(filereadable('XQuitallOne'))
-  call assert_false(filereadable('XQuitallTwo'))
-  call assert_equal(['quit'], readfile('XQuitallThree'))
+  call assert_false(filereadable('XQuitallDefOne'))
+  call assert_false(filereadable('XQuitallDefTwo'))
+  call assert_equal(['quit'], readfile('XQuitallDefThree'))
 
-  call delete('XQuitallThree')
+  call delete('XQuitallDefThree')
 endfunc
 
 func Test_defer_quitall_in_expr_func()
@@ -693,7 +718,7 @@ func Test_defer_quitall_in_expr_func()
       call Test_defer_in_funcref()
   END
   call writefile(lines, 'XdeferQuitallExpr', 'D')
-  let res = system(GetVimCommand() .. ' -X -S XdeferQuitallExpr')
+  call system(GetVimCommand() .. ' -X -S XdeferQuitallExpr')
   call assert_equal(0, v:shell_error)
   call assert_false(filereadable('Xentry0'))
   call assert_false(filereadable('Xentry1'))
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1468,
+/**/
     1467,
 /**/
     1466,
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1063,6 +1063,10 @@ invoke_defer_funcs(ectx_T *ectx)
 	int	    obj_off = functv->v_type == VAR_PARTIAL ? 1 : 0;
 	int	    argcount = l->lv_len - 1 - obj_off;
 
+	if (functv->vval.v_string == NULL)
+	    // already being called, can happen if function does ":qa"
+	    continue;
+
 	if (obj_off == 1)
 	    arg_li = arg_li->li_next;  // second list item is the object
 	for (i = 0; i < argcount; ++i)
@@ -1082,9 +1086,14 @@ invoke_defer_funcs(ectx_T *ectx)
 	    if (funcexe.fe_object != NULL)
 		++funcexe.fe_object->obj_refcount;
 	}
-	(void)call_func(functv->vval.v_string, -1,
-					  &rettv, argcount, argvars, &funcexe);
+
+	char_u *name = functv->vval.v_string;
+	functv->vval.v_string = NULL;
+
+	(void)call_func(name, -1, &rettv, argcount, argvars, &funcexe);
+
 	clear_tv(&rettv);
+	vim_free(name);
     }
 }