]> granicus.if.org Git - vim/commitdiff
patch 9.0.0579: using freed memory when 'tagfunc' wipes out buffer v9.0.0579
authorBram Moolenaar <Bram@vim.org>
Sat, 24 Sep 2022 18:20:30 +0000 (19:20 +0100)
committerBram Moolenaar <Bram@vim.org>
Sat, 24 Sep 2022 18:20:30 +0000 (19:20 +0100)
Problem:    Using freed memory when 'tagfunc' wipes out buffer that holds
            'complete'.
Solution:   Make a copy of the option.  Make sure cursor position is valid.

src/insexpand.c
src/move.c
src/testdir/test_ins_complete.vim
src/version.c

index 37162a48ce5a3c32ea2329f78012c81eb090f3ee..d3a1f881ea1f090ed0f4e17aa6c014536d3d0f54 100644 (file)
@@ -2490,7 +2490,8 @@ ins_compl_next_buf(buf_T *buf, int flag)
 
     if (flag == 'w')           // just windows
     {
-       if (buf == curbuf || wp == NULL)  // first call for this flag/expansion
+       if (buf == curbuf || !win_valid(wp))
+           // first call for this flag/expansion or window was closed
            wp = curwin;
        while ((wp = (wp->w_next != NULL ? wp->w_next : firstwin)) != curwin
                && wp->w_buffer->b_scanned)
@@ -3188,9 +3189,10 @@ enum
  */
 typedef struct
 {
-    char_u     *e_cpt;                 // current entry in 'complete'
+    char_u     *e_cpt_copy;            // copy of 'complete'
+    char_u     *e_cpt;                 // current entry in "e_cpt_copy"
     buf_T      *ins_buf;               // buffer being scanned
-    pos_T      *cur_match_pos;                 // current match position
+    pos_T      *cur_match_pos;         // current match position
     pos_T      prev_match_pos;         // previous match position
     int                set_match_pos;          // save first_match_pos/last_match_pos
     pos_T      first_match_pos;        // first match position
@@ -3257,7 +3259,8 @@ process_next_cpt_value(
        st->set_match_pos = TRUE;
     }
     else if (vim_strchr((char_u *)"buwU", *st->e_cpt) != NULL
-           && (st->ins_buf = ins_compl_next_buf(st->ins_buf, *st->e_cpt)) != curbuf)
+           && (st->ins_buf = ins_compl_next_buf(
+                                          st->ins_buf, *st->e_cpt)) != curbuf)
     {
        // Scan a buffer, but not the current one.
        if (st->ins_buf->b_ml.ml_mfp != NULL)   // loaded buffer
@@ -3756,19 +3759,30 @@ get_next_completion_match(int type, ins_compl_next_state_T *st, pos_T *ini)
     static int
 ins_compl_get_exp(pos_T *ini)
 {
-    static ins_compl_next_state_T st;
+    static ins_compl_next_state_T   st;
+    static int                     st_cleared = FALSE;
     int                i;
     int                found_new_match;
     int                type = ctrl_x_mode;
 
     if (!compl_started)
     {
-       FOR_ALL_BUFFERS(st.ins_buf)
-           st.ins_buf->b_scanned = 0;
+       buf_T *buf;
+
+       FOR_ALL_BUFFERS(buf)
+           buf->b_scanned = 0;
+       if (!st_cleared)
+       {
+           CLEAR_FIELD(st);
+           st_cleared = TRUE;
+       }
        st.found_all = FALSE;
        st.ins_buf = curbuf;
-       st.e_cpt = (compl_cont_status & CONT_LOCAL)
-                                           ? (char_u *)"." : curbuf->b_p_cpt;
+       vim_free(st.e_cpt_copy);
+       // Make a copy of 'complete', if case the buffer is wiped out.
+       st.e_cpt_copy = vim_strsave((compl_cont_status & CONT_LOCAL)
+                                           ? (char_u *)"." : curbuf->b_p_cpt);
+       st.e_cpt = st.e_cpt_copy == NULL ? (char_u *)"" : st.e_cpt_copy;
        st.last_match_pos = st.first_match_pos = *ini;
     }
     else if (st.ins_buf != curbuf && !buf_valid(st.ins_buf))
@@ -4112,6 +4126,7 @@ ins_compl_next(
     int            todo = count;
     int            advance;
     int            started = compl_started;
+    buf_T   *orig_curbuf = curbuf;
 
     // When user complete function return -1 for findstart which is next
     // time of 'always', compl_shown_match become NULL.
@@ -4144,6 +4159,13 @@ ins_compl_next(
                                                        &num_matches) == -1)
        return -1;
 
+    if (curbuf != orig_curbuf)
+    {
+       // In case some completion function switched buffer, don't want to
+       // insert the completion elsewhere.
+       return -1;
+    }
+
     // Insert the text of the new completion, or the compl_leader.
     if (compl_no_insert && !started)
     {
index a51d2d128e86df2554024d7150ab3dad2c5e6632..967dd77fb971a3e3b0a476998d58f35a7f20b423 100644 (file)
@@ -683,6 +683,7 @@ cursor_valid(void)
     void
 validate_cursor(void)
 {
+    check_cursor();
     check_cursor_moved(curwin);
     if ((curwin->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW))
        curs_columns(TRUE);
index 379db7fe7965b97fb7de5ee3e0e542721c41526a..3fc55ec1cdc952bafd26efc9ca63172542b977ce 100644 (file)
@@ -547,9 +547,8 @@ func Test_pum_with_preview_win()
 
   call writefile(lines, 'Xpreviewscript')
   let buf = RunVimInTerminal('-S Xpreviewscript', #{rows: 12})
-  call TermWait(buf, 50)
   call term_sendkeys(buf, "Gi\<C-X>\<C-O>")
-  call TermWait(buf, 100)
+  call TermWait(buf, 200)
   call term_sendkeys(buf, "\<C-N>")
   call VerifyScreenDump(buf, 'Test_pum_with_preview_win', {})
 
@@ -2172,4 +2171,21 @@ func Test_ins_complete_end_of_line()
   bwipe!
 endfunc
 
+func s:Tagfunc(t,f,o)
+  bwipe!
+  return []
+endfunc
+
+" This was using freed memory, since 'complete' was in a wiped out buffer.
+" Also using a window that was closed.
+func Test_tagfunc_wipes_out_buffer()
+  new
+  set complete=.,t,w,b,u,i
+  se tagfunc=s:Tagfunc
+  sil norm i\ e
+
+  bwipe!
+endfunc
+
+
 " vim: shiftwidth=2 sts=2 expandtab
index ff8264415d606a58f8b1ea98098cab985f3612ce..1061e4c67fa654b61369d457e496e0e580abe7e1 100644 (file)
@@ -699,6 +699,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    579,
 /**/
     578,
 /**/