neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit 3a70fc8cb8fedf78b5e0d7c606d99967faad70ab
parent 78bbe53f7615e8b38d5289d9ce0579996109579b
Author: zeertzjq <zeertzjq@outlook.com>
Date:   Thu, 11 Dec 2025 12:46:45 +0800

Merge pull request #36906 from janlazo/vim-8.2.1618

vim-patch:8.2.{1618,2403,2404,2409,2411,2571,2834,4474}
Diffstat:
Msrc/nvim/quickfix.c | 52+++++++++++++++++++++++++++++++++++-----------------
Mtest/old/testdir/test_profile.vim | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mtest/old/testdir/test_quickfix.vim | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 293 insertions(+), 74 deletions(-)

diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c @@ -735,7 +735,7 @@ static int qf_get_next_str_line(qfstate_T *state) return QF_OK; } -/// Get the next string from state->p_Li. +/// Get the next string from the List item state->p_li. static int qf_get_next_list_line(qfstate_T *state) { listitem_T *p_li = state->p_li; @@ -1249,7 +1249,8 @@ static void qf_store_title(qf_list_T *qfl, const char *title) /// that created the quickfix list with the ":" prefix. /// Create a quickfix list title string by prepending ":" to a user command. /// Returns a pointer to a static buffer with the title. -static char *qf_cmdtitle(char *cmd) +static char *qf_cmdtitle(const char *cmd) + FUNC_ATTR_NONNULL_ALL { static char qftitle_str[IOSIZE]; @@ -6623,7 +6624,7 @@ static int qf_add_entry_from_dict(qf_list_T *qfl, dict_T *d, bool first_entry, b // If the 'valid' field is present it overrules the detected value. if (tv_dict_find(d, "valid", -1) != NULL) { - valid = tv_dict_get_number(d, "valid"); + valid = tv_dict_get_bool(d, "valid", false); } const int status = qf_add_entry(qfl, @@ -7344,30 +7345,27 @@ static char *cexpr_get_auname(cmdidx_T cmdidx) } } -/// ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command. -/// ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command. -void ex_cexpr(exarg_T *eap) +static int trigger_cexpr_autocmd(int cmdidx) { - char *au_name = cexpr_get_auname(eap->cmdidx); + char *au_name = cexpr_get_auname(cmdidx); if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, true, curbuf)) { if (aborting()) { - return; + return FAIL; } } + return OK; +} +int cexpr_core(const exarg_T *eap, typval_T *tv) + FUNC_ATTR_NONNULL_ALL +{ win_T *wp = NULL; qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp); - // Evaluate the expression. When the result is a string or a list we can - // use it to fill the errorlist. - typval_T *tv = eval_expr(eap->arg, eap); - if (tv == NULL) { - return; - } - if ((tv->v_type == VAR_STRING && tv->vval.v_string != NULL) || tv->v_type == VAR_LIST) { + char *au_name = cexpr_get_auname(eap->cmdidx); incr_quickfix_busy(); int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, tv, p_efm, (eap->cmdidx != CMD_caddexpr @@ -7376,7 +7374,7 @@ void ex_cexpr(exarg_T *eap) qf_cmdtitle(*eap->cmdlinep), NULL); if (qf_stack_empty(qi)) { decr_quickfix_busy(); - goto cleanup; + return FAIL; } if (res >= 0) { qf_list_changed(qf_get_curlist(qi)); @@ -7396,10 +7394,30 @@ void ex_cexpr(exarg_T *eap) qf_jump_first(qi, save_qfid, eap->forceit); } decr_quickfix_busy(); + return OK; } else { emsg(_("E777: String or List expected")); } -cleanup: + return FAIL; +} + +/// ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command. +/// ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command. +/// Also: ":caddexpr", ":cgetexpr", "laddexpr" and "laddexpr". +void ex_cexpr(exarg_T *eap) +{ + if (trigger_cexpr_autocmd(eap->cmdidx) == FAIL) { + return; + } + + // Evaluate the expression. When the result is a string or a list we can + // use it to fill the errorlist. + typval_T *tv = eval_expr(eap->arg, eap); + if (tv == NULL) { + return; + } + + (void)cexpr_core(eap, tv); tv_free(tv); } diff --git a/test/old/testdir/test_profile.vim b/test/old/testdir/test_profile.vim @@ -5,24 +5,30 @@ CheckFeature profile source shared.vim source screendump.vim +source vim9.vim func Test_profile_func() + call RunProfileFunc('func', 'let', 'let') + " call RunProfileFunc('def', 'var', '') +endfunc + +func RunProfileFunc(command, declare, assign) let lines =<< trim [CODE] profile start Xprofile_func.log profile func Foo* - func! Foo1() - endfunc - func! Foo2() - let l:count = 100 - while l:count > 0 - let l:count = l:count - 1 + XXX Foo1() + endXXX + XXX Foo2() + DDD counter = 100 + while counter > 0 + AAA counter = counter - 1 endwhile sleep 1m - endfunc - func! Foo3() - endfunc - func! Bar() - endfunc + endXXX + XXX Foo3() + endXXX + XXX Bar() + endXXX call Foo1() call Foo1() profile pause @@ -37,6 +43,10 @@ func Test_profile_func() delfunc Foo3 [CODE] + call map(lines, {k, v -> substitute(v, 'XXX', a:command, '') }) + call map(lines, {k, v -> substitute(v, 'DDD', a:declare, '') }) + call map(lines, {k, v -> substitute(v, 'AAA', a:assign, '') }) + call writefile(lines, 'Xprofile_func.vim') call system(GetVimCommand() \ . ' -es --clean' @@ -70,10 +80,10 @@ func Test_profile_func() call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[12]) call assert_equal('', lines[13]) call assert_equal('count total (s) self (s)', lines[14]) - call assert_match('^\s*1\s\+.*\slet l:count = 100$', lines[15]) - call assert_match('^\s*101\s\+.*\swhile l:count > 0$', lines[16]) - call assert_match('^\s*100\s\+.*\s let l:count = l:count - 1$', lines[17]) - call assert_match('^\s*101\s\+.*\sendwhile$', lines[18]) + call assert_match('^\s*1\s\+.*\s\(let\|var\) counter = 100$', lines[15]) + call assert_match('^\s*101\s\+.*\swhile counter > 0$', lines[16]) + call assert_match('^\s*100\s\+.*\s \(let\)\= counter = counter - 1$', lines[17]) + call assert_match('^\s*10[01]\s\+.*\sendwhile$', lines[18]) call assert_match('^\s*1\s\+.\+sleep 1m$', lines[19]) call assert_equal('', lines[20]) call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[21]) @@ -92,39 +102,47 @@ func Test_profile_func() endfunc func Test_profile_func_with_ifelse() + call Run_profile_func_with_ifelse('func', 'let') + " call Run_profile_func_with_ifelse('def', 'var') +endfunc + +func Run_profile_func_with_ifelse(command, declare) let lines =<< trim [CODE] - func! Foo1() + XXX Foo1() if 1 - let x = 0 + DDD x = 0 elseif 1 - let x = 1 + DDD x = 1 else - let x = 2 + DDD x = 2 endif - endfunc - func! Foo2() + endXXX + XXX Foo2() if 0 - let x = 0 + DDD x = 0 elseif 1 - let x = 1 + DDD x = 1 else - let x = 2 + DDD x = 2 endif - endfunc - func! Foo3() + endXXX + XXX Foo3() if 0 - let x = 0 + DDD x = 0 elseif 0 - let x = 1 + DDD x = 1 else - let x = 2 + DDD x = 2 endif - endfunc + endXXX call Foo1() call Foo2() call Foo3() [CODE] + call map(lines, {k, v -> substitute(v, 'XXX', a:command, '') }) + call map(lines, {k, v -> substitute(v, 'DDD', a:declare, '') }) + call writefile(lines, 'Xprofile_func.vim') call system(GetVimCommand() \ . ' -es -i NONE --noplugin' @@ -149,11 +167,11 @@ func Test_profile_func_with_ifelse() call assert_equal('', lines[5]) call assert_equal('count total (s) self (s)', lines[6]) call assert_match('^\s*1\s\+.*\sif 1$', lines[7]) - call assert_match('^\s*1\s\+.*\s let x = 0$', lines[8]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 0$', lines[8]) call assert_match( '^\s\+elseif 1$', lines[9]) - call assert_match( '^\s\+let x = 1$', lines[10]) + call assert_match( '^\s\+\(let\|var\) x = 1$', lines[10]) call assert_match( '^\s\+else$', lines[11]) - call assert_match( '^\s\+let x = 2$', lines[12]) + call assert_match( '^\s\+\(let\|var\) x = 2$', lines[12]) call assert_match('^\s*1\s\+.*\sendif$', lines[13]) call assert_equal('', lines[14]) call assert_equal('FUNCTION Foo2()', lines[15]) @@ -163,11 +181,11 @@ func Test_profile_func_with_ifelse() call assert_equal('', lines[20]) call assert_equal('count total (s) self (s)', lines[21]) call assert_match('^\s*1\s\+.*\sif 0$', lines[22]) - call assert_match( '^\s\+let x = 0$', lines[23]) + call assert_match( '^\s\+\(let\|var\) x = 0$', lines[23]) call assert_match('^\s*1\s\+.*\selseif 1$', lines[24]) - call assert_match('^\s*1\s\+.*\s let x = 1$', lines[25]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 1$', lines[25]) call assert_match( '^\s\+else$', lines[26]) - call assert_match( '^\s\+let x = 2$', lines[27]) + call assert_match( '^\s\+\(let\|var\) x = 2$', lines[27]) call assert_match('^\s*1\s\+.*\sendif$', lines[28]) call assert_equal('', lines[29]) call assert_equal('FUNCTION Foo3()', lines[30]) @@ -177,11 +195,11 @@ func Test_profile_func_with_ifelse() call assert_equal('', lines[35]) call assert_equal('count total (s) self (s)', lines[36]) call assert_match('^\s*1\s\+.*\sif 0$', lines[37]) - call assert_match( '^\s\+let x = 0$', lines[38]) + call assert_match( '^\s\+\(let\|var\) x = 0$', lines[38]) call assert_match('^\s*1\s\+.*\selseif 0$', lines[39]) - call assert_match( '^\s\+let x = 1$', lines[40]) + call assert_match( '^\s\+\(let\|var\) x = 1$', lines[40]) call assert_match('^\s*1\s\+.*\selse$', lines[41]) - call assert_match('^\s*1\s\+.*\s let x = 2$', lines[42]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 2$', lines[42]) call assert_match('^\s*1\s\+.*\sendif$', lines[43]) call assert_equal('', lines[44]) call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[45]) @@ -202,42 +220,56 @@ func Test_profile_func_with_ifelse() endfunc func Test_profile_func_with_trycatch() + call Run_profile_func_with_trycatch('func', 'let') + " call Run_profile_func_with_trycatch('def', 'var') +endfunc + +func Run_profile_func_with_trycatch(command, declare) let lines =<< trim [CODE] - func! Foo1() + XXX Foo1() try - let x = 0 + DDD x = 0 catch - let x = 1 + DDD x = 1 finally - let x = 2 + DDD x = 2 endtry - endfunc - func! Foo2() + endXXX + XXX Foo2() try throw 0 catch - let x = 1 + DDD x = 1 finally - let x = 2 + DDD x = 2 endtry - endfunc - func! Foo3() + endXXX + XXX Foo3() try throw 0 catch throw 1 finally - let x = 2 + DDD x = 2 endtry - endfunc + endXXX call Foo1() call Foo2() + let rethrown = 0 try call Foo3() catch + let rethrown = 1 endtry + if rethrown != 1 + " call Foo1 again so that the test fails + call Foo1() + endif [CODE] + call map(lines, {k, v -> substitute(v, 'XXX', a:command, '') }) + call map(lines, {k, v -> substitute(v, 'DDD', a:declare, '') }) + call writefile(lines, 'Xprofile_func.vim') call system(GetVimCommand() \ . ' -es -i NONE --noplugin' @@ -262,11 +294,11 @@ func Test_profile_func_with_trycatch() call assert_equal('', lines[5]) call assert_equal('count total (s) self (s)', lines[6]) call assert_match('^\s*1\s\+.*\stry$', lines[7]) - call assert_match('^\s*1\s\+.*\s let x = 0$', lines[8]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 0$', lines[8]) call assert_match( '^\s\+catch$', lines[9]) - call assert_match( '^\s\+let x = 1$', lines[10]) + call assert_match( '^\s\+\(let\|var\) x = 1$', lines[10]) call assert_match('^\s*1\s\+.*\sfinally$', lines[11]) - call assert_match('^\s*1\s\+.*\s let x = 2$', lines[12]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 2$', lines[12]) call assert_match('^\s*1\s\+.*\sendtry$', lines[13]) call assert_equal('', lines[14]) call assert_equal('FUNCTION Foo2()', lines[15]) @@ -278,9 +310,9 @@ func Test_profile_func_with_trycatch() call assert_match('^\s*1\s\+.*\stry$', lines[22]) call assert_match('^\s*1\s\+.*\s throw 0$', lines[23]) call assert_match('^\s*1\s\+.*\scatch$', lines[24]) - call assert_match('^\s*1\s\+.*\s let x = 1$', lines[25]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 1$', lines[25]) call assert_match('^\s*1\s\+.*\sfinally$', lines[26]) - call assert_match('^\s*1\s\+.*\s let x = 2$', lines[27]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 2$', lines[27]) call assert_match('^\s*1\s\+.*\sendtry$', lines[28]) call assert_equal('', lines[29]) call assert_equal('FUNCTION Foo3()', lines[30]) @@ -294,7 +326,7 @@ func Test_profile_func_with_trycatch() call assert_match('^\s*1\s\+.*\scatch$', lines[39]) call assert_match('^\s*1\s\+.*\s throw 1$', lines[40]) call assert_match('^\s*1\s\+.*\sfinally$', lines[41]) - call assert_match('^\s*1\s\+.*\s let x = 2$', lines[42]) + call assert_match('^\s*1\s\+.*\s \(let\|var\) x = 2$', lines[42]) call assert_match( '^\s\+endtry$', lines[43]) call assert_equal('', lines[44]) call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[45]) @@ -595,3 +627,28 @@ func Test_profile_typed_func() call delete('XprofileTypedFunc') call delete('XtestProfile') endfunc + +func Test_vim9_profiling() + throw 'Skipped: Vim9 script is N/A' + " only tests that compiling and calling functions doesn't crash + let lines =<< trim END + vim9script + def Func() + Crash() + enddef + def Crash() + enddef + prof start Xprofile_crash.log + prof func Func + Func() + END + call writefile(lines, 'Xprofile_crash.vim') + call system(GetVimCommandClean() . ' -es -c "so Xprofile_crash.vim" -c q') + call assert_equal(0, v:shell_error) + call assert_true(readfile('Xprofile_crash.log')->len() > 10) + call delete('Xprofile_crash.vim') + call delete('Xprofile_crash.log') +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_quickfix.vim b/test/old/testdir/test_quickfix.vim @@ -670,6 +670,150 @@ func Test_browse() call Xtest_browse('l') endfunc +" Test for memory allocation failures +func Xnomem_tests(cchar) + call s:setup_commands(a:cchar) + + call test_alloc_fail(GetAllocId('qf_dirname_start'), 0, 0) + call assert_fails('Xvimgrep vim runtest.vim', 'E342:') + + call test_alloc_fail(GetAllocId('qf_dirname_now'), 0, 0) + call assert_fails('Xvimgrep vim runtest.vim', 'E342:') + + call test_alloc_fail(GetAllocId('qf_namebuf'), 0, 0) + call assert_fails('Xfile runtest.vim', 'E342:') + + call test_alloc_fail(GetAllocId('qf_errmsg'), 0, 0) + call assert_fails('Xfile runtest.vim', 'E342:') + + call test_alloc_fail(GetAllocId('qf_pattern'), 0, 0) + call assert_fails('Xfile runtest.vim', 'E342:') + + call test_alloc_fail(GetAllocId('qf_efm_fmtstr'), 0, 0) + set efm=%f + call assert_fails('Xexpr ["Xfile1"]', 'E342:') + set efm& + + call test_alloc_fail(GetAllocId('qf_efm_fmtpart'), 0, 0) + set efm=%f:%l:%m,%f-%l-%m + call assert_fails('Xaddexpr ["Xfile2", "Xfile3"]', 'E342:') + set efm& + + call test_alloc_fail(GetAllocId('qf_title'), 0, 0) + call assert_fails('Xexpr ""', 'E342:') + call assert_equal('', g:Xgetlist({'all': 1}).title) + + call test_alloc_fail(GetAllocId('qf_mef_name'), 0, 0) + set makeef=Xtmp##.err + call assert_fails('Xgrep needle haystack', 'E342:') + set makeef& + + call test_alloc_fail(GetAllocId('qf_qfline'), 0, 0) + call assert_fails('Xexpr "Xfile1:10:Line10"', 'E342:') + + if a:cchar == 'l' + for id in ['qf_qfline', 'qf_qfinfo'] + lgetexpr ["Xfile1:10:L10", "Xfile2:20:L20"] + call test_alloc_fail(GetAllocId(id), 0, 0) + call assert_fails('new', 'E342:') + call assert_equal(2, winnr('$')) + call assert_equal([], getloclist(0)) + %bw! + endfor + endif + + call test_alloc_fail(GetAllocId('qf_qfline'), 0, 0) + try + call assert_fails('Xvimgrep vim runtest.vim', 'E342:') + catch /^Vim:Interrupt$/ + endtry + + call test_alloc_fail(GetAllocId('qf_qfline'), 0, 0) + try + call assert_fails('Xvimgrep /vim/f runtest.vim', 'E342:') + catch /^Vim:Interrupt$/ + endtry + + let l = getqflist({"lines": ["Xfile1:10:L10"]}) + call test_alloc_fail(GetAllocId('qf_qfline'), 0, 0) + call assert_fails('call g:Xsetlist(l.items)', 'E342:') + + call test_alloc_fail(GetAllocId('qf_qfline'), 0, 0) + try + call assert_fails('Xhelpgrep quickfix', 'E342:') + catch /^Vim:Interrupt$/ + endtry + + call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0) + call assert_fails('let l = g:Xgetlist({"lines": ["Xfile1:10:L10"]})', 'E342:') + call assert_equal(#{items: []}, l) + + if a:cchar == 'l' + call setqflist([], 'f') + call setloclist(0, [], 'f') + call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0) + call assert_fails('lhelpgrep quickfix', 'E342:') + call assert_equal([], getloclist(0)) + + call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0) + call assert_fails('lvimgrep vim runtest.vim', 'E342:') + + let l = getqflist({"lines": ["Xfile1:10:L10"]}) + call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0) + call assert_fails('call setloclist(0, l.items)', 'E342:') + + call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0) + call assert_fails('lbuffer', 'E342:') + + call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0) + call assert_fails('lexpr ["Xfile1:10:L10", "Xfile2:20:L20"]', 'E342:') + + call test_alloc_fail(GetAllocId('qf_qfinfo'), 0, 0) + call assert_fails('lfile runtest.vim', 'E342:') + endif + + call test_alloc_fail(GetAllocId('qf_dirstack'), 0, 0) + set efm=%DEntering\ dir\ %f,%f:%l:%m + call assert_fails('Xexpr ["Entering dir abc", "abc.txt:1:Hello world"]', 'E342:') + set efm& + + call test_alloc_fail(GetAllocId('qf_dirstack'), 0, 0) + set efm=%+P[%f],(%l)%m + call assert_fails('Xexpr ["[runtest.vim]", "(1)Hello"]', 'E342:') + set efm& + + call test_alloc_fail(GetAllocId('qf_multiline_pfx'), 0, 0) + set efm=%EError,%Cline\ %l,%Z%m + call assert_fails('Xexpr ["Error", "line 1", "msg"]', 'E342:') + set efm& + + call test_alloc_fail(GetAllocId('qf_makecmd'), 0, 0) + call assert_fails('Xgrep vim runtest.vim', 'E342:') + + call test_alloc_fail(GetAllocId('qf_linebuf'), 0, 0) + call assert_fails('Xexpr repeat("a", 8192)', 'E342:') + + call test_alloc_fail(GetAllocId('qf_linebuf'), 0, 0) + call assert_fails('Xexpr [repeat("a", 8192)]', 'E342:') + + new + call setline(1, repeat('a', 8192)) + call test_alloc_fail(GetAllocId('qf_linebuf'), 0, 0) + call assert_fails('Xbuffer', 'E342:') + %bw! + + call writefile([repeat('a', 8192)], 'Xtest') + call test_alloc_fail(GetAllocId('qf_linebuf'), 0, 0) + call assert_fails('Xfile Xtest', 'E342:') + call delete('Xtest') +endfunc + +func Test_nomem() + throw 'Skipped: Nvim does not support test_alloc_fail()' + call Xnomem_tests('c') + call Xnomem_tests('l') +endfunc + func s:test_xhelpgrep(cchar) call s:setup_commands(a:cchar) Xhelpgrep quickfix