Skip to content

Commit 5877985

Browse files
committed
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.
1 parent 2834ebd commit 5877985

9 files changed

Lines changed: 123 additions & 19 deletions

File tree

src/eval.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,9 @@ eval_expr_typval(typval_T *expr, typval_T *argv, int argc, typval_T *rettv)
263263
if (partial->pt_func != NULL
264264
&& partial->pt_func->uf_def_status != UF_NOT_COMPILED)
265265
{
266+
// FIXME: should create a funccal and link it in current_funccal.
266267
if (call_def_function(partial->pt_func, argc, argv,
267-
partial, rettv) == FAIL)
268+
partial, NULL, rettv) == FAIL)
268269
return FAIL;
269270
}
270271
else

src/main.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,6 +1583,11 @@ getout(int exitval)
15831583
if (!is_not_a_term_or_gui())
15841584
windgoto((int)Rows - 1, 0);
15851585

1586+
#ifdef FEAT_EVAL
1587+
// Invoked all deferred functions in the function stack.
1588+
invoke_all_defer();
1589+
#endif
1590+
15861591
#if defined(FEAT_EVAL) || defined(FEAT_SYN_HL)
15871592
// Optionally print hashtable efficiency.
15881593
hash_debug_results();

src/proto/userfunc.pro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ void func_ref(char_u *name);
5959
void func_ptr_ref(ufunc_T *fp);
6060
void ex_return(exarg_T *eap);
6161
int add_defer(char_u *name, int argcount_arg, typval_T *argvars);
62-
void handle_defer(void);
62+
void invoke_all_defer(void);
6363
void ex_call(exarg_T *eap);
6464
int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv);
6565
void discard_pending_return(void *rettv);

src/proto/vim9execute.pro

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ typval_T *lookup_debug_var(char_u *name);
1313
int may_break_in_function(ufunc_T *ufunc);
1414
int exe_typval_instr(typval_T *tv, typval_T *rettv);
1515
char_u *exe_substitute_instr(void);
16-
int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, typval_T *rettv);
16+
int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, funccall_T *funccal, typval_T *rettv);
17+
void unwind_def_callstack(ectx_T *ectx);
18+
void may_invoke_defer_funcs(ectx_T *ectx);
1719
void set_context_in_disassemble_cmd(expand_T *xp, char_u *arg);
1820
char_u *get_disassemble_argument(expand_T *xp, int idx);
1921
void ex_disassemble(exarg_T *eap);

src/structs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1753,7 +1753,11 @@ struct funccall_S
17531753
linenr_T breakpoint; // next line with breakpoint or zero
17541754
int dbg_tick; // debug_tick when breakpoint was set
17551755
int level; // top nesting level of executed function
1756+
17561757
garray_T fc_defer; // functions to be called on return
1758+
ectx_T *fc_ectx; // execution context for :def function, NULL
1759+
// otherwise
1760+
17571761
#ifdef FEAT_PROFILE
17581762
proftime_T prof_child; // time spent in a child
17591763
#endif

src/testdir/test_user_func.vim

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,5 +581,49 @@ func Test_defer()
581581
call assert_fails('defer Part("arg2")', 'E1300:')
582582
endfunc
583583

584+
func DeferLevelTwo()
585+
call writefile(['text'], 'XDeleteTwo', 'D')
586+
throw 'someerror'
587+
endfunc
588+
589+
def DeferLevelOne()
590+
call writefile(['text'], 'XDeleteOne', 'D')
591+
call g:DeferLevelTwo()
592+
enddef
593+
594+
func Test_defer_throw()
595+
let caught = 'no'
596+
try
597+
call DeferLevelOne()
598+
catch /someerror/
599+
let caught = 'yes'
600+
endtry
601+
call assert_equal('yes', caught)
602+
call assert_false(filereadable('XDeleteOne'))
603+
call assert_false(filereadable('XDeleteTwo'))
604+
endfunc
605+
606+
func Test_defer_quitall()
607+
let lines =<< trim END
608+
vim9script
609+
func DeferLevelTwo()
610+
call writefile(['text'], 'XQuitallTwo', 'D')
611+
qa!
612+
endfunc
613+
614+
def DeferLevelOne()
615+
call writefile(['text'], 'XQuitallOne', 'D')
616+
call DeferLevelTwo()
617+
enddef
618+
619+
DeferLevelOne()
620+
END
621+
call writefile(lines, 'XdeferQuitall', 'D')
622+
let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitall')
623+
call assert_equal(0, v:shell_error)
624+
call assert_false(filereadable('XQuitallOne'))
625+
call assert_false(filereadable('XQuitallTwo'))
626+
endfunc
627+
584628

585629
" vim: shiftwidth=2 sts=2 expandtab

src/userfunc.c

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, int force);
3333
static void func_clear(ufunc_T *fp, int force);
3434
static int func_free(ufunc_T *fp, int force);
3535
static char_u *untrans_function_name(char_u *name);
36+
static void handle_defer_one(funccall_T *funccal);
3637

