changeset 26155:f2392648af3e v8.2.3609

patch 8.2.3609: internal error when ModeChanged is triggered recursively Commit: https://github.com/vim/vim/commit/3075a45592fe76f2febb6321632a23e352efe949 Author: Bram Moolenaar <Bram@vim.org> Date: Wed Nov 17 15:51:52 2021 +0000 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.
author Bram Moolenaar <Bram@vim.org>
date Wed, 17 Nov 2021 17:00:05 +0100
parents fa84cd94d8b6
children ebedb61a4b9b
files src/insexpand.c src/misc1.c src/proto/misc1.pro src/register.c src/structs.h src/testdir/test_edit.vim src/version.c
diffstat 7 files changed, 65 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -962,7 +962,7 @@ pum_enough_matches(void)
     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 @@ trigger_complete_changed_event(int cur)
     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 @@ trigger_complete_changed_event(int cur)
     textwinlock--;
     recursive = FALSE;
 
-    dict_free_contents(v_event);
-    hash_init(&v_event->dv_hashtab);
+    restore_v_event(v_event, &save_v_event);
 }
 #endif
 
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -2654,18 +2654,52 @@ path_with_url(char_u *fname)
     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 @@ trigger_modechanged()
 	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 @@ trigger_modechanged()
     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
 }
--- a/src/proto/misc1.pro
+++ b/src/proto/misc1.pro
@@ -47,5 +47,7 @@ int goto_im(void);
 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 : */
--- a/src/register.c
+++ b/src/register.c
@@ -991,17 +991,18 @@ shift_delete_registers()
     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 @@ yank_do_autocmd(oparg_T *oap, yankreg_T 
     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
 
--- a/src/structs.h
+++ b/src/structs.h
@@ -4465,3 +4465,8 @@ typedef struct {
 
 #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;
--- a/src/testdir/test_edit.vim
+++ b/src/testdir/test_edit.vim
@@ -2034,6 +2034,12 @@ func Test_mode_changes()
   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
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3609,
+/**/
     3608,
 /**/
     3607,