diff --git a/src/insexpand.c b/src/insexpand.c index 8d76c68..2ce675d 100644 --- a/src/insexpand.c +++ b/src/insexpand.c @@ -2162,7 +2162,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) @@ -2675,10 +2676,12 @@ ins_compl_get_exp(pos_T *ini) { static pos_T first_match_pos; static pos_T last_match_pos; - static char_u *e_cpt = (char_u *)""; // curr. entry in 'complete' + static char_u *e_cpt; // curr. entry in 'complete' + static char_u *e_cpt_copy; // copy of curr. entry in 'complete' static int found_all = FALSE; // Found all matches of a // certain type. static buf_T *ins_buf = NULL; // buffer being scanned + static int st_cleared = FALSE; pos_T *pos; char_u **matches; @@ -2697,12 +2700,34 @@ ins_compl_get_exp(pos_T *ini) if (!compl_started) { - FOR_ALL_BUFFERS(ins_buf) - ins_buf->b_scanned = 0; + buf_T *buf; + + FOR_ALL_BUFFERS(buf) + buf->b_scanned = 0; + if (!st_cleared) + { + // this code does not match upstream commit, upstream clears struct `st` + // which includes the members which are arguments of `CLEAR_FIELD()` macros + // below. The code below should do the same thing as upstream `CLEAR_FIELD(st)` + CLEAR_FIELD(e_cpt); + CLEAR_FIELD(e_cpt_copy); + CLEAR_FIELD(ins_buf); + CLEAR_FIELD(pos); + CLEAR_FIELD(set_match_pos); + CLEAR_FIELD(first_match_pos); + CLEAR_FIELD(last_match_pos); + CLEAR_FIELD(found_all); + CLEAR_FIELD(dict); + CLEAR_FIELD(dict_f); + st_cleared = TRUE; + } found_all = FALSE; ins_buf = curbuf; - e_cpt = (compl_cont_status & CONT_LOCAL) - ? (char_u *)"." : curbuf->b_p_cpt; + vim_free(e_cpt_copy); + // Make a copy of 'complete', if case the buffer is wiped out. + e_cpt_copy = vim_strsave((compl_cont_status & CONT_LOCAL) + ? (char_u *)"." : curbuf->b_p_cpt); + e_cpt = e_cpt_copy == NULL ? (char_u *)"" : e_cpt_copy; last_match_pos = first_match_pos = *ini; } else if (ins_buf != curbuf && !buf_valid(ins_buf)) @@ -3100,7 +3125,7 @@ ins_compl_get_exp(pos_T *ini) else { // Mark a buffer scanned when it has been scanned completely - if (type == 0 || type == CTRL_X_PATH_PATTERNS) + if (buf_valid(ins_buf) && (type == 0 || type == CTRL_X_PATH_PATTERNS)) ins_buf->b_scanned = TRUE; compl_started = FALSE; @@ -3210,6 +3235,7 @@ ins_compl_next( int found_end = FALSE; 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. @@ -3342,6 +3368,13 @@ ins_compl_next( } } + 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) { diff --git a/src/move.c b/src/move.c index 8ac1d0e..55cbd24 100644 --- a/src/move.c +++ b/src/move.c @@ -649,6 +649,7 @@ cursor_valid(void) void validate_cursor(void) { + check_cursor_lnum(); check_cursor_moved(curwin); if ((curwin->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW)) curs_columns(TRUE); diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index 108c528..e800864 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -92,6 +92,7 @@ NEW_TESTS = \ test_conceal \ test_const \ test_cpoptions \ + test_crash \ test_crypt \ test_cscope \ test_cursor_func \ @@ -348,6 +349,7 @@ NEW_TESTS_RES = \ test_conceal.res \ test_const.res \ test_cpoptions.res \ + test_crash.res \ test_crypt.res \ test_cscope.res \ test_cursor_func.res \ diff --git a/src/testdir/crash/poc_tagfunc.vim b/src/testdir/crash/poc_tagfunc.vim new file mode 100644 index 0000000..49d9b6f --- /dev/null +++ b/src/testdir/crash/poc_tagfunc.vim @@ -0,0 +1,6 @@ +fu Tagfunc(t,f,o) + bw +endf +set tagfunc=Tagfunc +n0 +sil0norm0i diff --git a/src/testdir/term_util.vim b/src/testdir/term_util.vim index 8541ef3..3a06019 100644 --- a/src/testdir/term_util.vim +++ b/src/testdir/term_util.vim @@ -54,6 +54,7 @@ endfunc " "cols" - width of the terminal window (max. 78) " "statusoff" - number of lines the status is offset from default " "wait_for_ruler" - if zero then don't wait for ruler to show +" "cmd" - run any other command, e.g. "xxd" (used in xxd test) func RunVimInTerminal(arguments, options) " If Vim doesn't exit a swap file remains, causing other tests to fail. " Remove it here. @@ -88,7 +89,11 @@ func RunVimInTerminal(arguments, options) let reset_u7 = ' --cmd "set t_u7=" ' endif - let cmd = GetVimCommandCleanTerm() .. reset_u7 .. a:arguments + if empty(get(a:options, 'cmd', '')) + let cmd = GetVimCommandCleanTerm() .. reset_u7 .. a:arguments + else + let cmd = get(a:options, 'cmd') + endif let options = #{curwin: 1} if &termwinsize == '' @@ -114,7 +119,7 @@ func RunVimInTerminal(arguments, options) call TermWait(buf) - if get(a:options, 'wait_for_ruler', 1) + if get(a:options, 'wait_for_ruler', 1) && empty(get(a:options, 'cmd', '')) " Wait for "All" or "Top" of the ruler to be shown in the last line or in " the status line of the last window. This can be quite slow (e.g. when " using valgrind). diff --git a/src/testdir/test_crash.vim b/src/testdir/test_crash.vim new file mode 100644 index 0000000..4be1b49 --- /dev/null +++ b/src/testdir/test_crash.vim @@ -0,0 +1,40 @@ +" Some tests, that used to crash Vim +source check.vim +source screendump.vim + +CheckScreendump + +func Test_crash1() + if !executable('sh') + throw 'Skipped: sh not executable!' + endif + " The following used to crash Vim + let opts = #{cmd: 'sh'} + let vim = GetVimProg() + + let buf = RunVimInTerminal('sh', opts) + + let file = 'crash/poc_tagfunc.vim' + let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'" + let args = printf(cmn_args, vim, file) + call term_sendkeys(buf, args .. + \ ' || echo "crash 5: [OK]" >> X_crash1_result.txt' .. "\") + + call TermWait(buf, 100) + + " clean up + exe buf .. "bw!" + + sp X_crash1_result.txt + + let expected = [ + \ 'crash 5: [OK]', + \ ] + + call assert_equal(expected, getline(1, '$')) + bw! + + call delete('X_crash1_result.txt') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim index 613e952..24232d8 100644 --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -496,9 +496,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\\") - call TermWait(buf, 100) + call TermWait(buf, 200) call term_sendkeys(buf, "\") call VerifyScreenDump(buf, 'Test_pum_with_preview_win', {}) @@ -724,7 +723,23 @@ func Test_z1_complete_no_history() exe "normal owh\\" exe "normal owh\" call assert_equal(currmess, execute('messages')) - close! + 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␎ + bwipe! endfunc + " vim: shiftwidth=2 sts=2 expandtab