diff options
author | zeertzjq <zeertzjq@outlook.com> | 2025-01-03 20:12:15 +0800 |
---|---|---|
committer | zeertzjq <zeertzjq@outlook.com> | 2025-01-07 09:15:10 +0800 |
commit | d5308637bf1aac2b97fccf73a0ffdef304eaa1d6 (patch) | |
tree | acb03c837290a225f1fcb7f73b4ae1306d1e7589 /src | |
parent | 06ff5480ce274daf3b7ad9950a587099200dc8ff (diff) | |
download | rneovim-d5308637bf1aac2b97fccf73a0ffdef304eaa1d6.tar.gz rneovim-d5308637bf1aac2b97fccf73a0ffdef304eaa1d6.tar.bz2 rneovim-d5308637bf1aac2b97fccf73a0ffdef304eaa1d6.zip |
vim-patch:9.1.0984: exception handling can be improved
Problem: exception handling can be improved
Solution: add v:stacktrace and getstacktrace()
closes: vim/vim#16360
https://github.com/vim/vim/commit/663d18d6102f40d14e36096ec590445e61026ed6
Co-authored-by: ichizok <gclient.gaap@gmail.com>
Co-authored-by: Naruhiko Nishino <naru123456789@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/eval.c | 1 | ||||
-rw-r--r-- | src/nvim/eval.h | 1 | ||||
-rw-r--r-- | src/nvim/eval.lua | 19 | ||||
-rw-r--r-- | src/nvim/eval/typval.c | 24 | ||||
-rw-r--r-- | src/nvim/ex_eval.c | 7 | ||||
-rw-r--r-- | src/nvim/ex_eval_defs.h | 2 | ||||
-rw-r--r-- | src/nvim/runtime.c | 66 | ||||
-rw-r--r-- | src/nvim/vvars.lua | 14 |
8 files changed, 132 insertions, 2 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8bdd8dad4c..a90f275713 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -270,6 +270,7 @@ static struct vimvar { VV(VV_COLLATE, "collate", VAR_STRING, VV_RO), VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), VV(VV_MAXCOL, "maxcol", VAR_NUMBER, VV_RO), + VV(VV_STACKTRACE, "stacktrace", VAR_LIST, VV_RO), // Neovim VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), diff --git a/src/nvim/eval.h b/src/nvim/eval.h index bb9b00abc7..8b4aa8101a 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -167,6 +167,7 @@ typedef enum { VV_COLLATE, VV_EXITING, VV_MAXCOL, + VV_STACKTRACE, // Nvim VV_STDERR, VV_MSGPACK_TYPES, diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index c650dee306..5901ed5766 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -4670,6 +4670,25 @@ M.funcs = { returns = 'vim.fn.getscriptinfo.ret[]', signature = 'getscriptinfo([{opts}])', }, + getstacktrace = { + args = 0, + desc = [=[ + 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. + ]=], + name = 'getstacktrace', + params = {}, + returns = 'table[]', + signature = 'getstacktrace()', + }, gettabinfo = { args = { 0, 1 }, base = 1, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index cbb6b5644f..ed1031577c 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2633,6 +2633,30 @@ int tv_dict_add_allocated_str(dict_T *const d, const char *const key, const size return OK; } +/// Add a function entry to dictionary. +/// +/// @param[out] d Dictionary to add entry to. +/// @param[in] key Key to add. +/// @param[in] key_len Key length. +/// @param[in] fp Function to add. +/// +/// @return OK in case of success, FAIL when key already exists. +int tv_dict_add_func(dict_T *const d, const char *const key, const size_t key_len, + ufunc_T *const fp) + FUNC_ATTR_NONNULL_ARG(1, 2, 4) +{ + dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); + + item->di_tv.v_type = VAR_FUNC; + item->di_tv.vval.v_string = xstrdup(fp->uf_name); + if (tv_dict_add(d, item) == FAIL) { + tv_dict_item_free(item); + return FAIL; + } + func_ref(item->di_tv.vval.v_string); + return OK; +} + //{{{2 Operations on the whole dict /// Clear all the keys of a Dictionary. "d" remains a valid empty Dictionary. diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index f9936dd88e..18c691d076 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -479,6 +479,9 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) excp->throw_lnum = SOURCING_LNUM; } + excp->stacktrace = stacktrace_create(); + tv_list_ref(excp->stacktrace); + if (p_verbose >= 13 || debug_break_level > 0) { int save_msg_silent = msg_silent; @@ -563,6 +566,7 @@ static void discard_exception(except_T *excp, bool was_finished) free_msglist(excp->messages); } xfree(excp->throw_name); + tv_list_unref(excp->stacktrace); xfree(excp); } @@ -584,6 +588,7 @@ static void catch_exception(except_T *excp) excp->caught = caught_stack; caught_stack = excp; set_vim_var_string(VV_EXCEPTION, excp->value, -1); + set_vim_var_list(VV_STACKTRACE, excp->stacktrace); if (*excp->throw_name != NUL) { if (excp->throw_lnum != 0) { vim_snprintf(IObuff, IOSIZE, _("%s, line %" PRId64), @@ -633,6 +638,7 @@ static void finish_exception(except_T *excp) caught_stack = caught_stack->caught; if (caught_stack != NULL) { set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1); + set_vim_var_list(VV_STACKTRACE, caught_stack->stacktrace); if (*caught_stack->throw_name != NUL) { if (caught_stack->throw_lnum != 0) { vim_snprintf(IObuff, IOSIZE, @@ -651,6 +657,7 @@ static void finish_exception(except_T *excp) } else { set_vim_var_string(VV_EXCEPTION, 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'. diff --git a/src/nvim/ex_eval_defs.h b/src/nvim/ex_eval_defs.h index 3f5e510a20..e0d06f3e93 100644 --- a/src/nvim/ex_eval_defs.h +++ b/src/nvim/ex_eval_defs.h @@ -2,6 +2,7 @@ #include <stdbool.h> +#include "nvim/eval/typval_defs.h" #include "nvim/pos_defs.h" /// A list used for saving values of "emsg_silent". Used by ex_try() to save the @@ -107,6 +108,7 @@ struct vim_exception { msglist_T *messages; ///< message(s) causing error exception char *throw_name; ///< name 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 }; diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index d849a18879..cdedf86977 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -228,6 +228,72 @@ char *estack_sfile(estack_arg_T which) return (char *)ga.ga_data; } +static void stacktrace_push_item(list_T *const l, ufunc_T *const fp, const char *const event, + const linenr_T lnum, char *const filepath, + const bool filepath_alloced) +{ + dict_T *const d = tv_dict_alloc_lock(VAR_FIXED); + typval_T tv = { + .v_type = VAR_DICT, + .v_lock = VAR_LOCKED, + .vval.v_dict = d, + }; + + if (fp != NULL) { + tv_dict_add_func(d, S_LEN("funcref"), fp); + } + if (event != NULL) { + tv_dict_add_str(d, S_LEN("event"), event); + } + tv_dict_add_nr(d, S_LEN("lnum"), lnum); + if (filepath_alloced) { + tv_dict_add_allocated_str(d, S_LEN("filepath"), filepath); + } else { + tv_dict_add_str(d, S_LEN("filepath"), filepath); + } + + tv_list_append_tv(l, &tv); +} + +/// Create the stacktrace from exestack. +list_T *stacktrace_create(void) +{ + list_T *const l = tv_list_alloc(exestack.ga_len); + + for (int i = 0; i < exestack.ga_len; i++) { + estack_T *const 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, false); + } else if (entry->es_type == ETYPE_UFUNC) { + ufunc_T *const fp = entry->es_info.ufunc; + const sctx_T sctx = fp->uf_script_ctx; + bool filepath_alloced = false; + char *filepath = sctx.sc_sid > 0 + ? get_scriptname((LastSet){ .script_ctx = sctx }, + &filepath_alloced) : ""; + lnum += sctx.sc_lnum; + stacktrace_push_item(l, fp, NULL, lnum, filepath, filepath_alloced); + } else if (entry->es_type == ETYPE_AUCMD) { + const sctx_T sctx = entry->es_info.aucmd->script_ctx; + bool filepath_alloced = false; + char *filepath = sctx.sc_sid > 0 + ? get_scriptname((LastSet){ .script_ctx = sctx }, + &filepath_alloced) : ""; + lnum += sctx.sc_lnum; + stacktrace_push_item(l, NULL, entry->es_name, lnum, filepath, filepath_alloced); + } + } + return l; +} + +/// getstacktrace() function +void f_getstacktrace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_set_ret(rettv, stacktrace_create()); +} + static bool runtime_search_path_valid = false; static int *runtime_search_path_ref = NULL; static RuntimeSearchPath runtime_search_path; diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua index e705c02e83..056e281c0b 100644 --- a/src/nvim/vvars.lua +++ b/src/nvim/vvars.lua @@ -220,7 +220,8 @@ M.vars = { type = 'string', desc = [=[ 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: >vim try throw "oops" @@ -701,6 +702,15 @@ M.vars = { < ]=], }, + stacktrace = { + type = 'table[]', + desc = [=[ + 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|. + ]=], + }, statusmsg = { type = 'string', desc = [=[ @@ -823,7 +833,7 @@ M.vars = { desc = [=[ The point where the exception most recently caught and not 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: >vim try throw "oops" |