]> granicus.if.org Git - vim/commitdiff
patch 9.0.0397: :defer not tested with exceptions and ":qa!" v9.0.0397
authorBram Moolenaar <Bram@vim.org>
Tue, 6 Sep 2022 17:31:14 +0000 (18:31 +0100)
committerBram Moolenaar <Bram@vim.org>
Tue, 6 Sep 2022 17:31:14 +0000 (18:31 +0100)
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.

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

index 6b2d86b47ae55c5ddcdec4db68c090d8eeae4755..5ec5b8ae7b7336301db88452d1492de24a72ed10 100644 (file)
@@ -263,8 +263,9 @@ eval_expr_typval(typval_T *expr, typval_T *argv, int argc, typval_T *rettv)
        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
index 7a42463ba3f41af4bb6e2d3f1520ac5825adf7d4..2a2dcb0cac3678cf7a0334003981c0b5625fcb39 100644 (file)
@@ -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();
index 074a2b82a2cc01fd158936bff45e252e041ade39..4fbbe86c9928486f87ef9a7a4e8a9c2c3b5cbd7b 100644 (file)
@@ -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);
index 5203304cc718820a93315f7817499302fc097e3c..d43b1e2ebd7da3ddf8daff7ad45bd405dde7a78c 100644 (file)
@@ -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);
index 8fab297b593c715def821daa5191e5d6cbd42a55..e6277198fc4c45d7ee97e2191a30b4e8a0702311 100644 (file)
@@ -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
index 4ea7711197e7efe38adaff8599b831f9b022ff7c..d7cfae1e9404d6bdc0885b2b4c772bd38ae72dbb 100644 (file)
@@ -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
index 0ee276b3c3ad5c1df70a1eeaf1d1082ba03a102e..8956b8244325cf1b8da4df103c105ecf9080438c 100644 (file)
@@ -33,6 +33,7 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, int force);
 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);
+       }
 }
 
 /*
index f1170220cfdc7dfa147aa836df1e7c3c325729d9..35184b2b797d879e3d1ca9b3c827c5b0c46e3ab1 100644 (file)
@@ -703,6 +703,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    397,
 /**/
     396,
 /**/
index 08774fee323cdb35f7f1c5e91eeaed8625901324..0b50acee33f47ab89d6a56e66dd5b0c77b5747ce 100644 (file)
@@ -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)
@@ -5603,6 +5601,30 @@ failed_early:
     return ret;
 }
 
+/*
+ * 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.