summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2022-09-06 18:31:14 +0100
committerBram Moolenaar <Bram@vim.org>2022-09-06 18:31:14 +0100
commit58779858fb5a82a3233af5d4237a3cece88c10d4 (patch)
treec1654eb20a1a8300e1b304263ccea655928b3401
parent2834ebdee473c838e50e60d0aa160f0e62fc8ef9 (diff)
patch 9.0.0397: :defer not tested with exceptions and ":qa!"v9.0.0397
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.
-rw-r--r--src/eval.c3
-rw-r--r--src/main.c5
-rw-r--r--src/proto/userfunc.pro2
-rw-r--r--src/proto/vim9execute.pro4
-rw-r--r--src/structs.h4
-rw-r--r--src/testdir/test_user_func.vim44
-rw-r--r--src/userfunc.c38
-rw-r--r--src/version.c2
-rw-r--r--src/vim9execute.c40
9 files changed, 123 insertions, 19 deletions
diff --git a/src/eval.c b/src/eval.c
index 6b2d86b47a..5ec5b8ae7b 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -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
diff --git a/src/main.c b/src/main.c
index 7a42463ba3..2a2dcb0cac 100644
--- 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();
diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro
index 074a2b82a2..4fbbe86c99 100644
--- 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);
diff --git a/src/proto/vim9execute.pro b/src/proto/vim9execute.pro
index 5203304cc7..d43b1e2ebd 100644
--- 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);
diff --git a/src/structs.h b/src/structs.h
index 8fab297b59..e6277198fc 100644
--- 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
diff --git a/src/testdir/test_user_func.vim b/src/testdir/test_user_func.vim
index 4ea7711197..d7cfae1e94 100644
--- 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
diff --git a/src/userfunc.c b/src/userfunc.c
index 0ee276b3c3..8956b82443 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -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);
+ }
}
/*
diff --git a/src/version.c b/src/version.c
index f1170220cf..35184b2b79 100644
--- 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,
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 08774fee32..0b50acee33 100644
--- 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.