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
index 6b2d86b..5ec5b8a 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -263,8 +263,9 @@
 	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 7a42463..2a2dcb0 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1583,6 +1583,11 @@
     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 074a2b8..4fbbe86 100644
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -59,7 +59,7 @@
 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 5203304..d43b1e2 100644
--- a/src/proto/vim9execute.pro
+++ b/src/proto/vim9execute.pro
@@ -13,7 +13,9 @@
 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 8fab297..e627719 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1753,7 +1753,11 @@
     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 4ea7711..d7cfae1 100644
--- a/src/testdir/test_user_func.vim
+++ b/src/testdir/test_user_func.vim
@@ -581,5 +581,49 @@
   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 0ee276b..8956b82 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -33,6 +33,7 @@
 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 @@
 	    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 @@
 				     DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
 
     // Invoke functions added with ":defer".
-    handle_defer();
+    handle_defer_one(current_funccal);
 
     --RedrawingDisabled;
 
@@ -5660,16 +5662,16 @@
 /*
  * 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 @@
 	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 f117022..35184b2 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@
 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 08774fe..0b50ace 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -5171,13 +5171,7 @@
 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 @@
     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 @@
 	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 @@
     }
 
     // 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 @@
 }
 
 /*
+ * 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.