# HG changeset patch # User Bram Moolenaar # Date 1662486304 -7200 # Node ID 458162398682d10f72317acbd1c2bf55374f8f00 # Parent 8bfd8ae3b4601934a3c540adbe1c36598bb9dcfd patch 9.0.0397: :defer not tested with exceptions and ":qa!" Commit: https://github.com/vim/vim/commit/58779858fb5a82a3233af5d4237a3cece88c10d4 Author: Bram Moolenaar 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. diff --git a/src/eval.c b/src/eval.c --- 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 diff --git a/src/main.c b/src/main.c --- 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 --- 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 --- 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 --- 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 --- 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 --- 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(¤t_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 --- 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 --- 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.