]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0817: crash when building a stacktrace during an autocommand v9.2.0817
authorHirohito Higashi <h.east.727@gmail.com>
Mon, 20 Jul 2026 17:11:43 +0000 (17:11 +0000)
committerChristian Brabandt <cb@256bit.org>
Mon, 20 Jul 2026 17:11:43 +0000 (17:11 +0000)
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) <noreply@anthropic.com>
Signed-off-by: Hirohito Higashi <h.east.727@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/scriptfile.c
src/testdir/test_stacktrace.vim
src/version.c

index c32c73ec0314cf65fe10243f0f22e1c013fd392a..c5f807562de7f501cac25e7fc6bdf1f95c1ffb88 100644 (file)
@@ -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);
        }
     }
index 77ea28f94fade0ef50e662ca2ec3671b77b787c3..25750cc34641735cc2d6d63d5e3b3e7b8860c9c9 100644 (file)
@@ -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
index bb8df567b779ea09bc6697b790b2aa7e6739abd4..91cfa5b70d0c81d05b63d8860dba21e2a9b198d5 100644 (file)
@@ -759,6 +759,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    817,
 /**/
     816,
 /**/