vim-patch:9.2.0996: debugger: crash when evaluating a variable in a :def function frame (#41460)

Problem:  In the debugger ">up" and ">frame" select an older function call
          frame, but get_funccal_local_ht() and the related functions check
          current_funccal while returning a dictionary of the frame that
          get_funccal() selected.  A :def function keeps its local variables
          on the vim9 stack, its funccall_T has no l: and a: dictionaries and
          is allocated cleared, so with such a frame selected the returned
          hashtab has a NULL ht_array and evaluating any variable name at the
          debug prompt crashes in hash_lookup().
Solution: Check the funccal that is actually used and return NULL when it has
          no l: variables, so that the variable is reported as undefined
          instead.

closes: vim/vim#21111

5ad47b07af

Co-authored-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zeertzjq
2026-08-24 09:27:46 +08:00
committed by GitHub
parent 126398c090
commit 70958dae75
2 changed files with 70 additions and 16 deletions

View File

@@ -3944,14 +3944,29 @@ funccall_T *get_funccal(void)
return funccal;
}
/// Get the function call environment to use for the l: and a: variables, based
/// on the backtrace debug level.
/// Returns NULL if there is no current funccal.
static funccall_T *get_funccal_for_vars(void)
{
funccall_T *funccal = NULL;
if (current_funccal == NULL) {
return NULL;
}
funccal = get_funccal();
if (funccal == NULL || funccal->fc_l_vars.dv_refcount == 0) {
return NULL;
}
return funccal;
}
/// @return dict used for local variables in the current funccal or
/// NULL if there is no current funccal.
dict_T *get_funccal_local_dict(void)
{
if (current_funccal == NULL || current_funccal->fc_l_vars.dv_refcount == 0) {
return NULL;
}
return &get_funccal()->fc_l_vars;
funccall_T *funccal = get_funccal_for_vars();
return funccal == NULL ? NULL : &funccal->fc_l_vars;
}
/// @return hashtable used for local variables in the current funccal or
@@ -3966,20 +3981,16 @@ hashtab_T *get_funccal_local_ht(void)
/// NULL if there is no current funccal.
dictitem_T *get_funccal_local_var(void)
{
if (current_funccal == NULL || current_funccal->fc_l_vars.dv_refcount == 0) {
return NULL;
}
return (dictitem_T *)&get_funccal()->fc_l_vars_var;
funccall_T *funccal = get_funccal_for_vars();
return funccal == NULL ? NULL : (dictitem_T *)&funccal->fc_l_vars_var;
}
/// @return the dict used for argument in the current funccal or
/// NULL if there is no current funccal.
dict_T *get_funccal_args_dict(void)
{
if (current_funccal == NULL || current_funccal->fc_l_vars.dv_refcount == 0) {
return NULL;
}
return &get_funccal()->fc_l_avars;
funccall_T *funccal = get_funccal_for_vars();
return funccal == NULL ? NULL : &funccal->fc_l_avars;
}
/// @return the hashtable used for argument in the current funccal or
@@ -3994,10 +4005,8 @@ hashtab_T *get_funccal_args_ht(void)
/// NULL if there is no current funccal.
dictitem_T *get_funccal_args_var(void)
{
if (current_funccal == NULL || current_funccal->fc_l_vars.dv_refcount == 0) {
return NULL;
}
return (dictitem_T *)&get_funccal()->fc_l_avars_var;
funccall_T *funccal = get_funccal_for_vars();
return funccal == NULL ? NULL : (dictitem_T *)&funccal->fc_l_avars_var;
}
/// List function variables, if there is a function.

View File

@@ -1107,6 +1107,51 @@ func Test_debug_def_and_legacy_function()
call StopVimInTerminal(buf)
endfunc
" Test for using "up" in the debugger to select the frame of a :def function
" and then evaluating a variable name
func Test_debug_backtrace_up_def_function()
CheckRunVimInTerminal
CheckCWD
let file =<< trim END
vim9script
def g:DefFunc()
var defvar = 'in def'
g:LegacyFunc('arg')
enddef
func g:LegacyFunc(text)
let legacyvar = 'in legacy'
echo legacyvar
endfunc
breakadd func 2 g:LegacyFunc
END
call writefile(file, 'XtestDebugUp.vim', 'D')
let buf = RunVimInTerminal('-S XtestDebugUp.vim', {})
call s:RunDbgCmd(buf, ':call DefFunc()', ['line 2: echo legacyvar'])
call s:RunDbgCmd(buf, 'echo legacyvar', ['in legacy'])
call s:RunDbgCmd(buf, 'echo l:legacyvar', ['in legacy'])
call s:RunDbgCmd(buf, 'echo a:text', ['arg'])
" Move up to the frame of the :def function, the local variables of the
" legacy function are not available there. This used to crash.
call s:RunDbgCmd(buf, 'up')
call s:RunDbgCmd(buf, 'echo legacyvar',
\ ['E121: Undefined variable: legacyvar'])
call s:RunDbgCmd(buf, 'echo l:legacyvar',
\ ['E121: Undefined variable: l:legacyvar'])
call s:RunDbgCmd(buf, 'echo a:text', ['E121: Undefined variable: a:text'])
" Back in the frame of the legacy function the variables are found again.
call s:RunDbgCmd(buf, 'down')
call s:RunDbgCmd(buf, 'echo legacyvar', ['in legacy'])
call s:RunDbgCmd(buf, 'cont')
call StopVimInTerminal(buf)
endfunc
func Test_debug_def_function()
CheckRunVimInTerminal
CheckCWD