changeset 30122:458162398682 v9.0.0397

patch 9.0.0397: :defer not tested with exceptions and ":qa!" Commit: https://github.com/vim/vim/commit/58779858fb5a82a3233af5d4237a3cece88c10d4 Author: Bram Moolenaar <Bram@vim.org> Date: Tue Sep 6 18:31:14 2022 +0100 patch 9.0.0397: :defer not tested with exceptions and ":qa!" Problem: :defer not tested with exceptions and ":qa!". Solution: Test :defer works when exceptions are thrown and when ":qa!" is used. Invoke the deferred calls on exit.
author Bram Moolenaar <Bram@vim.org>
date Tue, 06 Sep 2022 19:45:04 +0200
parents 8bfd8ae3b460
children a50f26ec428f
files src/eval.c src/main.c src/proto/userfunc.pro src/proto/vim9execute.pro src/structs.h src/testdir/test_user_func.vim src/userfunc.c src/version.c src/vim9execute.c
diffstat 9 files changed, 123 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/src/eval.c
+++ b/src/eval.c
@@ -263,8 +263,9 @@ eval_expr_typval(typval_T *expr, typval_
 	if (partial->pt_func != NULL
 			  && partial->pt_func->uf_def_status != UF_NOT_COMPILED)
 	{
+	    // FIXME: should create a funccal and link it in current_funccal.
 	    if (call_def_function(partial->pt_func, argc, argv,
-						       partial, rettv) == FAIL)
+						 partial, NULL, rettv) == FAIL)
 		return FAIL;
 	}
 	else
--- a/src/main.c
+++ b/src/main.c
@@ -1583,6 +1583,11 @@ getout(int exitval)
     if (!is_not_a_term_or_gui())
 	windgoto((int)Rows - 1, 0);
 
+#ifdef FEAT_EVAL
+    // Invoked all deferred functions in the function stack.
+    invoke_all_defer();
+#endif
+
 #if defined(FEAT_EVAL) || defined(FEAT_SYN_HL)
     // Optionally print hashtable efficiency.
     hash_debug_results();
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -59,7 +59,7 @@ void func_ref(char_u *name);
 void func_ptr_ref(ufunc_T *fp);
 void ex_return(exarg_T *eap);
 int add_defer(char_u *name, int argcount_arg, typval_T *argvars);
-void handle_defer(void);
+void invoke_all_defer(void);
 void ex_call(exarg_T *eap);
 int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv);
 void discard_pending_return(void *rettv);
--- a/src/proto/vim9execute.pro
+++ b/src/proto/vim9execute.pro
@@ -13,7 +13,9 @@ typval_T *lookup_debug_var(char_u *name)
 int may_break_in_function(ufunc_T *ufunc);
 int exe_typval_instr(typval_T *tv, typval_T *rettv);
 char_u *exe_substitute_instr(void);
-int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, typval_T *rettv);
+int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, funccall_T *funccal, typval_T *rettv);
+void unwind_def_callstack(ectx_T *ectx);
+void may_invoke_defer_funcs(ectx_T *ectx);
 void set_context_in_disassemble_cmd(expand_T *xp, char_u *arg);
 char_u *get_disassemble_argument(expand_T *xp, int idx);
 void ex_disassemble(exarg_T *eap);
--- a/src/structs.h
+++ b/src/structs.h
@@ -1753,7 +1753,11 @@ struct funccall_S
     linenr_T	breakpoint;	// next line with breakpoint or zero
     int		dbg_tick;	// debug_tick when breakpoint was set
     int		level;		// top nesting level of executed function
+
     garray_T	fc_defer;	// functions to be called on return
+    ectx_T	*fc_ectx;	// execution context for :def function, NULL
+				// otherwise
+
 #ifdef FEAT_PROFILE
     proftime_T	prof_child;	// time spent in a child
 #endif
--- a/src/testdir/test_user_func.vim
+++ b/src/testdir/test_user_func.vim
@@ -581,5 +581,49 @@ func Test_defer()
   call assert_fails('defer Part("arg2")', 'E1300:')
 endfunc
 
