aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2025-01-03 20:12:15 +0800
committerzeertzjq <zeertzjq@outlook.com>2025-01-07 09:15:10 +0800
commitd5308637bf1aac2b97fccf73a0ffdef304eaa1d6 (patch)
treeacb03c837290a225f1fcb7f73b4ae1306d1e7589 /src
parent06ff5480ce274daf3b7ad9950a587099200dc8ff (diff)
downloadrneovim-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.c1
-rw-r--r--src/nvim/eval.h1
-rw-r--r--src/nvim/eval.lua19
-rw-r--r--src/nvim/eval/typval.c24
-rw-r--r--src/nvim/ex_eval.c7
-rw-r--r--src/nvim/ex_eval_defs.h2
-rw-r--r--src/nvim/runtime.c66
-rw-r--r--src/nvim/vvars.lua14
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"