From: Hirohito Higashi Date: Mon, 20 Jul 2026 17:11:43 +0000 (+0000) Subject: patch 9.2.0817: crash when building a stacktrace during an autocommand X-Git-Tag: v9.2.0817^0 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=16f562b4e54ed13e54fac9a7bf27feff6beca345;p=thirdparty%2Fvim.git patch 9.2.0817: crash when building a stacktrace during an autocommand Problem: Vim can crash while building a stack trace when an autocommand is being triggered but has not matched a pattern yet, for example at the more prompt with 'verbose' set (stefanos82). Solution: Handle the autocommand entry on the execution stack that does not have a script context yet (Hirohito Higashi). fixes: #20730 closes: #20732 Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Hirohito Higashi Signed-off-by: Christian Brabandt --- diff --git a/src/scriptfile.c b/src/scriptfile.c index c32c73ec03..c5f807562d 100644 --- a/src/scriptfile.c +++ b/src/scriptfile.c @@ -166,10 +166,11 @@ estack_sfile(estack_arg_T which UNUSED) if (entry->es_type == ETYPE_UFUNC || entry->es_type == ETYPE_AUCMD) { sctx_T *def_ctx = entry->es_type == ETYPE_UFUNC - ? &entry->es_info.ufunc->uf_script_ctx - : acp_script_ctx(entry->es_info.aucmd); + ? &entry->es_info.ufunc->uf_script_ctx + : entry->es_info.aucmd != NULL + ? acp_script_ctx(entry->es_info.aucmd) : NULL; - return def_ctx->sc_sid > 0 + return def_ctx != NULL && def_ctx->sc_sid > 0 ? vim_strsave(SCRIPT_ITEM(def_ctx->sc_sid)->sn_name) : NULL; } @@ -322,11 +323,15 @@ stacktrace_create(void) } else if (entry->es_type == ETYPE_AUCMD) { - sctx_T sctx = *acp_script_ctx(entry->es_info.aucmd); - char_u *filepath = sctx.sc_sid > 0 ? - get_scriptname(sctx.sc_sid) : (char_u *)""; - - lnum += sctx.sc_lnum; + // The autocmd may not have a matching pattern yet, in which case + // es_info.aucmd is still NULL. + sctx_T *sctx = entry->es_info.aucmd != NULL + ? acp_script_ctx(entry->es_info.aucmd) : NULL; + char_u *filepath = sctx != NULL && sctx->sc_sid > 0 ? + get_scriptname(sctx->sc_sid) : (char_u *)""; + + if (sctx != NULL) + lnum += sctx->sc_lnum; stacktrace_push_item(l, NULL, entry->es_name, lnum, filepath); } } diff --git a/src/testdir/test_stacktrace.vim b/src/testdir/test_stacktrace.vim index 77ea28f94f..25750cc346 100644 --- a/src/testdir/test_stacktrace.vim +++ b/src/testdir/test_stacktrace.vim @@ -1,5 +1,6 @@ " Test for getstacktrace() and v:stacktrace +source util/screendump.vim import './util/vim9.vim' as v9 let s:thisfile = expand('%:p') @@ -36,7 +37,7 @@ func Test_getstacktrace() source Xscript1 call Xfunc1() call AssertStacktrace([ - \ #{funcref: funcref('Test_getstacktrace'), lnum: 37, filepath: s:thisfile}, + \ #{funcref: funcref('Test_getstacktrace'), lnum: 38, filepath: s:thisfile}, \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, \ ], g:stacktrace) @@ -63,7 +64,7 @@ func Test_getstacktrace_event() source Xscript1 source Xscript2 call AssertStacktrace([ - \ #{funcref: funcref('Test_getstacktrace_event'), lnum: 64, filepath: s:thisfile}, + \ #{funcref: funcref('Test_getstacktrace_event'), lnum: 65, filepath: s:thisfile}, \ #{event: 'SourcePre Autocommands for "*"', lnum: 7, filepath: Filepath('Xscript1')}, \ #{funcref: funcref('Xfunc'), lnum: 4, filepath: Filepath('Xscript1')}, \ ], g:stacktrace) @@ -106,12 +107,12 @@ func Test_vstacktrace() endtry call assert_equal([], v:stacktrace) call AssertStacktrace([ - \ #{funcref: funcref('Test_vstacktrace'), lnum: 97, filepath: s:thisfile}, + \ #{funcref: funcref('Test_vstacktrace'), lnum: 98, filepath: s:thisfile}, \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, \ ], stacktrace) call AssertStacktrace([ - \ #{funcref: funcref('Test_vstacktrace'), lnum: 101, filepath: s:thisfile}, + \ #{funcref: funcref('Test_vstacktrace'), lnum: 102, filepath: s:thisfile}, \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, \ ], stacktrace_inner) @@ -139,4 +140,50 @@ func Test_stacktrace_vim9() call v9.CheckDefSuccess(lines) endfunc +" Building a stacktrace at the "Executing autocommands" more prompt, before the +" autocommand has matched a pattern, must not crash. +func Test_getstacktrace_during_autocmd_prompt() + CheckRunVimInTerminal + + let lines =<< trim [SCRIPT] + func Cb(timer) + " The not-yet-matched autocmd entry has no funcref, no event and an empty + " filepath; only then does this hit the crash. + for d in getstacktrace() + if !has_key(d, 'funcref') && !has_key(d, 'event') + \ && get(d, 'filepath', '') == '' + call writefile(['ok'], 'Xstacktracedone') + endif + endfor + endfunc + augroup Test + autocmd! + autocmd User Foo echo 'autocmd body' + augroup END + func Go() + " verbose=8 prints the "Executing autocommands" message; redraw! fixes the + " screen origin so the fill lines make the more prompt land on it. + set more verbose=8 + redraw! + call timer_start(20, function('Cb'), #{repeat: -1}) + for i in range(&lines - 1) + echom 'fill' .. i + endfor + doautocmd User Foo + endfunc + [SCRIPT] + call writefile(lines, 'Xstacktracescript', 'D') + + let buf = RunVimInTerminal('-S Xstacktracescript', #{rows: 6}) + " Not from a timer: a nested timer callback would not fire at the more prompt. + call term_sendkeys(buf, ":call Go()\") + call WaitForAssert({-> assert_true(filereadable('Xstacktracedone'))}) + call assert_equal('run', job_status(term_getjob(buf))) + + " The more prompt makes a clean :qall unreliable, so stop the job. + call job_stop(term_getjob(buf)) + call WaitForAssert({-> assert_equal('dead', job_status(term_getjob(buf)))}) + call delete('Xstacktracedone') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index bb8df567b7..91cfa5b70d 100644 --- a/src/version.c +++ b/src/version.c @@ -759,6 +759,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 817, /**/ 816, /**/