patch 8.2.3609: internal error when ModeChanged is triggered recursively

Problem:    Internal error when ModeChanged is triggered when v:event is
            already in use.
Solution:   Save and restore v:event if needed.
diff --git a/src/insexpand.c b/src/insexpand.c
index c993d96..e9b4861 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -962,7 +962,7 @@
     return (i >= 2);
 }
 
-#ifdef FEAT_EVAL
+#if defined(FEAT_EVAL) || defined(PROTO)
 /*
  * Allocate Dict for the completed item.
  * { word, abbr, menu, kind, info }
@@ -993,17 +993,18 @@
     dict_T	    *v_event;
     dict_T	    *item;
     static int	    recursive = FALSE;
+    save_v_event_T  save_v_event;
 
     if (recursive)
 	return;
 
-    v_event = get_vim_var_dict(VV_EVENT);
     if (cur < 0)
 	item = dict_alloc();
     else
 	item = ins_compl_dict_alloc(compl_curr_match);
     if (item == NULL)
 	return;
+    v_event = get_v_event(&save_v_event);
     dict_add_dict(v_event, "completed_item", item);
     pum_set_event_info(v_event);
     dict_set_items_ro(v_event);
@@ -1014,8 +1015,7 @@
     textwinlock--;
     recursive = FALSE;
 
-    dict_free_contents(v_event);
-    hash_init(&v_event->dv_hashtab);
+    restore_v_event(v_event, &save_v_event);
 }
 #endif
 
diff --git a/src/misc1.c b/src/misc1.c
index 58f515d..e35ba98 100644
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -2654,18 +2654,52 @@
     return path_is_url(p);
 }
 
+#if defined(FEAT_EVAL) || defined(PROTO)
+/*
+ * Return the dictionary of v:event.
+ * Save and clear the value in case it already has items.
+ */
+    dict_T *
+get_v_event(save_v_event_T *sve)
+{
+    dict_T	*v_event = get_vim_var_dict(VV_EVENT);
+
+    if (v_event->dv_hashtab.ht_used > 0)
+    {
+	// recursive use of v:event, save, make empty and restore later
+	sve->sve_did_save = TRUE;
+	sve->sve_hashtab = v_event->dv_hashtab;
+	hash_init(&v_event->dv_hashtab);
+    }
+    else
+	sve->sve_did_save = FALSE;
+    return v_event;
+}
+
+    void
+restore_v_event(dict_T *v_event, save_v_event_T *sve)
+{
+    dict_free_contents(v_event);
+    if (sve->sve_did_save)
+	v_event->dv_hashtab = sve->sve_hashtab;
+    else
+	hash_init(&v_event->dv_hashtab);
+}
+#endif
+
 /*
  * Fires a ModeChanged autocmd
  */
     void
 trigger_modechanged()
 {
-#if defined(FEAT_EVAL) || defined(PROTO)
+#ifdef FEAT_EVAL
     dict_T	    *v_event;
     typval_T	    rettv;
     typval_T	    tv[2];
     char_u	    *pat_pre;
     char_u	    *pat;
+    save_v_event_T  save_v_event;
 
     if (!has_modechanged())
 	return;
@@ -2680,7 +2714,7 @@
 	return;
     }
 
-    v_event = get_vim_var_dict(VV_EVENT);
+    v_event = get_v_event(&save_v_event);
     (void)dict_add_string(v_event, "new_mode", rettv.vval.v_string);
     (void)dict_add_string(v_event, "old_mode", last_mode);
     dict_set_items_ro(v_event);
@@ -2694,8 +2728,7 @@
     STRCPY(last_mode, rettv.vval.v_string);
 
     vim_free(pat);
-    dict_free_contents(v_event);
-    hash_init(&v_event->dv_hashtab);
+    restore_v_event(v_event, &save_v_event);
     vim_free(rettv.vval.v_string);
 #endif
 }
diff --git a/src/proto/misc1.pro b/src/proto/misc1.pro
index 7d4c41f..db60a6d 100644
--- a/src/proto/misc1.pro
+++ b/src/proto/misc1.pro
@@ -47,5 +47,7 @@
 char_u *get_isolated_shell_name(void);
 int path_is_url(char_u *p);
 int path_with_url(char_u *fname);
+dict_T *get_v_event(save_v_event_T *sve);
+void restore_v_event(dict_T *v_event, save_v_event_T *sve);
 void trigger_modechanged(void);
 /* vim: set ft=c : */
diff --git a/src/register.c b/src/register.c
index 129c80d..268c839 100644
--- a/src/register.c
+++ b/src/register.c
@@ -991,17 +991,18 @@
     void
 yank_do_autocmd(oparg_T *oap, yankreg_T *reg)
 {
-    static int	recursive = FALSE;
-    dict_T	*v_event;
-    list_T	*list;
-    int		n;
-    char_u	buf[NUMBUFLEN + 2];
-    long	reglen = 0;
+    static int	    recursive = FALSE;
+    dict_T	    *v_event;
+    list_T	    *list;
+    int		    n;
+    char_u	    buf[NUMBUFLEN + 2];
+    long	    reglen = 0;
+    save_v_event_T  save_v_event;
 
     if (recursive)
 	return;
 
-    v_event = get_vim_var_dict(VV_EVENT);
+    v_event = get_v_event(&save_v_event);
 
     list = list_alloc();
     if (list == NULL)
@@ -1045,8 +1046,7 @@
     recursive = FALSE;
 
     // Empty the dictionary, v:event is still valid
-    dict_free_contents(v_event);
-    hash_init(&v_event->dv_hashtab);
+    restore_v_event(v_event, &save_v_event);
 }
 #endif
 
diff --git a/src/structs.h b/src/structs.h
index 8ffebf3..7bd1ff7 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -4465,3 +4465,8 @@
 
 #define WHERE_INIT {NULL, 0, 0}
 
+// Struct passed to get_v_event() and restore_v_event().
+typedef struct {
+    int		sve_did_save;
+    hashtab_T	sve_hashtab;
+} save_v_event_T;
diff --git a/src/testdir/test_edit.vim b/src/testdir/test_edit.vim
index 0abc47a..957f248 100644
--- a/src/testdir/test_edit.vim
+++ b/src/testdir/test_edit.vim
@@ -2034,6 +2034,12 @@
   unlet! g:i_to_any
 endfunc
 
+func Test_recursive_ModeChanged()
+  au! ModeChanged * norm 0u
+  sil! norm 
+  au!
+endfunc
+
 " Test toggling of input method. See :help i_CTRL-^
 func Test_edit_CTRL_hat()
   CheckFeature xim
diff --git a/src/version.c b/src/version.c
index e5146bc..55928db 100644
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3609,
+/**/
     3608,
 /**/
     3607,