diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 2c51d64972..0711d82fe5 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3144,7 +3144,7 @@ static char *sub_grow_buf(char **new_start, int *new_start_len, int needed_len) // substitution into (and some extra space to avoid // too many calls to xmalloc()/free()). *new_start_len = needed_len + 50; - *new_start = xmalloc((size_t)(*new_start_len)); + *new_start = xcalloc(1, (size_t)(*new_start_len)); **new_start = NUL; new_end = *new_start; } else { @@ -3154,8 +3154,11 @@ static char *sub_grow_buf(char **new_start, int *new_start_len, int needed_len) size_t len = strlen(*new_start); needed_len += (int)len; if (needed_len > *new_start_len) { + size_t prev_new_start_len = (size_t)(*new_start_len); *new_start_len = needed_len + 50; + size_t added_len = (size_t)(*new_start_len) - prev_new_start_len; *new_start = xrealloc(*new_start, (size_t)(*new_start_len)); + memset(*new_start + prev_new_start_len, 0, added_len); } new_end = *new_start + len; } diff --git a/src/nvim/option.c b/src/nvim/option.c index 7a7cda2fa0..ba9d1262d4 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1455,7 +1455,7 @@ int do_set(char *arg, int opt_flags) } else { char *startarg = arg; // remember for error message const char *errmsg = NULL; - char errbuf[80]; + char errbuf[ERR_BUFLEN]; do_one_set_option(opt_flags, &arg, &did_show, errbuf, sizeof(errbuf), &errmsg); @@ -3845,7 +3845,7 @@ const char *set_option_value(const char *const name, const OptVal value, int opt int opt_idx = findoption(name); if (opt_idx < 0) { - snprintf(errbuf, IOSIZE, _(e_unknown_option2), name); + snprintf(errbuf, sizeof(errbuf), _(e_unknown_option2), name); return errbuf; } diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index b2e8081a08..6d0401f319 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -66,6 +66,7 @@ typedef struct { /// is parameterized, then the "os_errbuf" buffer is used to store the error /// message (when it is not NULL). char *os_errbuf; + /// length of the error buffer size_t os_errbuflen; void *os_win; diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index b0e9ff9434..66185940ca 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -938,6 +938,8 @@ enum { // Value for b_p_ul indicating the global value must be used. #define NO_LOCAL_UNDOLEVEL (-123456) +#define ERR_BUFLEN 80 + #define SB_MAX 100000 // Maximum 'scrollback' value. #define MAX_NUMBERWIDTH 20 // used for 'numberwidth' and 'statuscolumn' diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 0a7d77e817..544524dd42 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -442,7 +442,7 @@ int check_signcolumn(win_T *wp) const char *check_stl_option(char *s) { int groupdepth = 0; - static char errbuf[80]; + static char errbuf[ERR_BUFLEN]; while (*s) { // Check for valid keys after % sequences diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index bdac5aa587..efd647a5c3 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -1941,6 +1941,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun // - Skip the byte if it's equal to the byte in the word, // accepting that byte is always better. n += sp->ts_curi++; + + // break out, if we would be accessing byts buffer out of bounds + if (byts == slang->sl_fbyts && n >= slang->sl_fbyts_len) { + got_int = true; + break; + } c = byts[n]; if (soundfold && sp->ts_twordlen == 0 && c == '*') { // Inserting a vowel at the start of a word counts less, diff --git a/src/nvim/window.c b/src/nvim/window.c index 7728efde33..ef1ef89d95 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4470,11 +4470,12 @@ void tabpage_move(int nr) redraw_tabline = true; } -// Go to another window. -// When jumping to another buffer, stop Visual mode. Do this before -// changing windows so we can yank the selection into the '*' register. -// When jumping to another window on the same buffer, adjust its cursor -// position to keep the same Visual area. +/// Go to another window. +/// When jumping to another buffer, stop Visual mode. Do this before +/// changing windows so we can yank the selection into the '*' register. +/// (note: this may trigger ModeChanged autocommand!) +/// When jumping to another window on the same buffer, adjust its cursor +/// position to keep the same Visual area. void win_goto(win_T *wp) { win_T *owp = curwin; @@ -4485,11 +4486,17 @@ void win_goto(win_T *wp) } if (wp->w_buffer != curbuf) { + // careful: triggers ModeChanged autocommand reset_VIsual_and_resel(); } else if (VIsual_active) { wp->w_cursor = curwin->w_cursor; } + // autocommand may have made wp invalid + if (!win_valid(wp)) { + return; + } + win_enter(wp, true); // Conceal cursor line in previous window, unconceal in current window. diff --git a/test/functional/legacy/crash_spec.lua b/test/functional/legacy/crash_spec.lua index 5094f81847..094bea253e 100644 --- a/test/functional/legacy/crash_spec.lua +++ b/test/functional/legacy/crash_spec.lua @@ -6,7 +6,6 @@ local feed = helpers.feed before_each(clear) --- oldtest: Test_crash1() it('no crash when ending Visual mode while editing buffer closes window', function() command('new') command('autocmd ModeChanged v:n ++once close') @@ -14,3 +13,21 @@ it('no crash when ending Visual mode while editing buffer closes window', functi command('enew') assert_alive() end) + +it('no crash when ending Visual mode close the window to switch to', function() + command('new') + command('autocmd ModeChanged v:n ++once only') + feed('v') + command('wincmd p') + assert_alive() +end) + +it('no crash when truncating overlong message', function() + pcall(command, 'source test/old/testdir/crash/vim_msg_trunc_poc') + assert_alive() +end) + +it('no crash with very long option error message', function() + pcall(command, 'source test/old/testdir/crash/poc_did_set_langmap') + assert_alive() +end) diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua index e0cc1dc79c..146b00acb0 100644 --- a/test/functional/legacy/messages_spec.lua +++ b/test/functional/legacy/messages_spec.lua @@ -801,9 +801,4 @@ describe('messages', function() ]]) os.remove('b.txt') end) - - it('no crash when truncating overlong message', function() - pcall(command, 'source test/old/testdir/crash/vim_msg_trunc_poc') - assert_alive() - end) end) diff --git a/test/old/testdir/crash/poc_did_set_langmap b/test/old/testdir/crash/poc_did_set_langmap new file mode 100644 index 0000000000..f77145b9d1 --- /dev/null +++ b/test/old/testdir/crash/poc_did_set_langmap @@ -0,0 +1 @@ +se lmap=°xÿ7sil;drlmap=°xÿ7sil;drmo: pm31 3" \ No newline at end of file diff --git a/test/old/testdir/crash/poc_ex_substitute b/test/old/testdir/crash/poc_ex_substitute new file mode 100644 index 0000000000..bcf1286512 Binary files /dev/null and b/test/old/testdir/crash/poc_ex_substitute differ diff --git a/test/old/testdir/crash/poc_suggest_trie_walk b/test/old/testdir/crash/poc_suggest_trie_walk new file mode 100644 index 0000000000..c79b6eeb5c Binary files /dev/null and b/test/old/testdir/crash/poc_suggest_trie_walk differ diff --git a/test/old/testdir/crash/poc_win_enter_ext b/test/old/testdir/crash/poc_win_enter_ext new file mode 100644 index 0000000000..73f53b575b Binary files /dev/null and b/test/old/testdir/crash/poc_win_enter_ext differ diff --git a/test/old/testdir/test_crash.vim b/test/old/testdir/test_crash.vim index b093b053c5..49e712a901 100644 --- a/test/old/testdir/test_crash.vim +++ b/test/old/testdir/test_crash.vim @@ -117,7 +117,7 @@ func Test_crash1_2() " The following used to crash Vim let opts = #{cmd: 'sh'} let vim = GetVimProg() - let result = 'X_crash1_1_result.txt' + let result = 'X_crash1_2_result.txt' let buf = RunVimInTerminal('sh', opts) @@ -128,6 +128,38 @@ func Test_crash1_2() \ ' && echo "crash 1: [OK]" > '.. result .. "\") call TermWait(buf, 150) + let file = 'crash/poc_win_enter_ext' + 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 2: [OK]" >> '.. result .. "\") + call TermWait(buf, 350) + + let file = 'crash/poc_suggest_trie_walk' + 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 3: [OK]" >> '.. result .. "\") + call TermWait(buf, 150) + + let file = 'crash/poc_did_set_langmap' + let cmn_args = "%s -u NONE -i NONE -n -X -m -n -e -s -S %s -c ':qa!'" + let args = printf(cmn_args, vim, file) + call term_sendkeys(buf, args .. + \ ' ; echo "crash 4: [OK]" >> '.. result .. "\") + call TermWait(buf, 150) + + let file = 'crash/poc_ex_substitute' + let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'" + let args = printf(cmn_args, vim, file) + " just make sure it runs, we don't care about the resulting echo + call term_sendkeys(buf, args .. "\") + " There is no output generated in Github CI for the asan clang build. + " so just skip generating the ouput. + " call term_sendkeys(buf, args .. + " \ ' && echo "crash 5: [OK]" >> '.. result .. "\") + call TermWait(buf, 150) + " clean up exe buf .. "bw!" @@ -135,6 +167,9 @@ func Test_crash1_2() let expected = [ \ 'crash 1: [OK]', + \ 'crash 2: [OK]', + \ 'crash 3: [OK]', + \ 'crash 4: [OK]', \ ] call assert_equal(expected, getline(1, '$'))