patch 9.1.0984: exception handling can be improved

Problem:  exception handling can be improved
Solution: add v:stacktrace and getstacktrace()

closes: #16360

Co-authored-by: Naruhiko Nishino <naru123456789@gmail.com>
Signed-off-by: ichizok <gclient.gaap@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
ichizok
2025-01-02 18:06:00 +01:00
committed by Christian Brabandt
parent fd771613b3
commit 663d18d610
17 changed files with 285 additions and 21 deletions

View File

@ -279,6 +279,7 @@ getregionpos({pos1}, {pos2} [, {opts}])
List get a list of positions for a region List get a list of positions for a region
getregtype([{regname}]) String type of a register getregtype([{regname}]) String type of a register
getscriptinfo([{opts}]) List list of sourced scripts getscriptinfo([{opts}]) List list of sourced scripts
getstacktrace() List get current stack trace of Vim scripts
gettabinfo([{expr}]) List list of tab pages gettabinfo([{expr}]) List list of tab pages
gettabvar({nr}, {varname} [, {def}]) gettabvar({nr}, {varname} [, {def}])
any variable {varname} in tab {nr} or {def} any variable {varname} in tab {nr} or {def}
@ -4997,6 +4998,21 @@ getscriptinfo([{opts}]) *getscriptinfo()*
Return type: list<dict<any>> Return type: list<dict<any>>
getstacktrace() *getstacktrace()*
Returns the current stack trace of Vim scripts.
Stack trace is a |List|, of which each item is a |Dictionary|
with the following items:
funcref The funcref if the stack is at the function,
otherwise this item is not exist.
event The string of the event description if the
stack is at autocmd event, otherwise this item
is not exist.
lnum The line number of the script on the stack.
filepath The file path of the script on the stack.
Return type: list<dict<any>>
gettabinfo([{tabnr}]) *gettabinfo()* gettabinfo([{tabnr}]) *gettabinfo()*
If {tabnr} is not specified, then information about all the If {tabnr} is not specified, then information about all the
tab pages is returned as a |List|. Each List item is a tab pages is returned as a |List|. Each List item is a

View File

@ -1,4 +1,4 @@
*eval.txt* For Vim version 9.1. Last change: 2024 Dec 23 *eval.txt* For Vim version 9.1. Last change: 2025 Jan 02
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@ -1953,7 +1953,8 @@ variables for each buffer. Use local buffer variables instead |b:var|.
PREDEFINED VIM VARIABLES *vim-variable* *v:var* *v:* PREDEFINED VIM VARIABLES *vim-variable* *v:var* *v:*
*E963* *E1063* *E963* *E1063*
Some variables can be set by the user, but the type cannot be changed. Most variables are read-only, when a variable can be set by the user, it will
be mentioned at the variable description below. The type cannot be changed.
*v:argv* *argv-variable* *v:argv* *argv-variable*
v:argv The command line arguments Vim was invoked with. This is a v:argv The command line arguments Vim was invoked with. This is a
@ -2172,7 +2173,8 @@ v:event Dictionary containing information about the current
< <
*v:exception* *exception-variable* *v:exception* *exception-variable*
v:exception The value of the exception most recently caught and not v:exception The value of the exception most recently caught and not
finished. See also |v:throwpoint| and |throw-variables|. finished. See also |v:stacktrace|, |v:throwpoint|, and
|throw-variables|.
Example: > Example: >
:try :try
: throw "oops" : throw "oops"
@ -2548,6 +2550,12 @@ v:sizeofpointer Number of bytes in a pointer. Depends on how Vim was compiled.
This is only useful for deciding whether a test will give the This is only useful for deciding whether a test will give the
expected result. expected result.
*v:stacktrace* *stacktrace-variable*
v:stacktrace The stack trace of the exception most recently caught and
not finished. Refer to |getstacktrace()| for the structure of
stack trace. See also |v:exception|, |v:throwpoint|, and
|throw-variables|.
*v:statusmsg* *statusmsg-variable* *v:statusmsg* *statusmsg-variable*
v:statusmsg Last given status message. It's allowed to set this variable. v:statusmsg Last given status message. It's allowed to set this variable.
@ -2676,7 +2684,7 @@ v:this_session Full filename of the last loaded or saved session file. See
*v:throwpoint* *throwpoint-variable* *v:throwpoint* *throwpoint-variable*
v:throwpoint The point where the exception most recently caught and not v:throwpoint The point where the exception most recently caught and not
finished was thrown. Not set when commands are typed. See finished was thrown. Not set when commands are typed. See
also |v:exception| and |throw-variables|. also |v:exception|, |v:stacktrace|, and |throw-variables|.
Example: > Example: >
:try :try
: throw "oops" : throw "oops"
@ -3856,7 +3864,8 @@ in the variable |v:exception|: >
: echo "Number thrown. Value is" v:exception : echo "Number thrown. Value is" v:exception
You may also be interested where an exception was thrown. This is stored in You may also be interested where an exception was thrown. This is stored in
|v:throwpoint|. Note that "v:exception" and "v:throwpoint" are valid for the |v:throwpoint|. And you can obtain the stack trace from |v:stacktrace|.
Note that "v:exception", "v:stacktrace" and "v:throwpoint" are valid for the
exception most recently caught as long it is not finished. exception most recently caught as long it is not finished.
Example: > Example: >