3738
void
3839
func_init()
@@ -2651,7 +2652,8 @@ call_user_func(
26512652
profile_may_start_func(&profile_info, fp, caller);
26522653
#endif
26532654
sticky_cmdmod_flags = 0;
2654-
call_def_function(fp, argcount, argvars, funcexe->fe_partial, rettv);
2655+
call_def_function(fp, argcount, argvars, funcexe->fe_partial,
2656+
fc, rettv);
26552657
funcdepth_decrement();
26562658
#ifdef FEAT_PROFILE
26572659
if (do_profiling == PROF_YES && (fp->uf_profiling
@@ -2906,7 +2908,7 @@ call_user_func(
29062908
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
29072909

29082910
// Invoke functions added with ":defer".
2909-
handle_defer();
2911+
handle_defer_one(current_funccal);
29102912

29112913
--RedrawingDisabled;
29122914

@@ -5660,16 +5662,16 @@ add_defer(char_u *name, int argcount_arg, typval_T *argvars)
56605662
/*
56615663
* Invoked after a functions has finished: invoke ":defer" functions.
56625664
*/
5663-
void
5664-
handle_defer(void)
5665+
static void
5666+
handle_defer_one(funccall_T *funccal)
56655667
{
56665668
int idx;
56675669

5668-
for (idx = current_funccal->fc_defer.ga_len - 1; idx >= 0; --idx)
5670+
for (idx = funccal->fc_defer.ga_len - 1; idx >= 0; --idx)
56695671
{
56705672
funcexe_T funcexe;
56715673
typval_T rettv;
5672-
defer_T *dr = ((defer_T *)current_funccal->fc_defer.ga_data) + idx;
5674+
defer_T *dr = ((defer_T *)funccal->fc_defer.ga_data) + idx;
56735675
int i;
56745676

56755677
CLEAR_FIELD(funcexe);
@@ -5683,7 +5685,29 @@ handle_defer(void)
56835685
for (i = dr->dr_argcount - 1; i >= 0; --i)
56845686
clear_tv(&dr->dr_argvars[i]);
56855687
}
5686-
ga_clear(&current_funccal->fc_defer);
5688+
ga_clear(&funccal->fc_defer);
5689+
}
5690+
5691+
/*
5692+
* Called when exiting: call all defer functions.
5693+
*/
5694+
void
5695+
invoke_all_defer(void)
5696+
{
5697+
funccall_T *funccal;
5698+
5699+
for (funccal = current_funccal; funccal != NULL; funccal = funccal->caller)
5700+
if (funccal->fc_ectx != NULL)
5701+
{
5702+
// :def function
5703+
unwind_def_callstack(funccal->fc_ectx);
5704+
may_invoke_defer_funcs(funccal->fc_ectx);
5705+
}
5706+
else
5707+
{
5708+
// legacy function
5709+
handle_defer_one(funccal);
5710+
}
56875711
}
56885712

56895713
/*

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,8 @@ static char *(features[]) =
703703

704704
static int included_patches[] =
705705
{ /* Add new patch number below this line */
706+
/**/
707+
397,
706708
/**/
707709
396,
708710
/**/

src/vim9execute.c

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5171,13 +5171,7 @@ exec_instructions(ectx_T *ectx)
51715171
done:
51725172
ret = OK;
51735173
theend:
5174-
{
5175-
dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data)
5176-
+ ectx->ec_dfunc_idx;
5177-
5178-
if (dfunc->df_defer_var_idx > 0)
5179-
invoke_defer_funcs(ectx);
5180-
}
5174+
may_invoke_defer_funcs(ectx);
51815175

51825176
dict_stack_clear(dict_stack_len_at_start);
51835177
ectx->ec_trylevel_at_start = save_trylevel_at_start;
@@ -5258,6 +5252,7 @@ call_def_function(
52585252
int argc_arg, // nr of arguments
52595253
typval_T *argv, // arguments
52605254
partial_T *partial, // optional partial for context
5255+
funccall_T *funccal,
52615256
typval_T *rettv) // return value
52625257
{
52635258
ectx_T ectx; // execution context
@@ -5494,6 +5489,10 @@ call_def_function(
54945489
ectx.ec_instr = INSTRUCTIONS(dfunc);
54955490
}
54965491

5492+
// Store the execution context in funccal, used by invoke_all_defer().
5493+
if (funccal != NULL)
5494+
funccal->fc_ectx = &ectx;
5495+
54975496
// Following errors are in the function, not the caller.
54985497
// Commands behave like vim9script.
54995498
estack_push_ufunc(ufunc, 1);
@@ -5537,8 +5536,7 @@ call_def_function(
55375536
}
55385537

55395538
// When failed need to unwind the call stack.
5540-
while (ectx.ec_frame_idx != ectx.ec_initial_frame_idx)
5541-
func_return(&ectx);
5539+
unwind_def_callstack(&ectx);
55425540

55435541
// Deal with any remaining closures, they may be in use somewhere.
55445542
if (ectx.ec_funcrefs.ga_len > 0)
@@ -5603,6 +5601,30 @@ call_def_function(
56035601
return ret;
56045602
}
56055603

5604+
/*
5605+
* Called when a def function has finished (possibly failed).
5606+
* Invoke all the function returns to clean up and invoke deferred functions,
5607+
* except the toplevel one.
5608+
*/
5609+
void
5610+
unwind_def_callstack(ectx_T *ectx)
5611+
{
5612+
while (ectx->ec_frame_idx != ectx->ec_initial_frame_idx)
5613+
func_return(ectx);
5614+
}
5615+
5616+
/*
5617+
* Invoke any deffered functions for the top function in "ectx".
5618+
*/
5619+
void
5620+
may_invoke_defer_funcs(ectx_T *ectx)
5621+
{
5622+
dfunc_T *dfunc = ((dfunc_T *)def_functions.ga_data) + ectx->ec_dfunc_idx;
5623+
5624+
if (dfunc->df_defer_var_idx > 0)
5625+
invoke_defer_funcs(ectx);
5626+
}
5627+
56065628
/*
56075629
* List instructions "instr" up to "instr_count" or until ISN_FINISH.
56085630
* "ufunc" has the source lines, NULL for the instructions of ISN_SUBSTITUTE.

0 commit comments

Comments
 (0)