patch 9.2.0817: crash when building a stacktrace during an autocommand
Commit:
https://github.com/vim/vim/commit/16f562b4e54ed13e54fac9a7bf27feff6beca345
Author: Hirohito Higashi <
h.eas...@gmail.com>
Date: Mon Jul 20 17:11:43 2026 +0000
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) <
nor...@anthropic.com>
Signed-off-by: Hirohito Higashi <
h.eas...@gmail.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/src/scriptfile.c b/src/scriptfile.c
index c32c73ec0..c5f807562 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 77ea28f94..25750cc34 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()\<CR>")
+ 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 bb8df567b..91cfa5b70 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,
/**/