View File

@ -7908,6 +7908,7 @@ getscript-history pi_getscript.txt /*getscript-history*
getscript-plugins pi_getscript.txt /*getscript-plugins* getscript-plugins pi_getscript.txt /*getscript-plugins*
getscript-start pi_getscript.txt /*getscript-start* getscript-start pi_getscript.txt /*getscript-start*
getscriptinfo() builtin.txt /*getscriptinfo()* getscriptinfo() builtin.txt /*getscriptinfo()*
getstacktrace() builtin.txt /*getstacktrace()*
gettabinfo() builtin.txt /*gettabinfo()* gettabinfo() builtin.txt /*gettabinfo()*
gettabvar() builtin.txt /*gettabvar()* gettabvar() builtin.txt /*gettabvar()*
gettabwinvar() builtin.txt /*gettabwinvar()* gettabwinvar() builtin.txt /*gettabwinvar()*
@ -10218,6 +10219,7 @@ sqrt() builtin.txt /*sqrt()*
squirrel.vim syntax.txt /*squirrel.vim* squirrel.vim syntax.txt /*squirrel.vim*
srand() builtin.txt /*srand()* srand() builtin.txt /*srand()*
sscanf eval.txt /*sscanf* sscanf eval.txt /*sscanf*
stacktrace-variable eval.txt /*stacktrace-variable*
standard-plugin usr_05.txt /*standard-plugin* standard-plugin usr_05.txt /*standard-plugin*
standard-plugin-list help.txt /*standard-plugin-list* standard-plugin-list help.txt /*standard-plugin-list*
standout syntax.txt /*standout* standout syntax.txt /*standout*
@ -11038,6 +11040,7 @@ v:shell_error eval.txt /*v:shell_error*
v:sizeofint eval.txt /*v:sizeofint* v:sizeofint eval.txt /*v:sizeofint*
v:sizeoflong eval.txt /*v:sizeoflong* v:sizeoflong eval.txt /*v:sizeoflong*
v:sizeofpointer eval.txt /*v:sizeofpointer* v:sizeofpointer eval.txt /*v:sizeofpointer*
v:stacktrace eval.txt /*v:stacktrace*
v:statusmsg eval.txt /*v:statusmsg* v:statusmsg eval.txt /*v:statusmsg*
v:swapchoice eval.txt /*v:swapchoice* v:swapchoice eval.txt /*v:swapchoice*
v:swapcommand eval.txt /*v:swapcommand* v:swapcommand eval.txt /*v:swapcommand*

View File

@ -1,4 +1,4 @@
*usr_41.txt* For Vim version 9.1. Last change: 2024 Dec 30 *usr_41.txt* For Vim version 9.1. Last change: 2025 Jan 02
VIM USER MANUAL - by Bram Moolenaar VIM USER MANUAL - by Bram Moolenaar
@ -1399,7 +1399,8 @@ Various: *various-functions*
eventhandler() check if invoked by an event handler eventhandler() check if invoked by an event handler
getcellpixels() get List of cell pixel size getcellpixels() get List of cell pixel size
getpid() get process ID of Vim getpid() get process ID of Vim
getscriptinfo() get list of sourced vim scripts getscriptinfo() get list of sourced Vim scripts
getstacktrace() get current stack trace of Vim scripts
getimstatus() check if IME status is active getimstatus() check if IME status is active
interrupt() interrupt script execution interrupt() interrupt script execution
windowsversion() get MS-Windows version windowsversion() get MS-Windows version

View File

@ -41623,6 +41623,8 @@ Changed~
for the ghostty terminal emulator (using kitty protocol) for the ghostty terminal emulator (using kitty protocol)
- |complete_info()| returns the list of matches shown in the poppu menu via - |complete_info()| returns the list of matches shown in the poppu menu via
the "matches" key the "matches" key
- |v:stacktrace| The stack trace of the exception most recently caught and
not finished
*added-9.2* *added-9.2*
Added ~ Added ~
@ -41642,6 +41644,7 @@ Functions: ~
|getcmdprompt()| get prompt for input()/confirm() |getcmdprompt()| get prompt for input()/confirm()
|getregion()| get a region of text from a buffer |getregion()| get a region of text from a buffer
|getregionpos()| get a list of positions for a region |getregionpos()| get a list of positions for a region
|getstacktrace()| get current stack trace of Vim scripts
|id()| get unique identifier for a Dict, List, Object, |id()| get unique identifier for a Dict, List, Object,
Channel or Blob variable Channel or Blob variable
|matchbufline()| all the matches of a pattern in a buffer |matchbufline()| all the matches of a pattern in a buffer

View File

@ -531,6 +531,29 @@ dict_add_callback(dict_T *d, char *key, callback_T *cb)
return OK; return OK;
} }
/*
* Add a function entry to dictionary "d".
* Returns FAIL when out of memory and when key already exists.
*/
int
dict_add_func(dict_T *d, char *key, ufunc_T *fp)
{
dictitem_T *item;
item = dictitem_alloc((char_u *)key);
if (item == NULL)
return FAIL;
item->di_tv.v_type = VAR_FUNC;
item->di_tv.vval.v_string = vim_strsave(fp->uf_name);
if (dict_add(d, item) == FAIL)
{
dictitem_free(item);
return FAIL;
}
func_ref(item->di_tv.vval.v_string);
return OK;
}
/* /*
* Initializes "iter" for iterating over dictionary items with * Initializes "iter" for iterating over dictionary items with
* dict_iterate_next(). * dict_iterate_next().

View File

@ -2170,6 +2170,8 @@ static funcentry_T global_functions[] =
ret_string, f_getregtype}, ret_string, f_getregtype},
{"getscriptinfo", 0, 1, 0, arg1_dict_any, {"getscriptinfo", 0, 1, 0, arg1_dict_any,
ret_list_dict_any, f_getscriptinfo}, ret_list_dict_any, f_getscriptinfo},
{"getstacktrace", 0, 0, 0, NULL,
ret_list_dict_any, f_getstacktrace},
{"gettabinfo", 0, 1, FEARG_1, arg1_number, {"gettabinfo", 0, 1, FEARG_1, arg1_number,
ret_list_dict_any, f_gettabinfo}, ret_list_dict_any, f_gettabinfo},
{"gettabvar", 2, 3, FEARG_1, arg3_number_string_any, {"gettabvar", 2, 3, FEARG_1, arg3_number_string_any,

View File

@ -160,7 +160,8 @@ static struct vimvar
{VV_NAME("python3_version", VAR_NUMBER), NULL, VV_RO}, {VV_NAME("python3_version", VAR_NUMBER), NULL, VV_RO},
{VV_NAME("t_typealias", VAR_NUMBER), NULL, VV_RO}, {VV_NAME("t_typealias", VAR_NUMBER), NULL, VV_RO},
{VV_NAME("t_enum", VAR_NUMBER), NULL, VV_RO}, {VV_NAME("t_enum", VAR_NUMBER), NULL, VV_RO},
{VV_NAME("t_enumvalue", VAR_NUMBER), NULL, VV_RO} {VV_NAME("t_enumvalue", VAR_NUMBER), NULL, VV_RO},
{VV_NAME("stacktrace", VAR_LIST), &t_list_string, VV_RO},
}; };
// shorthand // shorthand

View File

@ -562,6 +562,10 @@ throw_exception(void *value, except_type_T type, char_u *cmdname)
excp->throw_lnum = SOURCING_LNUM; excp->throw_lnum = SOURCING_LNUM;
} }
excp->stacktrace = stacktrace_create();
if (excp->stacktrace != NULL)
excp->stacktrace->lv_refcount = 1;
if (p_verbose >= 13 || debug_break_level > 0) if (p_verbose >= 13 || debug_break_level > 0)
{ {
int save_msg_silent = msg_silent; int save_msg_silent = msg_silent;
@ -647,6 +651,7 @@ discard_exception(except_T *excp, int was_finished)
if (excp->type == ET_ERROR) if (excp->type == ET_ERROR)
free_msglist(excp->messages); free_msglist(excp->messages);
vim_free(excp->throw_name); vim_free(excp->throw_name);
list_unref(excp->stacktrace);
vim_free(excp); vim_free(excp);
} }
@ -671,6 +676,7 @@ catch_exception(except_T *excp)
excp->caught = caught_stack; excp->caught = caught_stack;
caught_stack = excp; caught_stack = excp;
set_vim_var_string(VV_EXCEPTION, (char_u *)excp->value, -1); set_vim_var_string(VV_EXCEPTION, (char_u *)excp->value, -1);
set_vim_var_list(VV_STACKTRACE, excp->stacktrace);
if (*excp->throw_name != NUL) if (*excp->throw_name != NUL)
{ {
if (excp->throw_lnum != 0) if (excp->throw_lnum != 0)
@ -721,6 +727,7 @@ finish_exception(except_T *excp)
if (caught_stack != NULL) if (caught_stack != NULL)
{ {
set_vim_var_string(VV_EXCEPTION, (char_u *)caught_stack->value, -1); set_vim_var_string(VV_EXCEPTION, (char_u *)caught_stack->value, -1);
set_vim_var_list(VV_STACKTRACE, caught_stack->stacktrace);
if (*caught_stack->throw_name != NUL) if (*caught_stack->throw_name != NUL)
{ {
if (caught_stack->throw_lnum != 0) if (caught_stack->throw_lnum != 0)
@ -741,6 +748,7 @@ finish_exception(except_T *excp)
{ {
set_vim_var_string(VV_EXCEPTION, NULL, -1); set_vim_var_string(VV_EXCEPTION, NULL, -1);
set_vim_var_string(VV_THROWPOINT, NULL, -1); set_vim_var_string(VV_THROWPOINT, NULL, -1);
set_vim_var_list(VV_STACKTRACE, NULL);
} }
// Discard the exception, but use the finish message for 'verbose'. // Discard the exception, but use the finish message for 'verbose'.

View File

@ -22,6 +22,7 @@ int dict_add_string_len(dict_T *d, char *key, char_u *str, int len);
int dict_add_list(dict_T *d, char *key, list_T *list); int dict_add_list(dict_T *d, char *key, list_T *list);
int dict_add_tv(dict_T *d, char *key, typval_T *tv); int dict_add_tv(dict_T *d, char *key, typval_T *tv);
int dict_add_callback(dict_T *d, char *key, callback_T *cb); int dict_add_callback(dict_T *d, char *key, callback_T *cb);
int dict_add_func(dict_T *d, char *key, ufunc_T *fp);
void dict_iterate_start(typval_T *var, dict_iterator_T *iter); void dict_iterate_start(typval_T *var, dict_iterator_T *iter);
char_u *dict_iterate_next(dict_iterator_T *iter, typval_T **tv_result); char_u *dict_iterate_next(dict_iterator_T *iter, typval_T **tv_result);
int dict_add_dict(dict_T *d, char *key, dict_T *dict); int dict_add_dict(dict_T *d, char *key, dict_T *dict);

View File

@ -5,6 +5,8 @@ estack_T *estack_push_ufunc(ufunc_T *ufunc, long lnum);
int estack_top_is_ufunc(ufunc_T *ufunc, long lnum); int estack_top_is_ufunc(ufunc_T *ufunc, long lnum);
estack_T *estack_pop(void); estack_T *estack_pop(void);
char_u *estack_sfile(estack_arg_T which); char_u *estack_sfile(estack_arg_T which);
list_T *stacktrace_create(void);
void f_getstacktrace(typval_T *argvars, typval_T *rettv);
void ex_runtime(exarg_T *eap); void ex_runtime(exarg_T *eap);
void set_context_in_runtime_cmd(expand_T *xp, char_u *arg); void set_context_in_runtime_cmd(expand_T *xp, char_u *arg);
int find_script_by_name(char_u *name); int find_script_by_name(char_u *name);

View File

@ -237,6 +237,89 @@ estack_sfile(estack_arg_T which UNUSED)
#endif #endif
} }
#ifdef FEAT_EVAL
static void
stacktrace_push_item(
list_T *l,
ufunc_T *fp,
char_u *event,
linenr_T lnum,
char_u *filepath)
{
dict_T *d;
typval_T tv;
d = dict_alloc_lock(VAR_FIXED);
if (d == NULL)
return;
tv.v_type = VAR_DICT;
tv.v_lock = VAR_LOCKED;
tv.vval.v_dict = d;
if (fp != NULL)
dict_add_func(d, "funcref", fp);
if (event != NULL)
dict_add_string(d, "event", event);
dict_add_number(d, "lnum", lnum);
dict_add_string(d, "filepath", filepath);
list_append_tv(l, &tv);
}
/*
* Create the stacktrace from exestack.
*/
list_T *
stacktrace_create(void)
{
list_T *l;
int i;
l = list_alloc();
if (l == NULL)
return NULL;
for (i = 0; i < exestack.ga_len; ++i)
{
estack_T *entry = &((estack_T *)exestack.ga_data)[i];
linenr_T lnum = entry->es_lnum;
if (entry->es_type == ETYPE_SCRIPT)
stacktrace_push_item(l, NULL, NULL, lnum, entry->es_name);
else if (entry->es_type == ETYPE_UFUNC)
{
ufunc_T *fp = entry->es_info.ufunc;
sctx_T sctx = fp->uf_script_ctx;
char_u *filepath = sctx.sc_sid > 0 ?
get_scriptname(sctx.sc_sid) : (char_u *)"";
lnum += sctx.sc_lnum;
stacktrace_push_item(l, fp, NULL, lnum, filepath);
}
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;
stacktrace_push_item(l, NULL, entry->es_name, lnum, filepath);
}
}
return l;
}
/*
* getstacktrace() function
*/
void
f_getstacktrace(typval_T *argvars UNUSED, typval_T *rettv)
{
rettv_list_set(rettv, stacktrace_create());
}
#endif
/* /*
* Get DIP_ flags from the [where] argument of a :runtime command. * Get DIP_ flags from the [where] argument of a :runtime command.
* "*argp" is advanced to after the [where] argument if it is found. * "*argp" is advanced to after the [where] argument if it is found.

View File

@ -63,6 +63,17 @@ typedef struct growarray
#define GA_EMPTY {0, 0, 0, 0, NULL} #define GA_EMPTY {0, 0, 0, 0, NULL}
// On rare systems "char" is unsigned, sometimes we really want a signed 8-bit
// value.
typedef signed char int8_T;
typedef double float_T;
typedef struct typval_S typval_T;
typedef struct listvar_S list_T;
typedef struct dictvar_S dict_T;
typedef struct partial_S partial_T;
typedef struct blobvar_S blob_T;
typedef struct window_S win_T; typedef struct window_S win_T;
typedef struct wininfo_S wininfo_T; typedef struct wininfo_S wininfo_T;
typedef struct frame_S frame_T; typedef struct frame_S frame_T;
@ -1087,6 +1098,7 @@ struct vim_exception
struct msglist *messages; // message(s) causing error exception struct msglist *messages; // message(s) causing error exception
char_u *throw_name; // name of the throw point char_u *throw_name; // name of the throw point
linenr_T throw_lnum; // line number of the throw point linenr_T throw_lnum; // line number of the throw point
list_T *stacktrace; // stacktrace
except_T *caught; // next exception on the caught stack except_T *caught; // next exception on the caught stack
}; };
@ -1447,18 +1459,6 @@ typedef long_u hash_T; // Type for hi_hash
# endif # endif
#endif #endif
// On rare systems "char" is unsigned, sometimes we really want a signed 8-bit
// value.
typedef signed char int8_T;
typedef double float_T;
typedef struct typval_S typval_T;
typedef struct listvar_S list_T;
typedef struct dictvar_S dict_T;
typedef struct partial_S partial_T;
typedef struct blobvar_S blob_T;
// Struct that holds both a normal function name and a partial_T, as used for a // Struct that holds both a normal function name and a partial_T, as used for a
// callback argument. // callback argument.
// When used temporarily "cb_name" is not allocated. The refcounts to either // When used temporarily "cb_name" is not allocated. The refcounts to either

View File

@ -292,6 +292,7 @@ NEW_TESTS = \
test_spell_utf8 \ test_spell_utf8 \
test_spellfile \ test_spellfile \
test_spellrare \ test_spellrare \
test_stacktrace \
test_startup \ test_startup \
test_startup_utf8 \ test_startup_utf8 \
test_stat \ test_stat \
@ -545,6 +546,7 @@ NEW_TESTS_RES = \
test_spell_utf8.res \ test_spell_utf8.res \
test_spellfile.res \ test_spellfile.res \
test_spellrare.res \ test_spellrare.res \
test_stacktrace.res \
test_startup.res \ test_startup.res \
test_stat.res \ test_stat.res \
test_statusline.res \ test_statusline.res \

View File

@ -0,0 +1,107 @@
" Test for getstacktrace() and v:stacktrace
let s:thisfile = expand('%:p')
let s:testdir = s:thisfile->fnamemodify(':h')
func Filepath(name)
return s:testdir .. '/' .. a:name
endfunc
func AssertStacktrace(expect, actual)
call assert_equal(#{lnum: 617, filepath: Filepath('runtest.vim')}, a:actual[0])
call assert_equal(a:expect, a:actual[-len(a:expect):])
endfunc
func Test_getstacktrace()
let g:stacktrace = []
let lines1 =<< trim [SCRIPT]
" Xscript1
source Xscript2
func Xfunc1()
" Xfunc1
call Xfunc2()
endfunc
[SCRIPT]
let lines2 =<< trim [SCRIPT]
" Xscript2
func Xfunc2()
" Xfunc2
let g:stacktrace = getstacktrace()
endfunc
[SCRIPT]
call writefile(lines1, 'Xscript1', 'D')
call writefile(lines2, 'Xscript2', 'D')
source Xscript1
call Xfunc1()
call AssertStacktrace([
\ #{funcref: funcref('Test_getstacktrace'), lnum: 35, filepath: s:thisfile},
\ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')},
\ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')},
\ ], g:stacktrace)
unlet g:stacktrace
endfunc
func Test_getstacktrace_event()
let g:stacktrace = []
let lines1 =<< trim [SCRIPT]
" Xscript1
func Xfunc()
" Xfunc
let g:stacktrace = getstacktrace()
endfunc
augroup test_stacktrace
autocmd SourcePre * call Xfunc()
augroup END
[SCRIPT]
let lines2 =<< trim [SCRIPT]
" Xscript2
[SCRIPT]
call writefile(lines1, 'Xscript1', 'D')
call writefile(lines2, 'Xscript2', 'D')
source Xscript1
source Xscript2
call AssertStacktrace([
\ #{funcref: funcref('Test_getstacktrace_event'), lnum: 62, filepath: s:thisfile},
\ #{event: 'SourcePre Autocommands for "*"', lnum: 7, filepath: Filepath('Xscript1')},
\ #{funcref: funcref('Xfunc'), lnum: 4, filepath: Filepath('Xscript1')},
\ ], g:stacktrace)
augroup test_stacktrace
autocmd!
augroup END
unlet g:stacktrace
endfunc
func Test_vstacktrace()
let lines1 =<< trim [SCRIPT]
" Xscript1
source Xscript2
func Xfunc1()
" Xfunc1
call Xfunc2()
endfunc
[SCRIPT]
let lines2 =<< trim [SCRIPT]
" Xscript2
func Xfunc2()
" Xfunc2
throw 'Exception from Xfunc2'
endfunc
[SCRIPT]
call writefile(lines1, 'Xscript1', 'D')
call writefile(lines2, 'Xscript2', 'D')
source Xscript1
call assert_equal([], v:stacktrace)
try
call Xfunc1()
catch
let stacktrace = v:stacktrace
endtry
call assert_equal([], v:stacktrace)
call AssertStacktrace([
\ #{funcref: funcref('Test_vstacktrace'), lnum: 95, filepath: s:thisfile},
\ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')},
\ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')},
\ ], stacktrace)
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
984,
/**/ /**/
983, 983,
/**/ /**/

View File

@ -2190,7 +2190,8 @@ typedef int sock_T;
#define VV_TYPE_TYPEALIAS 107 #define VV_TYPE_TYPEALIAS 107
#define VV_TYPE_ENUM 108 #define VV_TYPE_ENUM 108
#define VV_TYPE_ENUMVALUE 109 #define VV_TYPE_ENUMVALUE 109
#define VV_LEN 110 // number of v: vars #define VV_STACKTRACE 110
#define VV_LEN 111 // number of v: vars
// used for v_number in VAR_BOOL and VAR_SPECIAL // used for v_number in VAR_BOOL and VAR_SPECIAL
#define VVAL_FALSE 0L // VAR_BOOL #define VVAL_FALSE 0L // VAR_BOOL