+func DeferLevelTwo()
+  call writefile(['text'], 'XDeleteTwo', 'D')
+  throw 'someerror'
+endfunc
+
+def DeferLevelOne()
+  call writefile(['text'], 'XDeleteOne', 'D')
+  call g:DeferLevelTwo()
+enddef
+
+func Test_defer_throw()
+  let caught = 'no'
+  try
+    call DeferLevelOne()
+  catch /someerror/
+    let caught = 'yes'
+  endtry
+  call assert_equal('yes', caught)
+  call assert_false(filereadable('XDeleteOne'))
+  call assert_false(filereadable('XDeleteTwo'))
+endfunc
+
+func Test_defer_quitall()
+  let lines =<< trim END
+      vim9script
+      func DeferLevelTwo()
+        call writefile(['text'], 'XQuitallTwo', 'D')
+        qa!
+      endfunc
+
+      def DeferLevelOne()
+        call writefile(['text'], 'XQuitallOne', 'D')
+        call DeferLevelTwo()
+      enddef
+
+      DeferLevelOne()
+  END
+  call writefile(lines, 'XdeferQuitall', 'D')
+  let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitall')
+  call assert_equal(0, v:shell_error)
+  call assert_false(filereadable('XQuitallOne'))
+  call assert_false(filereadable('XQuitallTwo'))
+endfunc
+
 
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -33,6 +33,7 @@ static void funccal_unref(funccall_T *fc
 static void func_clear(ufunc_T *fp, int force);
 static int func_free(ufunc_T *fp, int force);
 static char_u *untrans_function_name(char_u *name);
+static void handle_defer_one(funccall_T *funccal);
 
     void
 func_init()
@@ -2651,7 +2652,8 @@ call_user_func(
 	    profile_may_start_func(&profile_info, fp, caller);
 #endif
 	sticky_cmdmod_flags = 0;
-	call_def_function(fp, argcount, argvars, funcexe->fe_partial, rettv);
+	call_def_function(fp, argcount, argvars, funcexe->fe_partial,
+								    fc, rettv);
 	funcdepth_decrement();
 #ifdef FEAT_PROFILE
 	if (do_profiling == PROF_YES && (fp->uf_profiling
@@ -2906,7 +2908,7 @@ call_user_func(
 				     DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
 
     // Invoke functions added with ":defer".
-    handle_defer();
+    handle_defer_one(current_funccal);
 
     --RedrawingDisabled;
 
@@ -5660,16 +5662,16 @@ theend:
 /*
  * Invoked after a functions has finished: invoke ":defer" functions.
  */
-    void
-handle_defer(void)
+    static void
+handle_defer_one(funccall_T *funccal)
 {
     int	    idx;
 
-    for (idx = current_funccal->fc_defer.ga_len - 1; idx >= 0; --idx)
+    for (idx = funccal->fc_defer.ga_len - 1; idx >= 0; --idx)
     {
 	funcexe_T   funcexe;
 	typval_T    rettv;
-	defer_T	    *dr = ((defer_T *)current_funccal->fc_defer.ga_data) + idx;
+	defer_T	    *dr = ((defer_T *)funccal->fc_defer.ga_data) + idx;
 	int	    i;
 
 	CLEAR_FIELD(funcexe);
@@ -5683,7 +5685,29 @@ handle_defer(void)
 	for (i = dr->dr_argcount - 1; i >= 0; --i)
 	    clear_tv(&dr->dr_argvars[i]);
     }
-    ga_clear(&current_funccal->fc_defer);
+    ga_clear(&funccal->fc_defer);
+}
+
+/*
+ * Called when exiting: call all defer functions.
+ */
+    void
+invoke_all_defer(void)
+{
+    funccall_T *funccal;
+
+    for (funccal = current_funccal; funccal != NULL; funccal = funccal->caller)
+	if (funccal->fc_ectx != NULL)
+	{
+	    // :def function
+	    unwind_def_callstack(funccal->fc_ectx);
+	    may_invoke_defer_funcs(funccal->fc_ectx);
+	}
+	else
+	{
+	    // legacy function
+	    handle_defer_one(funccal);
+	}
 }
 
 /*
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    397,
+/**/
     396,
 /**/
     395,
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -5171,13 +5171,7 @@ on_fatal_error:
 done:
     ret = OK;
 theend:
-    {
-	dfunc_T	*dfunc = ((dfunc_T *)def_functions.ga_data)
-							  + ectx->ec_dfunc_idx;
-
-	if (dfunc->df_defer_var_idx > 0)
-	    invoke_defer_funcs(ectx);
-    }
+    may_invoke_defer_funcs(ectx);
 
     dict_stack_clear(dict_stack_len_at_start);
     ectx->ec_trylevel_at_start = save_trylevel_at_start;
@@ -5258,6 +5252,7 @@ call_def_function(
     int		argc_arg,	// nr of arguments
     typval_T	*argv,		// arguments
     partial_T	*partial,	// optional partial for context
+    funccall_T	*funccal,
     typval_T	*rettv)		// return value
 {
     ectx_T	ectx;		// execution context
@@ -5494,6 +5489,10 @@ call_def_function(
 	ectx.ec_instr = INSTRUCTIONS(dfunc);
     }
 
+    // Store the execution context in funccal, used by invoke_all_defer().
+    if (funccal != NULL)
+	funccal->fc_ectx = &ectx;
+
     // Following errors are in the function, not the caller.
     // Commands behave like vim9script.
     estack_push_ufunc(ufunc, 1);
@@ -5537,8 +5536,7 @@ call_def_function(
     }
 
     // When failed need to unwind the call stack.
-    while (ectx.ec_frame_idx != ectx.ec_initial_frame_idx)
-	func_return(&ectx);
+    unwind_def_callstack(&ectx);
 
     // Deal with any remaining closures, they may be in use somewhere.
     if (ectx.ec_funcrefs.ga_len > 0)
@@ -5604,6 +5602,30 @@ failed_early:
 }
 
 /*
+ * Called when a def function has finished (possibly failed).
+ * Invoke all the function returns to clean up and invoke deferred functions,
+ * except the toplevel one.
+ */
+    void
+unwind_def_callstack(ectx_T *ectx)
+{
+    while (ectx->ec_frame_idx != ectx->ec_initial_frame_idx)
+	func_return(ectx);
+}
+
+/*
+ * Invoke any deffered functions for the top function in "ectx".
+ */
+    void
+may_invoke_defer_funcs(ectx_T *ectx)
+{
+    dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx;
+
+    if (dfunc->df_defer_var_idx > 0)
+	invoke_defer_funcs(ectx);
+}
+
+/*
  * List instructions "instr" up to "instr_count" or until ISN_FINISH.
  * "ufunc" has the source lines, NULL for the instructions of ISN_SUBSTITUTE.
  * "pfx" is prefixed to every line.